(七)、锁存器
1、原理
蓝桥杯中数据传入口都是P0,也就是数码管段选、位选数据、LED亮灭的数据、蜂鸣器启动或禁用的数据,外设启动或者关闭都需要通过P0写入数据,那么如何这样共用一个端口会造成冲突嘛,答案是肯定的。所以蓝桥杯加入了锁存器---通过P2高三位(22、21、20)操作。如图 三十LED锁存器对应地址为Y4C,凑成高三位数据100,后五位自动补零,P2对应数据1000 0000(用十六进制表示0x80)。Y5C对应数据就是1010 0000(0xa0)…
2、代码示范与解读
如 图 三十一,首先定义一个temp临时变量,再在P0口传入数据
P2 & 0x1f就是保留低五位,单独使高三位全为0,因为高三位才是锁存器的选择位。
再单独改变temp = temp | 0x80,0x80是刚刚推出来的数据,这样|上,就是单独修改了高三位,低五位 保持不变。再将数据放入P2中,此时P0中数据就已经传入了。
最后我们temp = P2 & 0x1f就是清空高三位,P2 = temp关闭锁存器。注意如果此时不关闭锁存器,那么一旦有数据传入P0就会被立即写入。
对这个代码进行推广,只需改变0x80那个位置的数据即可,假设想操作数码管位选的锁存器Y6C,可知为1100 0000(即为0xc0),将0x80改成0xc0就可以传入数码管位选数据了。
图 三十 LED原理图
图 三十一 借助锁存器写入数据
(八)、LED
1、原理(图 三十)
在P00-P07口传入数据,对应第一盏灯到第八盏灯,打开锁存器,就可以实现LED亮灭了。由原理图可知,为共阳极接法,当P0端口传入低电平数据时,就点亮LED了。注意51单片机LED亮灭可能不同。
2、代码解读(图 三十二)
定义一个数组,传入LED亮灭的数据,1为亮表示使能,0为灭表示失能
定义两个变量,temp_1与temp_1_old,只有这两者不相等时操作寄存器(改变)写入,temp_1存放亮灭的八位数据。
LED_Buf[0]<<0就是将第一个灯数据左移0位,放在temp_1的最低位,就是操作P00第一个灯。LED_Buf[1]<<1就是把第二个灯数据按二进制左移1位,放在temp_1的Bit2位…
有改变发生,两者不同了,写入数据,记住低电平点亮灯,所以要取反,在打开LED的锁存器,更新temp_1_old的值。
在主程序中我们可以打开定时器0,写一个1ms中断函数,在中断中扫描这个函数。
图 三十二 LED代码解读
(九)、继电器、蜂鸣器、Motor
1、原理(图 三十三)
最左边P01~P07是数据输入端口,最右边Relay就是对应继电器,Motor就是发动机,Buzz对应蜂鸣器,低电平0就是打开这个设备,但是ULN2003设备在中间会对数据进行取反,所以打开Relay数据为 空一位011 1111,对应最左边数据为 0001 0000,即为0x10。同理打开蜂鸣器即为0x40,打开Motor即为0x20。
图 三十三 外设原理图
2、代码解读(图 三十四 )
开局必须定义两个全局变量为蜂鸣器、继电器、发动机共用,防止不同变量操作对其他设备影响。
当Flag为1时,必须单独变化第四位为1,其余的不变,用 | 操作符来实现,|上0这一位就是保持不变,|上1就是强制为1。当Flag为1时,第四位为0,其余的不变,用 & 符来实现。
temp_2就是临时数据值,temp_2_Old就是记录上一个状态的值,不相同说明状态发生了改变,将数据传入P0端,打开锁存器即可。
图 三十四 代码解读(继电器)
3、代码推广(图 三十五、图 三十六)
将0x10改为0x40就是单独打开蜂鸣器代码,改成0x20就是启动Motor电机代码,电机适用于设计PWM波形的。一般来说只考继电器在特定条件下打开与关闭。
图 三十五 蜂鸣器推广代码
图 三十六 Motor推广代码
4、提供参考代码,希望对读者有帮助
#include <STC15F2K60S2.H>
idata unsigned char temp_1 = 0x00;
idata unsigned char temp_old_1 = 0xff;
void LED_Disp(unsigned char *LED_Buf) //传入LED数据数组,1亮、0灭
{
unsigned char temp;
temp_1 = 0x00;
temp_1 = (LED_Buf[0]<<0) | (LED_Buf[1]<<1) |(LED_Buf[2]<<2) |
(LED_Buf[3]<<3) |(LED_Buf[4]<<4) |(LED_Buf[5]<<5) |(LED_Buf[6]<<6) |
(LED_Buf[7]<<7); //通过一个数组传入8个LED灯的数据
//LED_Buf[0]<<0,假设第一个数据为1,数据<<(按位左移)0就是让Bit1为1
//LED_Buf[1]<<1,假设第一个数据为1,数据<<(按位左移)1就是让Bit2为1
if(temp_1 != temp_old_1) //数据改变
{
P0 = ~temp_1; //一定记得取反
temp = P2 & 0x1f;
temp = temp | 0x80; //操作Led灯的锁存器
P2 = temp;
temp = P2 & 0x1f;
P2 = temp;
temp_old_1 = temp_1; //更新temp_old_1
}
}
idata unsigned char temp_2 = 0x00;
idata unsigned char temp_2_old = 0xff;
//继电器操作函数
void Relay_Disp(unsigned char Flag)
{
unsigned char temp;
if(Flag)
temp_2 |= 0x10;
else
temp_2 &= ~0x10;
if(temp_2 != temp_2_old)
{
P0 = temp_2;
temp = P2 & 0x1f;
temp = temp | 0xa0;
P2 = temp;
temp = P2 & 0x1f;
P2 = temp;
temp_2_old = temp_2;
}
}
//蜂鸣器操作函数
void Beep_Disp(unsigned char Flag)
{
unsigned char temp;
if(Flag)
temp_2 |= 0x40;
else
temp_2 &= ~0x40;
if(temp_2 != temp_2_old)
{
P0 = temp_2;
temp = P2 & 0x1f;
temp = temp | 0xa0;
P2 = temp;
temp = P2 & 0x1f;
P2 = temp;
temp_2_old = temp_2;
}
}
//Motor操作函数
void Motor_Disp(unsigned char Flag)
{
unsigned char temp;
if(Flag)
temp_2 |= 0x20;
else
temp_2 &= ~0x20;
if(temp_2 != temp_2_old)
{
P0 = temp_2;
temp = P2 & 0x1f;
temp = temp | 0xa0;
P2 = temp;
temp = P2 & 0x1f;
P2 = temp;
temp_2_old = temp_2;
}
}
5、提供定时器1代码与中断
//定时器一初始化,自己加上EA = 1;ET1 = 1;
void Timer1_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
EA = 1; //打开总中断
ET1 = 1; //打开定时器一中断允许位
}
void Timer1_Routine() interrupt 3
{
LED_Disp(LED_Buf); //LED扫描
}