文章目录
前言
本人是某大学一普通大一学生,磨磨唧唧学了一学期51,但没有实践过。听学长学姐推荐蓝桥杯比赛,就报名学习蓝桥杯比赛开发板。于是就有了我想写博客记录学习经历。第一篇博客,写的菜随便骂(不是
一、功能要求
1、功能图
二、硬件分析
众所周知,CT107D开发板的基础元器件由74HC138(后面简称138)和73HC573(后面简称573)扩展IO来控制。
如图:YNC接在对应573的LE引脚。
即为138通过四个或非门(74HC02)来控制不同的锁存器,进而控制不同的基础元器件。因此,可写一个选择573的函数,方便进行调用。
1、功能一硬件电路:
如图可知,当138选中Y5C时P0口控制步进电机,继电器,蜂鸣器。只需控制N_BUZZ,N_RELAY输出高电平即可关闭蜂鸣器和继电器,完成功能一·。(ULN2003左IN输入电平与右OUT输入电平相反)
2、功能二硬件电路:
138选中Y4C,写一个函数实现流水灯效果。(这大概是大家最熟练的 /狗头)
3、功能三硬件电路:
CT107D板子为共阳数码管,因此Y6C控制阳极(哪个数码管显示),Y7C控制阴极(数码管显示扫描数字)。因为要节约IO口,数码管显示就要用到我们最爱的动态扫描了。
动态扫描:一次点亮一个数码管,并显示相应的数字,即可做到16个io口控制八个数码管,因为用上了138和573,实际上只用了8+3个IO口(P0口可以复用)。
4、功能四硬件电路:
LED电路在上面
CON3跳线帽接到23脚上!!(此处只控制两个灯,暂用不到动态扫描)
三、代码
1、整体流程(主函数)
设定定时器中断,计算时间的同时动态刷新数码管(中断刷新数码管可以防止止数码管出现不显示的情况)
void main()
{
Close_Other(); //关闭蜂鸣器与继电器
Init_Test(); //初始化检测函数
Init_UART(); //初始化串口
Init_Timer0(); //初始化定时器0中断,每1ms进入一次中断
while(1)
{
SMG_Show();//每隔200ms刷新显示一次数码管
KeyState(); //扫描按键状态,内部执行按键函数
UARTState(); //扫描串口状态,内部执行串口函数
}
}
2、138选择函数与关闭蜂鸣器与串口
void Sel_HC138(unsigned char n)
{
/*
通过控制P2口控制138,此算法可不影响P2口后三位状态
*/
switch(n)
{
case 4:P2 = (P2 & 0x1F) | 0x80;break;//LED
case 5:P2 = (P2 & 0x1F) | 0xa0;break;//步进电机、蜂鸣器、继电器
case 6:P2 = (P2 & 0x1F) | 0xc0;break;//SMG阴极
case 7:P2 = (P2 & 0x1F) | 0xe0;break;//SMG阳极
case 0:P2 = (P2 & 0x1F) | 0x00;break;//锁存数据,退出前一锁存器,防止改变数据。
default: break;
}
}
void Close_Other()
{
Sel_HC138(5); //选择Y5锁存器
P0 = 0x00; //关闭所有设备
Sel_HC138(0); //锁存数据,退出状态
}
位运算解析
位运算符 | 1 | 0 |
---|---|---|
& | x本身 | 0 |
I | 1 | x本身 |
^ | x相反 | x本身 |
"x"为对应计算数,如: 0bxxxx & 0b1111 = 0b1111;
3、检测,初始化定时器0,串口
检测函数,LED流水灯与数码管流水灯的实现。
/*流水灯应该不用说什么吧*/
void SMG_Test()
{
unsigned char i;
for(i=0;i<9;i++)
{
Sel_HC138(6);
P0 = 0x80>>i;
Sel_HC138(7);
P0 = 0x00; //数码管流水灯
Delay(60000);
Delay(60000);
Delay(60000);
}
}
void Init_Test()
{
unsigned char i;
Sel_HC138(4);
for(i=0;i<8;i++)
{
P0 = ~(0x80>>i);
Delay(60000);
Delay(60000);
Delay(60000);
}
P0 = 0xFF;//流水灯结束
Sel_HC138(0);
SMG_Test();
}
初始化串口和定时器0寄存器,可用STC烧录软件快速完成。
void Init_UART()
{
TMOD = TMOD | 0x20; //定时器1八位重装载模式
TH1 = 0xfd; //波特率9600
TL1 = 0xfd;
TR1 = 1;
SCON = 0x50; //串口模式
AUXR = 0x00;
ES = 1; //打开串口中断
EA = 1;
}
void Init_Timer0()
{
TMOD |= 0x01; //定时器零十六位计数模式
TH0 = (65535 - 1000)/256;
TL0 = (65535 - 1000)%256;
//设定1ms延时
TR0 = 1;
ET0 = 1;
EA = 1;
}
reg52.h中没有定义AUXR寄存器,因此需要自行定义sfr AUXR = 0x8e(0x8e为AUXR地址)或者使用stc15f2k60s2.h头文件。
4、中断下的数码管刷新显示
①、初始化所需全局变量
//初始化
unsigned char t_h=0,t_m=0,t_s=0;//时,分,秒(定时器计数)
unsigned char code SMG_num[]={
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90
};//共阳数码管数字数组
unsigned char SMG_COM[8] = {
0,0,0,0,0,0,0,0
};//数码管显示内容,将每位所得数字对应的SMG_num值存入该数组中
②、利用中断计算t_h, t_m, t_s(时,分,秒)
因为前面中断设置每1ms进入一次,所以可设置静态变量Num1ms利用中断进入次数计算时间。计算如下:
static unsigned int Num1ms = 0;
Num1ms++;//进入一次1ms数加一
if(Num1ms >= 1000) //1000ms == 1s
{
Num1ms = 0; //1s时Num1ms归零重新开始计算
t_s++;
if(t_s >= 60) //秒分计数60进1
{
t_s = 0; //进1归零
t_m++;
if(t_m >= 60) //分时计数60进1
{
t_h++;
t_m = 0; //进1归零
}
}
}
③、得出时间,对时间处理并存入SMG_COM[ ]数组中
将所得的每个数字用SMG_num的形式表达并存入对应SMG_COM(数码管数据备份)中。
void SMG_Show()
{
SMG_COM[7] = SMG_num[t_h/10];
SMG_COM[6] = SMG_num[t_h%10];
SMG_COM[5] = 0xBF;
SMG_COM[4] = SMG_num[t_m/10];
SMG_COM[3] = SMG_num[t_m%10];
SMG_COM[2] = 0xBF;
SMG_COM[1] = SMG_num[t_s/10];
SMG_COM[0] = SMG_num[t_s%10];
//下方为串口数据处理
/*TimerChar[0] = t_h/10+'0';
TimerChar[1] = t_h%10+'0';
TimerChar[2] = '-';
TimerChar[3] = t_m/10+'0';
TimerChar[4] = t_m%10+'0';
TimerChar[5] = '-';
TimerChar[6] = t_s/10+'0';
TimerChar[7] = t_s%10+'0';
TimerChar[8] = '\r';
TimerChar[9] = '\n';*/
}
④、完善中断
我们已经对数码管写出了三个函数,不过还差一个最重要的函数!即:显示函数
因为要设置每个数码管间的刷新间隔为1ms,所以设置控制某个数码管是否显示时应再中断中设置。
void it0() interrupt 1
{
static unsigned int Num1ms = 0;
TH0 = (65535 - 1000)/256;
TL0 = (65535 - 1000)%256;
Num1ms++;
if(Num1ms >= 1000)
{
Num1ms = 0;
t_s++;
if(t_s >= 60)
{
t_s = 0;
t_m++;
if(t_m >= 60)
{
t_h++;
t_m = 0;
}
}
}
SMG_Scan(Num1ms%8);//Num1ms%8所得为,进入中断时,应当亮的数码管号
}
P0控制数码管的显示内容,对应SMG_num[ ]中的值。
例如:P0 = SMG_num[0]且该数码管阳极为正时,对应数码管显示为0。
完善SMG_scan为
void SMG_Scan(unsigned char i)
{
Sel_HC138(6);//打开数码管阳极锁存器
P0 = 0x80>>i;//选择对应数码管使其发光
Sel_HC138(7);//选择数码管阴极锁存器
P0 = SMG_COM[i];//输出对应i的值
Sel_HC138(0);//锁存数据,退出锁存器
}
5、按键、串口控制LED
①、规定串口控制协议
高四位选择功能,当高四位值为A时,第四位控制灯光开关。
②、串口控制
接收数据存入Char_RI中
void UARTIT1() interrupt 4
{
if(RI) //当检测到接收数据
{
RI = 0; // RI清零,防止不停接收
Char_RI = SBUF; //接收数据存入Char_RI中
}
}
处理Char_RI并实现规定的串口控制协议,处理完数据注意需要清空Char_RI的值,防止不停发送指令!!!
void SendTimer0(unsigned char *str)//接收时间字符串
{
while(*str!='\0') //当检测到'\0'退出
{
SBUF = *str++; //发送一位字节字符串
while(TI == 0); //当发送完毕
TI = 0; //TI置零
}
Char_RI = 0; //清空Char_RI的值防止不停发送!
}
void UARTState()
{
if(Char_RI == 0xb0) //开头为b发送当前时间
{
SendTimer0(TimerChar); //发送当前时间
}
else if((Char_RI>>4)==0x0a)开头为a控制前四位LED
{
P0 = LED_data; //读取LED锁存器数据
Sel_HC138(4); //选择LED锁存
LED_data = (P0 | 0x0f)&(~(Char_RI&0x0f)); //改变LED_data中信息
P0 = LED_data; //储存数据
Sel_HC138(0); //锁存数据
Char_RI = 0; //停止接收信息
}
}
看到这里,很多同学可能会对时间字符串怎么来的有问题。由于中断中应尽量少写程序,防止中断时间误差增大,所以我们对t_h,t_m,t_s处理成字符串形式的代码与数码管数据处理一同放在SMG_show中。当然,如果觉得这样不够整齐也可以另开一个函数放在主函数中,这里我就偷一下懒吧。(见4-③,自行去掉下划线)
③、按键控制
众所周知,按键按下与弹起会有抖动问题,惹人很烦
抖动问题的软件解决有两种方法,一种是延迟10ms左右消抖,另一种是诸位读取字节(需要中断)。我们暂且用延迟法,而延迟时我们可以在while函数中写上时间处理的函数,又因为数码管刷新是中断函数,所以按键可以不影响到数码管的显示~~(几乎不影响)~~
void KeyState()
{
if(Key5 == 0)
{
Delay(500);//延迟
if(Key5 == 0)
{
while(Key5 == 0)
{
SMG_Show();
}
P0 = LED_data;
Sel_HC138(4);
LED_data = (LED_data&0xbf)|((LED_data&0x40)^0x40);
P0 = LED_data;
Sel_HC138(0);
}
}
if(Key4 == 0)
{
Delay(500);
if(Key4 == 0)
{
while(Key4 == 0)
{
SMG_Show();
}
P0 = LED_data;
Sel_HC138(4);
LED_data = (LED_data&0x7f)|((LED_data&0x80)^0x80);
P0 = LED_data;
Sel_HC138(0);
}
}
}
6、控制整合(源码)
因为只有一个P0口,而在锁存器切换过程中P0口的无法及时改变可能导致鬼影、led控制出错等问题,所以我们设置两个数据来存储对应锁存器数据,一个是LED_data,另一个SMG_data,在切换寄存器时,我们可以先变换对应的data,再把XXX_data赋值给P0,这样就可以避免一些P0变更不及时或者突变的错误。
源码度娘:
链接:https://pan.baidu.com/s/1poUThLg9ckJN2FCiFsTZsg
提取码:ci16
BUG总结
1、不能不设置存储锁存器数据,并且在切换锁存器,控制P0时,应该先改变对应存储锁存器的数据。
2、延时消抖需要在延时中写入需要功能防止按下按键时其它功能不反应。
3、串口接收的数据处理完后清零,防止不停接收!
4、位运算时,若想将某一或几位赋1或0应先将该(几)位统一清零或置一。