diy0769
发表于 2014-5-3 18:29:29
顶楼主,很详细很好理解,感谢分享
ztflmm
发表于 2014-5-3 21:56:26
写得很好。顶一个。
BADBADFISH
发表于 2014-5-3 22:48:01
请楼主收小弟为徒,呵呵呵,很好,拜读中,楼主加油。
szzxl10
发表于 2014-5-4 10:03:21
谢谢分享
yhtjay
发表于 2014-5-4 11:30:47
mark!这个帖子觉对要细读啊!
xujielong
发表于 2014-5-4 11:46:54
楼主加油!{:handshake:}
吴坚鸿
发表于 2014-5-5 11:40:34
本帖最后由 吴坚鸿 于 2014-5-5 12:03 编辑
第四十五节:主机的串口收发综合程序框架
开场白:
在大部分的项目中,串口都需要“一收一应答”的握手协议,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
上一节已经讲了从机,这节就讲主机的收发端程序实例。要教会大家四个知识点:
第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:主机端的收发端程序框架。包括重发,超时检测等等。
第三个:主机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。
第四个:其实上一节的LED灯闪烁的时间里,我忘了加原子锁,不加原子锁的后果是,闪烁的时间有时候会不一致,所以这节多增加一个原子锁变量ucLedLock,再次感谢“红金龙吸味”关于原子锁的建议,真的很好用。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用朱兆祺51单片机学习板中的S1,S5,S9,S13作为独立按键。
一共有4个窗口。每个窗口显示一个参数。串口可以把当前设置的4个数据发送给从机。从机端可以用电脑的串口助手来模拟。
第一:按键更改参数:
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是启动发送数据和复位按键,当系统处于待机状态时,按下此按键会启动发送数据;当通讯超时蜂鸣器报警时,可以按下此键清除报警,返回到待机的状态。
第二:通过串口把更改的参数发送给从机。
波特率是:9600.
通讯协议:EB 00 55GG 00 02 XX XXCY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表更改参数1,02代表更改参数2,03代表更改参数3,04代表更改参数4,
其中第5,6位00 02就是有效数据长度。高位在左,低位在右。
其中从第7,8位XX XX是被更改的参数。高位在左,低位在右。
第9位CY是累加和,前面所有字节的累加。
一个完整的通讯必须发送完4串数据,每串数据之间的间隔时间不能超过10秒钟,否则认为通讯超时主机会重发数据,如果连续三次都没有返回,则引发蜂鸣器报警。如果接收到得数据校验正确,主机继续发送新的一串数据,直到把4串数据发送完毕为止。
系统处于待机状态时,LED灯一直亮,
系统处于非待机状态时,LED灯闪烁,
系统处于出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。
通过电脑的串口助手来模拟从机,返回不同的应答
从机返回校验正确应答:eb 00 55 f5 00 00 35
从机返回校验出错应答:eb 00 55 fa 00 00 3a
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间
#define const_key_time420 //按键去抖动延时的时间
#define const_led_0_5s200 //大概0.5秒的时间
#define const_led_1s 400 //大概1秒的时间
#define const_send_time_out 4000//通讯超时出错的时间 大概10秒
#define const_rc_size20//接收串口中断数据的缓冲区数组大小
#define const_receive_time5//如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
#define const_send_size10//串口发送数据的缓冲区数组大小
void initial_myself(void);
void initial_peripheral(void);
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(void); //显示数码管字模的驱动函数
void display_service(void); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(void);//定时中断函数
void usart_receive(void); //串口接收中断函数
void usart_service(void);//串口接收服务程序,在main函数里
void communication_service(void); //一发一收的通讯服务程序
void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时
void key_service(void); //按键服务的应用程序
void key_scan(void);//按键扫描函数 放在定时中断里
void status_service(void);//状态显示的应用程序
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;//作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucSendregBuf; //发送的缓冲区数组
unsigned intuiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
unsigned intuiRcregTotal=0;//代表当前缓冲区已经接收了多少个数据
unsigned char ucRcregBuf; //接收串口中断数据的缓冲区数组
unsigned intuiRcMoveIndex=0;//用来解析数据协议的中间变量
unsigned charucSendCntLock=0; //串口计时器的原子锁
unsigned char ucRcType=0;//数据类型
unsigned intuiRcSize=0;//数据长度
unsigned char ucRcCy=0;//校验累加和
unsigned char ucLedLock=0; //原子锁
unsigned intuiLedCnt=0;//控制Led闪烁的延时计时器
unsigned intuiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
unsigned char ucSendTimeOutLock=0; //原子锁
unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错
unsigned char ucSendStep=0; //发送数据的过程步骤
unsigned char ucErrorCnt=0; //累计错误总数
unsigned char ucSendTotal=0; //记录当前已经发送了多少串数据
unsigned char ucReceiveStatus=0; //返回的数据状态 0代表待机 1代表校验正确 2代表校验出错
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt4=0; //按键去抖动延时计数器
unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁
unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容
unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1;//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned intuiSetData1=0;//本程序中需要被设置的参数1
unsigned intuiSetData2=0;//本程序中需要被设置的参数2
unsigned intuiSetData3=0;//本程序中需要被设置的参数3
unsigned intuiSetData4=0;//本程序中需要被设置的参数4
unsigned char ucTemp1=0;//中间过渡变量
unsigned char ucTemp2=0;//中间过渡变量
unsigned char ucTemp3=0;//中间过渡变量
unsigned char ucTemp4=0;//中间过渡变量
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//无 序号10
0x40,//- 序号11
0x73,//P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
usart_service();//串口接收服务程序
communication_service(); //一发一收的通讯服务程序
display_service(); //显示的窗口菜单服务程序
status_service();//状态显示的应用程序
}
}
void communication_service(void) //一发一收的通讯服务程序
{
unsigned int i;
if(ucStatus==1)//处于正在通讯的过程中
{
switch(ucSendStep)
{
case 0: //通讯过程0发送一串数据
switch(ucSendTotal)//根据当前已经发送到第几条数据来决定发送哪些参数
{
case 0: //发送参数1
ucSendregBuf=0xeb; //把准备发送的数据放入发送缓冲区
ucSendregBuf=0x00;
ucSendregBuf=0x55;
ucSendregBuf=0x01; //代表发送参数1
ucSendregBuf=0x00;
ucSendregBuf=0x02; //代表发送2个字节的有效数据
ucSendregBuf=uiSetData1>>8;//把int类型的参数分解成两个字节的数据
ucSendregBuf=uiSetData1;
break;
case 1://发送参数2
ucSendregBuf=0xeb; //把准备发送的数据放入发送缓冲区
ucSendregBuf=0x00;
ucSendregBuf=0x55;
ucSendregBuf=0x02; //代表发送参数2
ucSendregBuf=0x00;
ucSendregBuf=0x02; //代表发送2个字节的有效数据
ucSendregBuf=uiSetData2>>8;//把int类型的参数分解成两个字节的数据
ucSendregBuf=uiSetData2;
break;
case 2://发送参数3
ucSendregBuf=0xeb; //把准备发送的数据放入发送缓冲区
ucSendregBuf=0x00;
ucSendregBuf=0x55;
ucSendregBuf=0x03; //代表发送参数3
ucSendregBuf=0x00;
ucSendregBuf=0x02; //代表发送2个字节的有效数据
ucSendregBuf=uiSetData3>>8;//把int类型的参数分解成两个字节的数据
ucSendregBuf=uiSetData3;
break;
case 3://发送参数4
ucSendregBuf=0xeb; //把准备发送的数据放入发送缓冲区
ucSendregBuf=0x00;
ucSendregBuf=0x55;
ucSendregBuf=0x04; //代表发送参数4
ucSendregBuf=0x00;
ucSendregBuf=0x02; //代表发送2个字节的有效数据
ucSendregBuf=uiSetData4>>8;//把int类型的参数分解成两个字节的数据
ucSendregBuf=uiSetData4;
break;
}
ucSendregBuf=0x00;
for(i=0;i<8;i++)//最后一个字节是校验和,是前面所有字节累加,溢出部分不用我们管,系统会有规律的自动处理
{
ucSendregBuf=ucSendregBuf+ucSendregBuf;
}
for(i=0;i<9;i++)
{
eusart_send(ucSendregBuf);//把一串完整的数据发送给下位机
}
ucSendTimeOutLock=1; //原子锁加锁
uiSendTimeOutCnt=0;//超时计时器计时清零
ucSendTimeOutLock=0; //原子锁解锁
ucReceiveStatus=0;//返回的数据状态清零
ucSendStep=1;//切换到下一个步骤,等待返回的数据
break;
case 1: //通讯过程1判断返回的指令
if(ucReceiveStatus==1)//校验正确
{
ucErrorCnt=0; //累计校验错误总数清零
ucSendTotal++;//累加当前发送了多少串数据
if(ucSendTotal>=4) //已经发送完全部4串数据,结束
{
ucStatus=0;//切换到结束时的待机状态
}
else//还没发送完4串数据,则继续发送下一串新数据
{
ucSendStep=0;//返回上一个步骤,继续发送新数据
}
}
else if(ucReceiveStatus==2||uiSendTimeOutCnt>const_send_time_out)//校验出错或者超时出错
{
ucErrorCnt++; //累计错误总数
if(ucErrorCnt>=3)//累加重发次数3次以上,则报错
{
ucStatus=2;//切换到出错报警状态
}
else//重发还没超过3次,继续返回重发
{
ucSendStep=0;//返回上一个步骤,重发一次数据
}
}
break;
}
}
}
void status_service(void)//状态显示的应用程序
{
if(ucStatus!=0) //处于非待机的状态,Led闪烁
{
if(uiLedCnt<const_led_0_5s)//大概0.5秒
{
led_dr=1;//前半秒亮
if(ucStatus==2)//处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
{
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
}
}
else if(uiLedCnt<const_led_1s)//大概1秒
{
led_dr=0; //前半秒灭
}
else
{
ucLedLock=1; //原子锁加锁
uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
ucLedLock=0; //原子锁解锁
}
}
else//处于待机状态,Led一直亮
{
led_dr=1;
}
}
void usart_service(void)//串口接收服务程序,在main函数里
{
unsigned int i;
if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
{
ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
//下面的代码进入数据协议解析和数据处理的阶段
uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
{
if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
{
ucRcType=ucRcregBuf; //数据类型一个字节
uiRcSize=ucRcregBuf; //数据长度两个字节
uiRcSize=uiRcSize<<8;
uiRcSize=uiRcSize+ucRcregBuf;
ucRcCy=ucRcregBuf; //记录最后一个字节的校验
ucRcregBuf=0;//清零最后一个字节的累加和变量
for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
{
ucRcregBuf=ucRcregBuf+ucRcregBuf;
}
if(ucRcCy==ucRcregBuf)//如果一串数据校验正确,则进入以下数据指令的判断
{
switch(ucRcType) //根据不同的数据类型来做不同的数据处理
{
case 0xf5: //返回的是正确的校验指令
ucReceiveStatus=1;//代表校验正确
break;
case 0xfa: //返回的是错误的校验指令
ucReceiveStatus=2;//代表校验错误
break;
}
}
break; //退出循环
}
uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
}
uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据
}
}
void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
{
ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节
delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断
}
void display_service(void) //显示的窗口菜单服务程序
{
switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示P--1窗口的数据
if(ucWd1Update==1)//窗口1要全部更新显示
{
ucWd1Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=1; //第6位数码管显示1
ucDigShow5=10;//第5位数码管显示无
//先分解数据
ucTemp4=uiSetData1/1000;
ucTemp3=uiSetData1%1000/100;
ucTemp2=uiSetData1%100/10;
ucTemp1=uiSetData1%10;
//再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
if(uiSetData1<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData1<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData1<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 2://显示P--2窗口的数据
if(ucWd2Update==1)//窗口2要全部更新显示
{
ucWd2Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=2;//第6位数码管显示2
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData2/1000; //分解数据
ucTemp3=uiSetData2%1000/100;
ucTemp2=uiSetData2%100/10;
ucTemp1=uiSetData2%10;
if(uiSetData2<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData2<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData2<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 3://显示P--3窗口的数据
if(ucWd3Update==1)//窗口3要全部更新显示
{
ucWd3Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=3;//第6位数码管显示3
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData3/1000; //分解数据
ucTemp3=uiSetData3%1000/100;
ucTemp2=uiSetData3%100/10;
ucTemp1=uiSetData3%10;
if(uiSetData3<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData3<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData3<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 4://显示P--4窗口的数据
if(ucWd4Update==1)//窗口4要全部更新显示
{
ucWd4Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=4;//第6位数码管显示4
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData4/1000; //分解数据
ucTemp3=uiSetData4%1000/100;
ucTemp2=uiSetData4%100/10;
ucTemp1=uiSetData4%10;
if(uiSetData4<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData4<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData4<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
}
}
void key_scan(void)//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1;//自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1;//自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1;//自锁按键置位,避免一直触发
ucKeySec=3; //触发3号键
}
}
if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock4=0; //按键自锁标志清零
uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt4++; //累加定时中断次数
if(uiKeyTimeCnt4>const_key_time4)
{
uiKeyTimeCnt4=0;
ucKeyLock4=1;//自锁按键置位,避免一直触发
ucKeySec=4; //触发4号键
}
}
}
void key_service(void) //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1++;
if(uiSetData1>9999) //最大值是9999
{
uiSetData1=9999;
}
ucWd1Update=1;//窗口1更新显示
break;
case 2:
uiSetData2++;
if(uiSetData2>9999) //最大值是9999
{
uiSetData2=9999;
}
ucWd2Update=1;//窗口2更新显示
break;
case 3:
uiSetData3++;
if(uiSetData3>9999) //最大值是9999
{
uiSetData3=9999;
}
ucWd3Update=1;//窗口3更新显示
break;
case 4:
uiSetData4++;
if(uiSetData4>9999) //最大值是9999
{
uiSetData4=9999;
}
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1--;
if(uiSetData1>9999)
{
uiSetData1=0;//最小值是0
}
ucWd1Update=1;//窗口1更新显示
break;
case 2:
uiSetData2--;
if(uiSetData2>9999)
{
uiSetData2=0;//最小值是0
}
ucWd2Update=1;//窗口2更新显示
break;
case 3:
uiSetData3--;
if(uiSetData3>9999)
{
uiSetData3=0;//最小值是0
}
ucWd3Update=1;//窗口3更新显示
break;
case 4:
uiSetData4--;
if(uiSetData4>9999)
{
uiSetData4=0;//最小值是0
}
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
ucWd++;//切换窗口
if(ucWd>4)
{
ucWd=1;
}
switch(ucWd)//在不同的窗口下,在不同的窗口下,更新显示不同的窗口
{
case 1:
ucWd1Update=1;//窗口1更新显示
break;
case 2:
ucWd2Update=1;//窗口2更新显示
break;
case 3:
ucWd3Update=1;//窗口3更新显示
break;
case 4:
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 4:// 启动发送数据和复位按键 对应朱兆祺学习板的S13键
switch(ucStatus)//在不同的状态下,进行不同的操作
{
case 0://处于待机状态,则启动发送数据
ucErrorCnt=0; //累计错误总数清零
ucSendTotal=0; //已经发送串数据总数清零
ucSendStep=0; //发送数据的过程步骤清零,返回开始的步骤待命
ucStatus=1; //启动发送数据,1代表正在通讯过程
break;
case 1://处于正在通讯的过程
break;
case 2: //发送数据出错,比如中间超时没有接收到数据
ucStatus=0; //切换回待机的状态
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive(void)
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1://显示第1位
ucDigShowTemp=dig_table;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void usart_receive(void) interrupt 4 //串口接收数据中断
{
if(RI==1)
{
RI = 0;
++uiRcregTotal;
if(uiRcregTotal>const_rc_size)//超过缓冲区
{
uiRcregTotal=const_rc_size;
}
ucRcregBuf=SBUF; //将串口接收到的数据缓存到接收缓冲区里
if(ucSendCntLock==0)//原子锁判断
{
ucSendCntLock=1; //加锁
uiSendCnt=0;//及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
ucSendCntLock=0; //解锁
}
}
else//我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
{
TI = 0;//如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
}
}
void T0_time(void) interrupt 1 //定时中断
{
TF0=0;//清除中断标志
TR0=0; //关中断
/* 注释一:
* 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
*/
if(ucSendCntLock==0)//原子锁判断
{
ucSendCntLock=1; //加锁
if(uiSendCnt<const_receive_time) //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
{
uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
ucSendLock=1; //开自锁标志
}
ucSendCntLock=0; //解锁
}
if(ucVoiceLock==0) //原子锁判断
{
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
}
if(ucStatus!=0) //处于非待机的状态,Led闪烁
{
if(ucLedLock==0)//原子锁判断
{
uiLedCnt++; //Led闪烁计时器不断累加
}
}
if(ucStatus==1) //处于正在通讯的状态,
{
if(ucSendTimeOutLock==0)//原子锁判断
{
uiSendTimeOutCnt++; //超时计时器累加
}
}
key_scan(); //按键扫描函数
display_drive();//数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++)//内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself(void)//第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
led_dr=1;//点亮独立LED灯
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
//配置串口
SCON=0x50;
TMOD=0X21;
/* 注释三:
* 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
* 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
*/
IP =0x10;//把串口中断设置为最高优先级,必须的。
TH1=TL1=-(11059200L/12/32/9600);//串口波特率为9600。
TR1=1;
}
void initial_peripheral(void) //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ES=1; //允许串口中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
总结陈词:
前面花了大量篇幅详细地讲解了串口收发数据的程序框架,从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。
(未完待续,下节更精彩,不要走开哦)
happy521837
发表于 2014-5-5 14:20:52
mark,支持一下
u123321
发表于 2014-5-6 09:04:57
再支持一下,学习一下
peter_zj
发表于 2014-5-6 09:26:56
非常感谢!!!!!!
pangzi530
发表于 2014-5-6 09:56:07
谢谢楼主分享!
wangkx1990
发表于 2014-5-6 10:08:14
支持并mark下{:2_27:}
sky_prince
发表于 2014-5-6 10:32:15
牛人。
wkgwz
发表于 2014-5-6 12:30:56
不错,支持一下
wang55
发表于 2014-5-6 14:29:26
好资料,可以让我少走很多弯路。谢谢楼主分享。
lzy_scanworld
发表于 2014-5-6 15:20:44
楼主好人啊,果断顶起!
beibaoshangxue
发表于 2014-5-6 16:41:35
不错。常来看看!
名海2012
发表于 2014-5-7 09:21:18
谢谢楼主。。。受益匪浅啊!{:handshake:}
aragon
发表于 2014-5-7 10:42:22
看了一点,,,,是个高手,,,,,先顶一个,,,,赞!~~~
261854681
发表于 2014-5-7 13:14:59
节后一上班,没有想到我一个小小的请求,楼主又接着更新了44,45节的串口部分内容,读完后原来模糊的概念渐渐清晰起来,不再如黑夜中走路般找不见方向。相信很多初学者会从你的文章中接受更多的经验与知识,见识。感谢!
xxzzhy
发表于 2014-5-7 23:58:08
值得好好看看。
second_chan
发表于 2014-5-9 09:20:15
顶一个呀!怎么最近都没有更新啦!!!
micintcnh
发表于 2014-5-9 15:42:38
慢慢看吧,信息量大呀!
xinwu
发表于 2014-5-9 18:15:43
吴坚鸿手把手教你单片机程序框架(1-45)整理
蓝蓝的恋
发表于 2014-5-10 09:48:24
先标记了,程序这东西说不定就用着了~
u123321
发表于 2014-5-10 11:40:17
好样的,下载再学习
roywanglu
发表于 2014-5-10 15:16:39
好!{:handshake:}
xxzzhy
发表于 2014-5-10 21:28:23
楼上兄弟整理的不错。{:smile:}
xxzzhy
发表于 2014-5-10 21:29:56
群主什么时候出下一节啊
吴坚鸿
发表于 2014-5-10 23:43:59
xinwu 发表于 2014-5-9 18:15
吴坚鸿手把手教你单片机程序框架(1-45)整理
辛苦了,兄弟。我支持你。
hithms
发表于 2014-5-11 00:57:08
LZ的分享精神很值得学习。有空必须仔细研读。。。
tt98
发表于 2014-5-11 13:39:57
座等楼主下文!{:titter:}
xinwu
发表于 2014-5-11 16:48:02
吴坚鸿 发表于 2014-5-10 23:43
辛苦了,兄弟。我支持你。
鸿哥客气了,应该感谢你的付出,把多年的经验分享给我们!
感觉很好,就整理了一下。就顺便给有需要的坛友。
看电脑眼睛太累了,我是直接打印下来的看的,也方便很多。
biying
发表于 2014-5-12 09:47:40
吴坚鸿 发表于 2014-3-15 09:52
第三十四节:在数码管中实现iphone4S开机密码锁的程序。
开场白:
吴老师,请教你一下,在数码管中实现iphone4S开机密码锁的程序中第85行,unsigned char ucInputPassword;//在第1个窗口下,显示输入的4个密码,如何实现动态设置密码位数?我现在学的数组都是固定位数的,可在密码锁中如果重新设置密码的话,要想随意设置密码位数就处理不过来了,希望在这里能得到您的指导!
liuzp001
发表于 2014-5-12 09:53:02
鸿哥,你好!
这两天,用你的菜单理论,写一个电力多功能液晶表的菜单,发现一个问题:、
菜单的来回跳转,有个现场进入、还原的问题,可能是从子菜单、孙菜单返回主菜单。
窗口的刷新标志,窗口默认的子菜单标号等。
我是一边写,一边试验,发现问题再补, 我总觉得这不是个办法,有统一的,更专业的方法吗?
虽然目前菜单已完成。
liuzp001
发表于 2014-5-12 09:59:40
还有一个问题:没有足够的代码经验, 事先画状态图,再写代码,不行啊!
我是写完了,功能实现了,再归纳,总结,规范程序,后者化的时间,比写代码还要多得多!
高手们,是一次性就将代码完成吗?
liuzp001
发表于 2014-5-12 10:02:59
/* 菜单结构*/
#include "..\INC\LCD_SK.H"
#include "..\INC\LCD_HT1623.H"
#include "..\INC\key_scan.h"
/*-------------------与菜单相关的变量-------------------------*/
/*----窗口ID变量:
* 1.当前菜单刷新----->用switch查询当前有效的子菜单。
* 2.当前菜单不刷新--->执行当前菜单下的按键服务程序。
-------------------------------------------------------------*/
unsigned char uc_Wd_Top = 1; //顶层:4个定时循环显示的页面
unsigned char ucWd=0; //主菜单窗口ID
//默认:每次显示子菜单1
unsigned char uc_Wd_Login=1; //主菜单1:登录窗口
unsigned char uc_Wd_PerSet=1; //主菜单2:参数设置
//窗口刷新标志变量
//顶层刷新-->主菜单;主菜单刷新-->一级子菜单。。。。
unsigned char uc_Top_Update = 0; //顶层
unsigned char uc_Login_Update = 0; //主菜单1:密码登录
unsigned char uc_PerSet_Update = 0;//主菜单2:参数设置
unsigned char ucValidBit=3; //密码登录框:当前有效位
unsigned int ui_CT=0;
unsigned int ui_PT=0;
unsigned int ui_ADDR=0;
unsigned int ui_BAUD=0;
unsigned int ui_NET=0;
unsigned int ui_temp;
//顶层菜单
void Menu_TOP_Disp(void); //顶层:显示参数
void KeyService_TOP(void); //顶层按键服务程序。
//主菜单1--登录
void Menu_Login(void);
void KeyService_Login(void);
void Menu_Login_Disp(void);
//主菜单1--登录--密码输入
void Menu_Login_Password(void);
//主菜单2--参数设置
void Menu_PerSet(void);
void KeyService_PerSet(void);
void Menu_PerSet_Disp(void);
//主菜单2--参数设置--下拉子菜单
unsigned int KeyService_PerSet_Input(void);
void Menu_PerSet_CT(void);
void Menu_PerSet_PT(void);
void Menu_PerSet_ADDR(void);
void Menu_PerSet_BAUD(void);
void Menu_PerSet_NET(void);
extern unsigned int uiTimeCnt;
extern unsigned char password;
/* 设计主菜单、子菜单、孙菜单名称
*1. 反应该函数是哪一层
主:Wd子:Part孙:sub
层:layerLayer
第3层:主名_子名_孙名
第2层:主名_子名 PerSet_CT 参数设置主菜单-->下拉菜单 设置CT
第1层:主名
*2. 功能 加前缀 Menu : 表示是菜单函数。
login:登录 Password:密码
Menu_Login :菜单函数-主菜单登录
Menu_Login_Password :
Menu_Login_Password_Input :
Menu_PerSet :
Menu_PerSet_CT :
Menu_PerSet_CT_Input :
-----------------------------------------------------------------*/
/* -----------------------菜单总框架-----------------------------*/
void key_service(void)
{
if(uc_Top_Update==0) //顶层不刷新
{
Menu_TOP_Disp(); //循环显示4个参数页面。
KeyService_TOP(); //顶层按键服务程序。
}
else if(uc_Top_Update==1) //刷新顶层菜单
{
switch(ucWd) //查询主菜单页面ID
{
case 1:
Menu_Login(); //主菜单1:登录
break;
case 2:
Menu_PerSet();//主菜单2:参数设置
break;
}
}
}
/*---------------顶层====开机显示函数------------------------------
* 开机显示各项参数,定义为顶层菜单内容。
------------------------------------------------------------------*/
void Menu_TOP_Disp(void)//循环显示4个参数页面
{
switch(uc_Wd_Top) //中断计数:每隔5s,ucWdTop加1.
{
case 1: //顶层页面1:显示三相电压
LCD_Clear();
LCD_Disp_WdU();
break;
case 2: //顶层页面2:显示三相电流
LCD_Clear();
LCD_Disp_WdI();
break;
case 3: //顶层页面3:显示功率、功率因数
LCD_Clear();
LCD_Disp_WdP1();
break;
case 4: //顶层页面4:功率、 交流电频率
LCD_Clear();
LCD_Disp_WdP2();
break;
}
}
void KeyService_TOP(void)//顶层按键服务程序。
{
switch(ucKeySec)
{
case 1:
ucKeySec=0;
break;
case 2:
ucKeySec=0;
uc_Wd_Top++;
if(uc_Wd_Top>4)
uc_Wd_Top=1;
uiTimeCnt=0;
break;
case 3:
ucKeySec=0;
uc_Wd_Top--;
if(uc_Wd_Top==0)
uc_Wd_Top=4;
uiTimeCnt=0;
break;
case 4:
ucKeySec=0;
uc_Top_Update = 1; //刷新顶层菜单。
ucWd = 1; //1. 选择主菜单项:主菜单1(登录)
// uc_Wd_Login= 1; //2. 选择子菜单项:子菜单1
//1. 开机默认:主菜单、子菜单的ID---都是1
//2. 不管处于哪级菜单:按确认键---刷新当前菜单即可。
//3. 返回上级菜单、直接返回顶层菜单:怎么办?
// 比如:主菜单2 --- PT设置子菜单中,参数设置完成后,直接返回顶层。
// 1。 参数设置主菜单下的子菜单ID uc_Wd_PerSet
// 2。 顶层菜单下主菜单ID uc_Wd
//写成函数: Back_to_TOP()Back_to_PerSet() Back_to_Login()
break;
}
}
/*-------------------------END: 顶层菜单------------------------------------*/
/*-----------------------主菜单1: 登录------------------------------------- */
void Menu_Login(void)
{
if(uc_Login_Update==0) //登录主菜单不刷新
{
Menu_Login_Disp(); //查询显示登录子菜单的名称。
KeyService_Login(); //主菜单1“登录”的按键服务程序。
}
else if(uc_Login_Update==1) //刷新登录主菜单
{
switch(uc_Wd_Login) //查询当前有效子菜单的ID,并且进入相应的子菜单。
{
case 1:
Menu_Login_Password(); //跳转:“登录”的子菜单-->密码输入。
break;
}
}
}
//开机第一次进入,什么也不显示;第二次进入:显示正确。
void Menu_Login_Disp(void) //显示登录的各项子菜单的名称。
{
switch(uc_Wd_Login) //查询当前有效子菜单的ID, 只显示其名称。
{
case 1:
LCD_Clear();
Disp_LCD_Line1(ID_P, ID_A, ID_S, ID_d); //显示子菜单项名称:“PASd”
break;
/* 密码登录:只有一个子菜单(显示登录框)*/
}
}
void KeyService_Login(void)//主菜单1“登录”:按键服务程序。
{
if(ucKeySec==4) //按下设置键
{
ucKeySec=0;
uc_Wd_Login= 1; //当前有效子菜单ID==1。
uc_Login_Update = 1; //刷新主菜单1登录
}
//1、2、3号键只要有一个被按下:
else if((ucKeySec==1)|(ucKeySec==2)|(ucKeySec==3)) //注释1:
{
ucKeySec=0;
uc_Wd_Login = 0; // 1. 登录主菜单——>窗口变量 ??不需要
uc_Top_Update = 0; // 2. 顶层页面不刷新
uc_Wd_Top = 1; // 3. 回到顶层参数显示页面1
uiTimeCnt=0; // 4. 5s计时:重新开始。
}
}
void Menu_Login_Password(void)//主菜单1"登录"--子菜单“密码输入”
{
LCD_Clear();
Disp_LCD_Line1(ID_P, ID_A, ID_S, ID_d); //液晶第一行: “PASd ”
LCD_Line2_bit_flash(ucValidBit); //液晶第二行:“0000”,当前有效位闪烁。
switch(ucKeySec)
{
case 1: //按下确认键
ucKeySec = 0;
//密码:0001,如果有一个不符合:
if( (password!=0)|(password!=0)|(password!=0)|(password!=1) ) //密码错误:返回顶层页面。
{
password=0; //1. 保存密码的数组清零
password=0;
password=0;
password=0;
uc_Top_Update = 0; //2. 顶层页面不刷新,自动退回顶层参数循环显示页面。
uc_Wd_Top = 1; //3. 回到顶层参数循环显示页面1。
uiTimeCnt=0; //4. 顶层页面计时进行中,清零,重新开始计时。
ucValidBit = 3; //5. 登录密码闪烁位: 回到起点。
uc_Login_Update=0; //6. 登录菜单刷新标志清零。
}
//密码:0001,如果密码全符合:
if((password==0)&&(password==0)&&(password==0)&&(password==1)) //密码正确:进入参数设置子菜单。
{
password=0; //1. 保存密码的数组清零
password=0;
password=0;
password=0;
ucWd = 2; //2. 密码正确:跳转--> 参数设置菜单。
uc_Wd_Login=0; //登录子菜单刷新标志归零
}
break;
case 2: //按下加键
ucKeySec=0;
password++; //当前有效位数字加1.
if(password>9)
password = 0;//上面越界:回到0。
break;
case 3: //按下减键
ucKeySec=0;
password--; //当前有效位数字减1.
if(password==255)// uchar型,0-1 = 255 !!
password = 9;// 下面越界:回到最大值9。
break;
case 4: //按下设置键
ucKeySec=0;
ucValidBit--; //当前有效位循环左移。
if(ucValidBit==255)
ucValidBit=3; //如果越界,回到起点。
break;
}
}
/*---------------------------END:主菜单1: 登录-------------------------*/
/*--------------------------主菜单2:参数设置 -------------------------*/
void Menu_PerSet(void)
{
if(uc_PerSet_Update==0) //参数设置主菜单不刷新
{
KeyService_PerSet(); //主菜单参数设置下的按键服务程序。
Menu_PerSet_Disp(); //显示参数设置主菜单下的各项子菜单。
}
else if(uc_PerSet_Update==1) //刷新参数设置主菜单
{
switch(uc_Wd_PerSet) //根据当前的子菜单ID:
{
case 1: Menu_PerSet_CT();
break;
case 2: Menu_PerSet_PT();
break;
case 3: Menu_PerSet_ADDR();
break;
case 4: Menu_PerSet_BAUD();
break;
case 5: Menu_PerSet_NET();
break;
}
}
}
void KeyService_PerSet(void) //主菜单2“参数设置”下的按键服务程序。
{
switch(ucKeySec)
{
case 1://退回顶层页面, 重新循环显示各项参数.
ucKeySec=0;
uc_Top_Update = 0; //1. 顶层页面不刷新.
uc_Wd_Top = 1; //2. 回到参数显示页面1.
uiTimeCnt=0; //3. 顶层页面计时进行中,清零,重新开始计时。
ucValidBit = 3; //4. 闪烁位: 回归起点。
uc_Wd_PerSet=1; //5. 保证下次再进入参数设置,还是从头开始.
uc_Login_Update=0; //
uc_Wd_Login=1;
break;
case 2:
ucKeySec=0;
uc_Wd_PerSet++; // 下一个子菜单ID
if(uc_Wd_PerSet>5)
uc_Wd_PerSet=1; // 回到第一项子菜单
break;
case 3:
ucKeySec=0;
uc_Wd_PerSet--; // 上一个子菜单ID
if(uc_Wd_PerSet==0)
uc_Wd_PerSet=5; // 退回到最后一个子菜单
break;
case 4:
ucKeySec=0;
uc_PerSet_Update = 1; //刷新参数设置窗口
break;
}
}
void Menu_PerSet_Disp(void) //参数设置主菜单的各个子菜单
{
switch(uc_Wd_PerSet)
{
case 1: //子菜单1:CT
LCD_Clear();
Disp_LCD_Line1(ID_NULL, ID_C, ID_T, ID_NULL); //显示”CT“
break;
case 2: //子菜单2:PT
LCD_Clear();
Disp_LCD_Line1(ID_NULL, ID_P, ID_T, ID_NULL); //显示”PT“
break;
case 3: //子菜单3:Addr
LCD_Clear();
Disp_LCD_Line1(ID_A, ID_d, ID_d, ID_R); //显示”AddR“
break;
case 4: //子菜单4:Baud
LCD_Clear();
Disp_LCD_Line1(ID_b, ID_A, ID_U, ID_d); //显示”bAUd“
break;
case 5: //子菜单5:NET
LCD_Clear();
Disp_LCD_Line1(ID_NULL, ID_N, ID_E, ID_T); //显示”NET“
break;
}
}
/*--------------------END: 主菜单2:参数设置----------------------------*/
/*-------------------主菜单2:参数设置--->下拉子菜单--------------------*/
void Menu_PerSet_CT(void)
{
LCD_Clear();
Disp_LCD_Line1(ID_NULL, ID_C, ID_T, ID_NULL); //显示”CT“
iVal_flash(ui_CT, ucValidBit);
ui_CT = KeyService_PerSet_Input();
// LCD_Line3_Disp(ui_CT); 测试返回值是否正确。
}
void Menu_PerSet_PT(void)
{
LCD_Clear();
Disp_LCD_Line1(ID_NULL, ID_P, ID_T, ID_NULL); //显示”PT“
LCD_Line2_bit_flash(ucValidBit);
ui_PT = KeyService_PerSet_Input();
}
void Menu_PerSet_ADDR(void)
{
LCD_Clear();
Disp_LCD_Line1(ID_A, ID_d, ID_d, ID_R); //显示”AddR“
LCD_Line2_bit_flash(ucValidBit);
ui_ADDR = KeyService_PerSet_Input();
}
void Menu_PerSet_BAUD(void)
{
LCD_Clear();
Disp_LCD_Line1(ID_b, ID_A, ID_U, ID_d); //显示”bAUd“
LCD_Line2_bit_flash(ucValidBit);
ui_BAUD = KeyService_PerSet_Input();
}
void Menu_PerSet_NET(void)
{
LCD_Clear();
Disp_LCD_Line1(ID_NULL, ID_N, ID_E, ID_T); //显示”NET“
// LCD_Line2_bit_flash(ucValidBit);
ui_NET = KeyService_PerSet_Input();
LCD_Line2_bit_flash(ucValidBit);
}
/*
* 1. 4位无符号数拆开,存放在password[]数组
* 2. 4位无符号数,当前有效位加、减后,重新确定其数值大小。
* 3. 确认密码有效:进入下级子菜单; 密码无效:返回上级菜单
* 4. 必须用中间变量:保存当前的菜单编号,下级子菜单号
*/
unsigned int KeyService_PerSet_Input(void)
{ //要点1:按下加、减、确认键,都必须重新计时ui_temp的值。
//要点2:必须先判断上、下标是否越界,再计算ui_temp的值。
// 否则:计算ui_temp时,password==255!!
switch(ucKeySec)
{
case 1: //按下确认键
ucKeySec = 0;
ui_temp = (1000*password+100*password+10*password+password);
uc_PerSet_Update=0; //1. 退出继续设置其它参数
password=0; //2. 保存密码的数组清零
password=0;
password=0;
password=0;
ucValidBit = 3; //闪烁位回归起始位。
break;
case 2: //按下加键
ucKeySec=0;
password++; //当前有效位数字加1.
if(password>9)
password = 0;//上面越界:回到0。
ui_temp = (1000*password+100*password+10*password+password);
break;
case 3: //按下减键
ucKeySec=0;
password--; //当前有效位数字减1.
if(password==255)// uchar型,0-1 = 255 !!
password = 9;// 下面越界:回到最大值9。
ui_temp = (1000*password+100*password+10*password+password);
break;
case 4: //按下设置键
ucKeySec=0;
ucValidBit--; //当前有效位循环左移。
if(ucValidBit==255)
{
ucValidBit= 3; //如果越界,回到起点。
}
break;
}
return(ui_temp);
}
liuzp001
发表于 2014-5-12 10:08:32
biying 发表于 2014-5-12 09:47
吴老师,请教你一下,在数码管中实现iphone4S开机密码锁的程序中第85行,unsigned char ucInputPassword[ ...
看我刚才的代码,已实现!
吴坚鸿
发表于 2014-5-12 13:01:31
biying 发表于 2014-5-12 09:47
吴老师,请教你一下,在数码管中实现iphone4S开机密码锁的程序中第85行,unsigned char ucInputPassword[ ...
这个很简单,你首先要学会如何通过外部按键或者串口去更改单片机内部数据的方法。具体方法请参考我第二十九节的内容。
吴坚鸿
发表于 2014-5-12 13:06:25
liuzp001 发表于 2014-5-12 09:59
还有一个问题:没有足够的代码经验, 事先画状态图,再写代码,不行啊!
我是写完了,功能实现了,再归纳, ...
我大概看了你以下发表的程序,你已经很不错了。一开始麻烦是很正常,以后多编几个程序就会找到它的规律了。就像打篮球一样,只有多打才会有手感。
我平时写代码很少画流程图,大部分都是一气呵成的。如果是涉及到通讯协议,我才会把具体的通讯协议先写出来。
吴坚鸿
发表于 2014-5-12 13:08:47
第四十六节:利用AT24C02进行掉电后的数据保存。
开场白:
一个AT24C02可以存储256个字节,地址范围是(0至255)。利用AT24C02存储数据时,要教会大家六个知识点:
第一个:单片机操作AT24C02的通讯过程也就是IIC的通讯过程, IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此在整个通讯过程中应该先关闭总中断,完成之后再开中断。
第二个:在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,应该适当继续把这个时间延长,尤其是在写入数据时。
第三个:如何初始化EEPROM数据的方法。系统第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。
第四个:在时序中,发送ACK确认信号时,要记得把数据线eeprom_sda_dr_s设置为输入的状态。对于51单片机来说,只要把eeprom_sda_dr_s=1就可以。而对于PIC或者AVR单片机来说,它们都是带方向寄存器的,就不能直接eeprom_sda_dr_s=1,而要直接修改方向寄存器,把它设置为输入状态。在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
第五个: 提醒各位读者在硬件上应该注意的问题,单片机跟AT24C02通讯的2根IO口都要加上一个4.7K左右的上拉电阻。凡是在IIC通讯场合,都要加上拉电阻。AT24C02的WP引脚一定要接地,否则存不进数据。
第六个:旧版的朱兆祺51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的朱兆祺51学习板已经改过来了。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:
4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。
显示和独立按键部分根据第29节的程序来改编,用朱兆祺51单片机学习板中的S1,S5,S9作为独立按键。
一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间
void initial_myself(void);
void initial_peripheral(void);
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(void); //显示数码管字模的驱动函数
void display_service(void); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void start24(void);//开始位
void ack24(void);//确认位
void stop24(void);//停止位
unsigned char read24(void);//读取一个字节的时序
void write24(unsigned char dd); //发送一个字节的时序
unsigned char read_eeprom(unsigned int address); //从一个地址读取出一个字节数据
void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
unsigned int read_eeprom_int(unsigned int address); //从一个地址读取出一个int类型的数据
void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据
void T0_time(void);//定时中断函数
void key_service(void); //按键服务的应用程序
void key_scan(void);//按键扫描函数 放在定时中断里
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit eeprom_scl_dr=P3^7; //时钟线
sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁
unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容
unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1;//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned intuiSetData1=0;//本程序中需要被设置的参数1
unsigned intuiSetData2=0;//本程序中需要被设置的参数2
unsigned intuiSetData3=0;//本程序中需要被设置的参数3
unsigned intuiSetData4=0;//本程序中需要被设置的参数4
unsigned char ucTemp1=0;//中间过渡变量
unsigned char ucTemp2=0;//中间过渡变量
unsigned char ucTemp3=0;//中间过渡变量
unsigned char ucTemp4=0;//中间过渡变量
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//无 序号10
0x40,//- 序号11
0x73,//P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
display_service(); //显示的窗口菜单服务程序
}
}
//AT24C02驱动程序
void start24(void)//开始位
{
eeprom_sda_dr_sr=1;
eeprom_scl_dr=1;
delay_short(15);
eeprom_sda_dr_sr=0;
delay_short(15);
eeprom_scl_dr=0;
}
void ack24(void)//确认位时序
{
eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入
eeprom_scl_dr=1;
delay_short(15);
eeprom_scl_dr=0;
delay_short(15);
//在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
//有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
}
void stop24(void)//停止位
{
eeprom_sda_dr_sr=0;
eeprom_scl_dr=1;
delay_short(15);
eeprom_sda_dr_sr=1;
}
unsigned char read24(void)//读取一个字节的时序
{
unsigned char outdata,tempdata;
outdata=0;
eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
delay_short(2);
for(tempdata=0;tempdata<8;tempdata++)
{
eeprom_scl_dr=0;
delay_short(2);
eeprom_scl_dr=1;
delay_short(2);
outdata<<=1;
if(eeprom_sda_dr_sr==1)outdata++;
eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
delay_short(2);
}
return(outdata);
}
void write24(unsigned char dd) //发送一个字节的时序
{
unsigned char tempdata;
for(tempdata=0;tempdata<8;tempdata++)
{
if(dd>=0x80)eeprom_sda_dr_sr=1;
else eeprom_sda_dr_sr=0;
dd<<=1;
delay_short(2);
eeprom_scl_dr=1;
delay_short(4);
eeprom_scl_dr=0;
}
}
unsigned char read_eeprom(unsigned int address) //从一个地址读取出一个字节数据
{
unsigned char dd,cAddress;
cAddress=address; //把低字节地址传递给一个字节变量。
/* 注释一:
* IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此
* 在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新
* 问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在操作EEPROM时,
* 数码管就会出现闪烁的现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管
* 的方案,应该用静态显示的方案。那么程序上还有没有改善的方法?有的,下一节我会讲这个问题
* 的改善方法。
*/
EA=0; //禁止中断
start24(); //IIC通讯开始
write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
//指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
ack24(); //发送应答信号
write24(cAddress); //发送读取的存储地址(范围是0至255)
ack24(); //发送应答信号
start24(); //开始
write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
//指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
ack24(); //发送应答信号
dd=read24(); //读取一个字节
ack24(); //发送应答信号
stop24();//停止
/* 注释二:
* 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,
* 写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。
* 否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,
* 应该适当继续把这个时间延长,尤其是在写入数据时。
*/
delay_short(800);//此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
EA=1; //允许中断
return(dd);
}
void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
{
unsigned char cAddress;
cAddress=address; //把低字节地址传递给一个字节变量。
EA=0; //禁止中断
start24(); //IIC通讯开始
write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
//指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
ack24(); //发送应答信号
write24(cAddress); //发送写入的存储地址(范围是0至255)
ack24(); //发送应答信号
write24(dd);//写入存储的数据
ack24(); //发送应答信号
stop24();//停止
delay_short(2000);//此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
EA=1; //允许中断
}
unsigned int read_eeprom_int(unsigned int address) //从一个地址读取出一个int类型的数据
{
unsigned char ucReadDataH;
unsigned char ucReadDataL;
unsigned intuiReadDate;
ucReadDataH=read_eeprom(address); //读取高字节
ucReadDataL=read_eeprom(address+1);//读取低字节
uiReadDate=ucReadDataH;//把两个字节合并成一个int类型数据
uiReadDate=uiReadDate<<8;
uiReadDate=uiReadDate+ucReadDataL;
return uiReadDate;
}
void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
{
unsigned char ucWriteDataH;
unsigned char ucWriteDataL;
ucWriteDataH=uiWriteData>>8;
ucWriteDataL=uiWriteData;
write_eeprom(address,ucWriteDataH); //存入高字节
write_eeprom(address+1,ucWriteDataL); //存入低字节
}
void display_service(void) //显示的窗口菜单服务程序
{
switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示P--1窗口的数据
if(ucWd1Update==1)//窗口1要全部更新显示
{
ucWd1Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=1; //第6位数码管显示1
ucDigShow5=10;//第5位数码管显示无
//先分解数据
ucTemp4=uiSetData1/1000;
ucTemp3=uiSetData1%1000/100;
ucTemp2=uiSetData1%100/10;
ucTemp1=uiSetData1%10;
//再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
if(uiSetData1<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData1<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData1<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 2://显示P--2窗口的数据
if(ucWd2Update==1)//窗口2要全部更新显示
{
ucWd2Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=2;//第6位数码管显示2
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData2/1000; //分解数据
ucTemp3=uiSetData2%1000/100;
ucTemp2=uiSetData2%100/10;
ucTemp1=uiSetData2%10;
if(uiSetData2<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData2<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData2<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 3://显示P--3窗口的数据
if(ucWd3Update==1)//窗口3要全部更新显示
{
ucWd3Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=3;//第6位数码管显示3
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData3/1000; //分解数据
ucTemp3=uiSetData3%1000/100;
ucTemp2=uiSetData3%100/10;
ucTemp1=uiSetData3%10;
if(uiSetData3<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData3<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData3<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 4://显示P--4窗口的数据
if(ucWd4Update==1)//窗口4要全部更新显示
{
ucWd4Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=4;//第6位数码管显示4
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData4/1000; //分解数据
ucTemp3=uiSetData4%1000/100;
ucTemp2=uiSetData4%100/10;
ucTemp1=uiSetData4%10;
if(uiSetData4<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData4<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData4<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
}
}
void key_scan(void)//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1;//自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1;//自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1;//自锁按键置位,避免一直触发
ucKeySec=3; //触发3号键
}
}
}
void key_service(void) //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1++;
if(uiSetData1>9999) //最大值是9999
{
uiSetData1=9999;
}
write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁
ucWd1Update=1;//窗口1更新显示
break;
case 2:
uiSetData2++;
if(uiSetData2>9999) //最大值是9999
{
uiSetData2=9999;
}
write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd2Update=1;//窗口2更新显示
break;
case 3:
uiSetData3++;
if(uiSetData3>9999) //最大值是9999
{
uiSetData3=9999;
}
write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd3Update=1;//窗口3更新显示
break;
case 4:
uiSetData4++;
if(uiSetData4>9999) //最大值是9999
{
uiSetData4=9999;
}
write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1--;
if(uiSetData1>9999)
{
uiSetData1=0;//最小值是0
}
write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd1Update=1;//窗口1更新显示
break;
case 2:
uiSetData2--;
if(uiSetData2>9999)
{
uiSetData2=0;//最小值是0
}
write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd2Update=1;//窗口2更新显示
break;
case 3:
uiSetData3--;
if(uiSetData3>9999)
{
uiSetData3=0;//最小值是0
}
write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd3Update=1;//窗口3更新显示
break;
case 4:
uiSetData4--;
if(uiSetData4>9999)
{
uiSetData4=0;//最小值是0
}
write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
ucWd++;//切换窗口
if(ucWd>4)
{
ucWd=1;
}
switch(ucWd)//在不同的窗口下,在不同的窗口下,更新显示不同的窗口
{
case 1:
ucWd1Update=1;//窗口1更新显示
break;
case 2:
ucWd2Update=1;//窗口2更新显示
break;
case 3:
ucWd3Update=1;//窗口3更新显示
break;
case 4:
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive(void)
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1://显示第1位
ucDigShowTemp=dig_table;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time(void) interrupt 1 //定时中断
{
TF0=0;//清除中断标志
TR0=0; //关中断
/* 注释三:
* 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
*/
if(ucVoiceLock==0) //原子锁判断
{
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
}
key_scan(); //按键扫描函数
display_drive();//数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++)//内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself(void)//第一区 初始化单片机
{
/* 注释四:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
*/
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral(void) //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
/* 注释五:
* 如何初始化EEPROM数据的方法。在使用EEPROM时,这一步初始化很关键!
* 第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。
* 这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。另外,
* 由于int类型数据占用2个字节,所以以下4个数据挨着的地址是0,2,4,6.
*/
uiSetData1=read_eeprom_int(0);//读取uiSetData1,内部占用2个字节地址
if(uiSetData1>9999) //不在范围内
{
uiSetData1=0; //填入一个初始化数据
write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
}
uiSetData2=read_eeprom_int(2);//读取uiSetData2,内部占用2个字节地址
if(uiSetData2>9999)//不在范围内
{
uiSetData2=0;//填入一个初始化数据
write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
}
uiSetData3=read_eeprom_int(4);//读取uiSetData3,内部占用2个字节地址
if(uiSetData3>9999)//不在范围内
{
uiSetData3=0;//填入一个初始化数据
write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
}
uiSetData4=read_eeprom_int(6);//读取uiSetData4,内部占用2个字节地址
if(uiSetData4>9999)//不在范围内
{
uiSetData4=0;//填入一个初始化数据
write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
}
}
总结陈词:
IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此,在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在按键更改参数,内部操作EEPROM时,数码管就会出现短暂明显的闪烁现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管的方案,应该用静态显示的方案。那么在程序上还有没有改善这种现象的方法?当然有。欲知详情,请听下回分解-----操作AT24C02时,利用“一气呵成的定时器方式”改善数码管的闪烁现象。
(未完待续,下节更精彩,不要走开哦)
Codoox
发表于 2014-5-12 13:25:54
楼主辛苦,楼主威武!
stevenniu500
发表于 2014-5-12 18:03:09
楼主你太伟大了。下班了。先收藏。回去继续看!!!!
牛东
发表于 2014-5-13 07:14:58
本帖最后由 牛东 于 2014-5-13 07:22 编辑
鸿哥:34节后的每行代码前有数字(应该是显示行数的吧),34节以前没有,能不能去掉!我删的很辛苦啊!!{:loveliness:}
吴坚鸿
发表于 2014-5-13 11:25:31
牛东 发表于 2014-5-13 07:14
鸿哥:34节后的每行代码前有数字(应该是显示行数的吧),34节以前没有,能不能去掉!我删的很辛苦啊!!{:l ...
你点击最下面的“复制代码”,然后粘贴在keil编译软件的编辑文本里,千万不要直接粘贴在txt文本里。这样就不会存在这个问题了。
wjwjwjwj98
发表于 2014-5-13 12:29:58
不错收藏了,慢慢学
liuzp001
发表于 2014-5-13 15:13:11
鸿哥你好,请教一个问题:
for(n=7; n==255; n--) { do something;} ====结果错误
for(n=8; n>0;n--) { do something;} ====结果正确
为什么?
吴坚鸿
发表于 2014-5-13 16:27:46
liuzp001 发表于 2014-5-13 15:13
鸿哥你好,请教一个问题:
for(n=7; n==255; n--) { do something;} ====结果错误
你要翻一下C语言关于for语法的书。
for(n=7; n==255; n--) //如果 for里面的条件不满足,就不会执行括号里面的内容。7很明显不等于255,所以就不会执行括号的内容。
{ do something;}
绝对零度
发表于 2014-5-13 16:40:41
写的很好!关注中!!
xiaoxiao88
发表于 2014-5-13 17:52:27
支持一下!
ghhuang
发表于 2014-5-13 17:57:15
吴坚鸿 发表于 2014-3-10 11:27
第一节:吴坚鸿谈初学单片机的误区。
(1)很难记住繁杂的寄存器?寄存器不用死记硬背,我做了那么久单片 ...
说的非常好,赞同!!{:handshake:}
liuzp001
发表于 2014-5-13 19:28:33
吴坚鸿 发表于 2014-5-13 16:27
你要翻一下C语言关于for语法的书。
for(n=7; n==255; n--)//如果 for里面的条件不满足,就不会执行括号 ...
谢谢!
_Funnnn
发表于 2014-5-13 20:25:28
酷,,,好好学习
牛东
发表于 2014-5-13 20:30:09
吴坚鸿 发表于 2014-5-13 11:25
你点击最下面的“复制代码”,然后粘贴在keil编译软件的编辑文本里,千万不要直接粘贴在txt文本里。这样 ...
哦,如此!谢了!鸿哥!{:victory:}
wgm_123
发表于 2014-5-14 07:48:38
超牛逼的帖子,超牛逼的作者,能写书了,好文章,得要仔细看,慢慢欣赏。谢谢作者的无私奉献。
wgm_123
发表于 2014-5-14 08:03:14
吴坚鸿 发表于 2014-3-12 23:07
我一路走来,都是靠自己不断积累不断摸索的,积累到一定程度才形成现在的理论。我懂初学者缺什么,我知道 ...
好样的,敬佩你这种无私的奉献精神,你会有更大回报的,老天保佑你。
吴坚鸿
发表于 2014-5-14 11:25:20
wgm_123 发表于 2014-5-14 07:48
超牛逼的帖子,超牛逼的作者,能写书了,好文章,得要仔细看,慢慢欣赏。谢谢作者的无私奉献。 ...
感谢你的高度评价和赞扬。对我是一种鼓励。
ghhuang
发表于 2014-5-14 14:55:50
MARK,写的不错! 支持!
单枪舞九州
发表于 2014-5-14 16:20:09
你不是把电子发烧友的东西移过来了吧,加油哦,
单枪舞九州
发表于 2014-5-14 16:39:45
吴坚鸿 发表于 2014-3-10 12:32
谁说“一女不可以侍二夫”?
我到处混,没有许身于任何论坛网站。
我是很开放的,目的就是分 ...
消除成见,互相学习,共同进步,嘿嘿
吴坚鸿
发表于 2014-5-14 20:52:40
单枪舞九州 发表于 2014-5-14 16:20
你不是把电子发烧友的东西移过来了吧,加油哦,
不是把某个论坛的东西移过来,而是在几个论坛同步更新播放。我现在写的都是我最新构思的东西,绝对第一时间在阿莫论坛更新上传。
xxzzhy
发表于 2014-5-14 23:19:34
牛人{:lol:}!!!
bajie_zhl19
发表于 2014-5-15 09:25:21
谢谢好人~~好人一生平安~
牛东
发表于 2014-5-15 10:06:26
本帖最后由 牛东 于 2014-5-15 10:39 编辑
吴坚鸿 发表于 2014-4-6 12:19
第三十九节:判断数据头来接收一串数据的串口通用程序框架。
开场白:
鸿哥:你用的是那种串口调试助手?我用一种串口调试助手试了几下你的第38节,第39节串口通讯程序蜂鸣器不响啊!(在我的开发板试过自带串口程序是可以通讯的),有空帮我看看!另外这段代码TH1=TL1=-(11059200L/12/32/9600);中的L是啥意思啊还有一个负号?
/*-----------------------------------------------
名称:串口通信
编写:shifang
日期:2009.5
修改:无
内容:连接好串口或者usb转串口至电脑,下载该程序,打开电源
打开串口调试程序,将波特率设置为9600,无奇偶校验
晶振11.0592MHz,发送和接收使用的格式相同,如都使用
字符型格式,按复位重启程序,可以看到接收到 UART test,技术论坛:www.doflye.net 请在发送区输入任意信
然后在发送区发送任意信息,接收区返回同样信息,表明串口收发无误
------------------------------------------------*/
#include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
/*------------------------------------------------
函数声明
------------------------------------------------*/
void SendStr(unsigned char *s);
/*------------------------------------------------
串口初始化
------------------------------------------------*/
void InitUART(void)
{
SCON= 0x50; // SCON: 模式 1, 8-bit UART, 使能接收
TMOD |= 0x20; // TMOD: timer 1, mode 2, 8-bit 重装
TH1 = 0xFD; // TH1:重装值 9600 波特率 晶振 11.0592MHz
TR1 = 1; // TR1:timer 1 打开
EA = 1; //打开总中断
// ES = 1; //打开串口中断
}
/*------------------------------------------------
主函数
------------------------------------------------*/
void main (void)
{
InitUART();
SendStr("UART test,技术论坛:www.doflye.net 请在发送区输入任意信息");
ES = 1; //打开串口中断
while (1)
{
}
}
/*------------------------------------------------
发送一个字节
------------------------------------------------*/
void SendByte(unsigned char dat)
{
SBUF = dat;
while(!TI);
TI = 0;
}
/*------------------------------------------------
发送一个字符串
------------------------------------------------*/
void SendStr(unsigned char *s)
{
while(*s!='\0')// \0 表示字符串结束标志,通过检测是否字符串末尾
{
SendByte(*s);
s++;
}
}
/*------------------------------------------------
串口中断程序
------------------------------------------------*/
void UART_SER (void) interrupt 4 //串行中断服务程序
{
unsigned char Temp; //定义临时变量
if(RI) //判断是接收中断产生
{
RI=0; //标志位清零
Temp=SBUF; //读入缓冲区的值
P1=Temp; //把值输出到P1口,用于观察
SBUF=Temp; //把接收到的值再发回电脑端
}
if(TI) //如果是发送标志位,清零
TI=0;
}
qtds67
发表于 2014-5-15 10:47:32
鸿哥V5啊,顶起
吴坚鸿
发表于 2014-5-15 10:53:48
本帖最后由 吴坚鸿 于 2014-5-15 10:58 编辑
牛东 发表于 2014-5-15 10:06
鸿哥:你用的是那种串口调试助手?我用一种串口调试助手试了几下你的第38节,第39节串口通讯程序蜂鸣器不 ...
TH1=TL1=-(11059200L/12/32/9600);
L代表数据long类型吧。至于负号是什么意思,我也不懂,也没仔细深入去想,也没这个必要去想。
这个配置代码是我直接抄别人的配置代码,只要知道这个配置跟波特率有关的就行了。
如果是我去配置,我肯定不会用负号以及L这样的符号。我当时第一次看到别人这样写的时候,我也是觉得有点奇怪,但是只要它能用就行了,管它是什么意思。
38节,第39节的内容我自己全部亲自在朱兆祺51学习板上测试过的,没有问题。上位机发送数据的时候,记得必须用十六进制的数据类型发送,千万不能用字符型数据发送,否则肯定不成功。
kuanglf
发表于 2014-5-15 11:02:11
谢谢分享记号备用
吴坚鸿
发表于 2014-5-15 12:43:12
第四十七节:操作AT24C02时,利用“一气呵成的定时器延时”改善数码管的闪烁现象。
开场白:
上一节在按键更改参数时,会出现短暂明显的数码管闪烁现象。这节通过教大家使用新型延时函数可以有效的改善闪烁现象。要教会大家三个知识点:
第一个:如何编写一气呵成的定时器延时函数。
第二个:如何编写检查EEPROM芯片是否存在短路,虚焊或者芯片坏了的监控程序。
第三个:经过网友“cjseng”的提醒,我建议大家以后在用EEPROM芯片时,如果单片机IO口足够多,WP引脚应该专门接一个IO口,并且加一个上拉电阻,需要更改EEPROM存储数据时置低,其他任何一个时刻都置高,这样可以更加有效地保护EEPROM内部数据不会被意外更改。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。旧版的朱兆祺51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的朱兆祺51学习板已经改过来了。
(2)实现功能:
4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。如果AT24C02短路,虚焊,或者坏了,系统可以检查出来,并且蜂鸣器会间歇性鸣叫报警。按更改参数按键时,数码管比上一节大大降低了闪烁现象。
显示和独立按键部分根据第29节的程序来改编,用朱兆祺51单片机学习板中的S1,S5,S9作为独立按键。
一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。
(3)源代码讲解如下:
#include "REG52.H"
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间
#define const_eeprom_1s 400 //大概1秒的时间
void initial_myself(void);
void initial_peripheral(void);
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void delay_timer(unsigned int uiDelayTimerTemp); //一气呵成的定时器延时方式
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(void); //显示数码管字模的驱动函数
void display_service(void); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void start24(void);//开始位
void ack24(void);//确认位
void stop24(void);//停止位
unsigned char read24(void);//读取一个字节的时序
void write24(unsigned char dd); //发送一个字节的时序
unsigned char read_eeprom(unsigned int address); //从一个地址读取出一个字节数据
void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
unsigned int read_eeprom_int(unsigned int address); //从一个地址读取出一个int类型的数据
void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据
void T0_time(void);//定时中断函数
void key_service(void); //按键服务的应用程序
void key_scan(void);//按键扫描函数 放在定时中断里
void eeprom_alarm_service(void); //EEPROM出错报警
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit eeprom_scl_dr=P3^7; //时钟线
sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁
unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容
unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1;//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned intuiSetData1=0;//本程序中需要被设置的参数1
unsigned intuiSetData2=0;//本程序中需要被设置的参数2
unsigned intuiSetData3=0;//本程序中需要被设置的参数3
unsigned intuiSetData4=0;//本程序中需要被设置的参数4
unsigned char ucTemp1=0;//中间过渡变量
unsigned char ucTemp2=0;//中间过渡变量
unsigned char ucTemp3=0;//中间过渡变量
unsigned char ucTemp4=0;//中间过渡变量
unsigned char ucDelayTimerLock=0; //原子锁
unsigned intuiDelayTimer=0;
unsigned char ucCheckEeprom=0;//检查EEPROM芯片是否正常
unsigned char ucEepromError=0; //EEPROM芯片是否正常的标志
unsigned char ucEepromLock=0;//原子锁
unsigned intuiEepromCnt=0; //间歇性蜂鸣器报警的计时器
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//无 序号10
0x40,//- 序号11
0x73,//P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
display_service(); //显示的窗口菜单服务程序
eeprom_alarm_service(); //EEPROM出错报警
}
}
void eeprom_alarm_service(void) //EEPROM出错报警
{
if(ucEepromError==1) //EEPROM出错
{
if(uiEepromCnt<const_eeprom_1s)//大概1秒钟蜂鸣器响一次
{
ucEepromLock=1;//原子锁加锁
uiEepromCnt=0; //计时器清零
ucEepromLock=0;//原子锁解锁
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
}
}
}
//AT24C02驱动程序
void start24(void)//开始位
{
eeprom_sda_dr_sr=1;
eeprom_scl_dr=1;
delay_short(15);
eeprom_sda_dr_sr=0;
delay_short(15);
eeprom_scl_dr=0;
}
void ack24(void)//确认位时序
{
eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入
eeprom_scl_dr=1;
delay_short(15);
eeprom_scl_dr=0;
delay_short(15);
//在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
//有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
}
void stop24(void)//停止位
{
eeprom_sda_dr_sr=0;
eeprom_scl_dr=1;
delay_short(15);
eeprom_sda_dr_sr=1;
}
unsigned char read24(void)//读取一个字节的时序
{
unsigned char outdata,tempdata;
outdata=0;
eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
delay_short(2);
for(tempdata=0;tempdata<8;tempdata++)
{
eeprom_scl_dr=0;
delay_short(2);
eeprom_scl_dr=1;
delay_short(2);
outdata<<=1;
if(eeprom_sda_dr_sr==1)outdata++;
eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
delay_short(2);
}
return(outdata);
}
void write24(unsigned char dd) //发送一个字节的时序
{
unsigned char tempdata;
for(tempdata=0;tempdata<8;tempdata++)
{
if(dd>=0x80)eeprom_sda_dr_sr=1;
else eeprom_sda_dr_sr=0;
dd<<=1;
delay_short(2);
eeprom_scl_dr=1;
delay_short(4);
eeprom_scl_dr=0;
}
}
unsigned char read_eeprom(unsigned int address) //从一个地址读取出一个字节数据
{
unsigned char dd,cAddress;
cAddress=address; //把低字节地址传递给一个字节变量。
EA=0; //禁止中断
start24(); //IIC通讯开始
write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
//指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
ack24(); //发送应答信号
write24(cAddress); //发送读取的存储地址(范围是0至255)
ack24(); //发送应答信号
start24(); //开始
write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
//指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
ack24(); //发送应答信号
dd=read24(); //读取一个字节
ack24(); //发送应答信号
stop24();//停止
EA=1; //允许中断
delay_timer(2); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管
return(dd);
}
void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
{
unsigned char cAddress;
cAddress=address; //把低字节地址传递给一个字节变量。
EA=0; //禁止中断
start24(); //IIC通讯开始
write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
//指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
ack24(); //发送应答信号
write24(cAddress); //发送写入的存储地址(范围是0至255)
ack24(); //发送应答信号
write24(dd);//写入存储的数据
ack24(); //发送应答信号
stop24();//停止
EA=1; //允许中断
delay_timer(4); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管
}
unsigned int read_eeprom_int(unsigned int address) //从一个地址读取出一个int类型的数据
{
unsigned char ucReadDataH;
unsigned char ucReadDataL;
unsigned intuiReadDate;
ucReadDataH=read_eeprom(address); //读取高字节
ucReadDataL=read_eeprom(address+1);//读取低字节
uiReadDate=ucReadDataH;//把两个字节合并成一个int类型数据
uiReadDate=uiReadDate<<8;
uiReadDate=uiReadDate+ucReadDataL;
return uiReadDate;
}
void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
{
unsigned char ucWriteDataH;
unsigned char ucWriteDataL;
ucWriteDataH=uiWriteData>>8;
ucWriteDataL=uiWriteData;
write_eeprom(address,ucWriteDataH); //存入高字节
write_eeprom(address+1,ucWriteDataL); //存入低字节
}
void display_service(void) //显示的窗口菜单服务程序
{
switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示P--1窗口的数据
if(ucWd1Update==1)//窗口1要全部更新显示
{
ucWd1Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=1; //第6位数码管显示1
ucDigShow5=10;//第5位数码管显示无
//先分解数据
ucTemp4=uiSetData1/1000;
ucTemp3=uiSetData1%1000/100;
ucTemp2=uiSetData1%100/10;
ucTemp1=uiSetData1%10;
//再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
if(uiSetData1<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData1<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData1<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 2://显示P--2窗口的数据
if(ucWd2Update==1)//窗口2要全部更新显示
{
ucWd2Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=2;//第6位数码管显示2
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData2/1000; //分解数据
ucTemp3=uiSetData2%1000/100;
ucTemp2=uiSetData2%100/10;
ucTemp1=uiSetData2%10;
if(uiSetData2<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData2<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData2<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 3://显示P--3窗口的数据
if(ucWd3Update==1)//窗口3要全部更新显示
{
ucWd3Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=3;//第6位数码管显示3
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData3/1000; //分解数据
ucTemp3=uiSetData3%1000/100;
ucTemp2=uiSetData3%100/10;
ucTemp1=uiSetData3%10;
if(uiSetData3<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData3<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData3<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
case 4://显示P--4窗口的数据
if(ucWd4Update==1)//窗口4要全部更新显示
{
ucWd4Update=0;//及时清零标志,避免一直进来扫描
ucDigShow8=12;//第8位数码管显示P
ucDigShow7=11;//第7位数码管显示-
ucDigShow6=4;//第6位数码管显示4
ucDigShow5=10; //第5位数码管显示无
ucTemp4=uiSetData4/1000; //分解数据
ucTemp3=uiSetData4%1000/100;
ucTemp2=uiSetData4%100/10;
ucTemp1=uiSetData4%10;
if(uiSetData4<1000)
{
ucDigShow4=10;//如果小于1000,千位显示无
}
else
{
ucDigShow4=ucTemp4;//第4位数码管要显示的内容
}
if(uiSetData4<100)
{
ucDigShow3=10;//如果小于100,百位显示无
}
else
{
ucDigShow3=ucTemp3;//第3位数码管要显示的内容
}
if(uiSetData4<10)
{
ucDigShow2=10;//如果小于10,十位显示无
}
else
{
ucDigShow2=ucTemp2;//第2位数码管要显示的内容
}
ucDigShow1=ucTemp1;//第1位数码管要显示的内容
}
break;
}
}
void key_scan(void)//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1;//自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1;//自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1;//自锁按键置位,避免一直触发
ucKeySec=3; //触发3号键
}
}
}
void key_service(void) //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1++;
if(uiSetData1>9999) //最大值是9999
{
uiSetData1=9999;
}
write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁
ucWd1Update=1;//窗口1更新显示
break;
case 2:
uiSetData2++;
if(uiSetData2>9999) //最大值是9999
{
uiSetData2=9999;
}
write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd2Update=1;//窗口2更新显示
break;
case 3:
uiSetData3++;
if(uiSetData3>9999) //最大值是9999
{
uiSetData3=9999;
}
write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd3Update=1;//窗口3更新显示
break;
case 4:
uiSetData4++;
if(uiSetData4>9999) //最大值是9999
{
uiSetData4=9999;
}
write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
uiSetData1--;
if(uiSetData1>9999)
{
uiSetData1=0;//最小值是0
}
write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd1Update=1;//窗口1更新显示
break;
case 2:
uiSetData2--;
if(uiSetData2>9999)
{
uiSetData2=0;//最小值是0
}
write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd2Update=1;//窗口2更新显示
break;
case 3:
uiSetData3--;
if(uiSetData3>9999)
{
uiSetData3=0;//最小值是0
}
write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd3Update=1;//窗口3更新显示
break;
case 4:
uiSetData4--;
if(uiSetData4>9999)
{
uiSetData4=0;//最小值是0
}
write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
ucWd++;//切换窗口
if(ucWd>4)
{
ucWd=1;
}
switch(ucWd)//在不同的窗口下,在不同的窗口下,更新显示不同的窗口
{
case 1:
ucWd1Update=1;//窗口1更新显示
break;
case 2:
ucWd2Update=1;//窗口2更新显示
break;
case 3:
ucWd3Update=1;//窗口3更新显示
break;
case 4:
ucWd4Update=1;//窗口4更新显示
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
}
void display_drive(void)
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1://显示第1位
ucDigShowTemp=dig_table;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time(void) interrupt 1 //定时中断
{
TF0=0;//清除中断标志
TR0=0; //关中断
if(ucVoiceLock==0) //原子锁判断
{
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
}
if(ucDelayTimerLock==0) //原子锁判断
{
if(uiDelayTimer>0)
{
uiDelayTimer--; //一气呵成的定时器延时方式的计时器
}
}
if(ucEepromError==1) //EEPROM出错
{
if(ucEepromLock==0)//原子锁判断
{
uiEepromCnt++;//间歇性蜂鸣器报警的计时器
}
}
key_scan(); //按键扫描函数
display_drive();//数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++)//内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void delay_timer(unsigned int uiDelayTimerTemp)
{
ucDelayTimerLock=1; //原子锁加锁
uiDelayTimer=uiDelayTimerTemp;
ucDelayTimerLock=0; //原子锁解锁
/* 注释一:
*延时等待,一直等到定时中断把它减到0为止.这种一气呵成的定时器方式,
*可以在延时的时候动态扫描数码管,改善数码管的闪烁现象
*/
while(uiDelayTimer!=0);//一气呵成的定时器方式延时等待
}
void initial_myself(void)//第一区 初始化单片机
{
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral(void) //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
/* 注释二:
* 检查AT24C02芯片是否存在短路,虚焊,芯片坏了等不工作现象。
* 在一个特定的地址里把数据读出来,如果发现不等于0x5a,则重新写入0x5a,再读出来
* 判断是不是等于0x5a,如果不相等,则芯片有问题,出错报警提示。
*/
ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
if(ucCheckEeprom!=0x5a)//如果不等于特定内容。则重新写入数据再判断一次
{
write_eeprom(254,0x5a);//重新写入标志数据
ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
if(ucCheckEeprom!=0x5a)//如果还是不等于特定数字,则芯片不正常
{
ucEepromError=1;//表示AT24C02芯片出错报警
}
}
uiSetData1=read_eeprom_int(0);//读取uiSetData1,内部占用2个字节地址
if(uiSetData1>9999) //不在范围内
{
uiSetData1=0; //填入一个初始化数据
write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
}
uiSetData2=read_eeprom_int(2);//读取uiSetData2,内部占用2个字节地址
if(uiSetData2>9999)//不在范围内
{
uiSetData2=0;//填入一个初始化数据
write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
}
uiSetData3=read_eeprom_int(4);//读取uiSetData3,内部占用2个字节地址
if(uiSetData3>9999)//不在范围内
{
uiSetData3=0;//填入一个初始化数据
write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
}
uiSetData4=read_eeprom_int(6);//读取uiSetData4,内部占用2个字节地址
if(uiSetData4>9999)//不在范围内
{
uiSetData4=0;//填入一个初始化数据
write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
}
}
总结陈词:
下一节开始讲关于单片机驱动实时时钟芯片的内容,欲知详情,请听下回分解-----利用DS1302做一个实时时钟。
(未完待续,下节更精彩,不要走开哦)
mqlltxdl
发表于 2014-5-15 16:17:37
谢谢楼主无私分享,受益匪浅。
请问楼主可否上传一个MODBUS协议的项目例程,最近被他搞得头大。
吴坚鸿
发表于 2014-5-15 20:12:59
mqlltxdl 发表于 2014-5-15 16:17
谢谢楼主无私分享,受益匪浅。
请问楼主可否上传一个MODBUS协议的项目例程,最近被他搞得头大。 ...
我没搞过MODBUS协议的项目。
gtcggtcg
发表于 2014-5-15 21:29:34
鸿哥,厉害啊,佩服佩服。分析的透彻,讲解的详细。我向来是只看帖不回复的,看来鸿哥的贴,忍不住了!
吴坚鸿
发表于 2014-5-16 10:58:40
gtcggtcg 发表于 2014-5-15 21:29
鸿哥,厉害啊,佩服佩服。分析的透彻,讲解的详细。我向来是只看帖不回复的,看来鸿哥的贴,忍不住了! ...
呵呵,我的真有那么好?{:shy:}
xrydt
发表于 2014-5-16 11:05:31
感谢鸿哥无私的奉献。
牛东
发表于 2014-5-16 11:26:35
吴坚鸿 发表于 2014-5-15 10:53
TH1=TL1=-(11059200L/12/32/9600);
L代表数据long类型吧。至于负号是什么意思,我也不懂,也没仔细深入去 ...
谢了,原来是串口助手的问题,程序没问题,换一个就OK了!!!
一匹狼
发表于 2014-5-16 16:46:21
楼主要是能整理成pdf就更好咯
heyong9
发表于 2014-5-16 17:37:37
标记一下
cjp88811283
发表于 2014-5-17 14:04:12
楼主辛苦了,谢谢分享!
hnzlf
发表于 2014-5-17 21:52:02
谢谢楼主分享!
熬松螺丝
发表于 2014-5-18 03:23:04
写得正对要点,得赞一下才行
牛东
发表于 2014-5-18 15:14:57
mcu_lover 发表于 2014-4-6 09:00
被中断了没有关系啊,中断优先级本来就高于主循环。
ISR里面:
我是初学者,请问能说说原子锁的原理吗?
xiaoyan911
发表于 2014-5-18 21:44:25
想成为鸿哥一样的人,哈哈。鸿哥让我想起了郭天祥,广大初学者的福音。早点入门,早点享受编程带来的快乐才是最重要的。大赞!
chp019479
发表于 2014-5-18 22:13:49
感谢分享,mark
dtdzlujian
发表于 2014-5-19 08:05:30
期待吴总的佳作!支持
zhongjp85
发表于 2014-5-19 08:23:04
mark yixia
18814888577
发表于 2014-5-19 12:10:15
本帖最后由 18814888577 于 2014-5-19 16:17 编辑
最近看了楼主写的串口通信方面的程序,感觉增长了很多。非常感谢!
yxm433
发表于 2014-5-19 12:24:44
支持,学习了,谢谢楼主
77588858
发表于 2014-5-19 15:21:14
今天学了前几节简单易懂 重新燃起学习51 的渴望把落灰已久的开发板拿出来 重新搞起 鸿哥跟定你了
吴坚鸿
发表于 2014-5-19 18:02:15
牛东 发表于 2014-5-18 15:14
我是初学者,请问能说说原子锁的原理吗?
原子锁的本质,就是通过一个原子锁变量的设置和判断,避免某个重要变量在还没完全更改完,或者某个重要过程还没完全执行完的时候,在其他的中断函数里也被同时更改或者同时执行。因为某些重要变量如果在主函数里还没更改完,接着马上被中断函数也更改,就很容易出现严重的事故。
xxzzhy
发表于 2014-5-20 20:36:58
xinwu 发表于 2014-5-9 18:15
吴坚鸿手把手教你单片机程序框架(1-45)整理
老兄最近怎么不整理了吴坚鸿的程序了啊。
xinwu
发表于 2014-5-20 21:30:42
xxzzhy 发表于 2014-5-20 20:36
老兄最近怎么不整理了吴坚鸿的程序了啊。
前段时间打印的还没看完,况且鸿哥最近才更新了两节,过段时间在这整理一下。
xxzzhy
发表于 2014-5-20 22:08:04
xinwu 发表于 2014-5-20 21:30
前段时间打印的还没看完,况且鸿哥最近才更新了两节,过段时间在这整理一下。 ...
呵呵,你看到那节了。楼主的资料确实不错
吴坚鸿
发表于 2014-5-21 11:03:09
第四十八节:利用DS1302做一个实时时钟。
开场白:
DS1302有两路独立电源输入,我们只要在其中一路电源上挂一个纽扣电池就可以实现掉电时钟继续跑的功能,纽扣电池作为备用电源必须比主电源的电压低一点。DS1302还给我们预留了一片RAM区,我们可以把一些数据存入到DS1302,只要DS1302的电池有电,那么它就相当于一个EEPROM。这个RAM区有什么用呢?因为RAM区的数据只要一掉电,所有的数据都会变成0x00或者0xff,也就是数据掉电会丢失,我们可以利用这个特点,可以在里面存入标志位数据,一旦发现这个数据改变了,就知道时钟的数据需要重新设置过,或者说明电池没电了。
在移植DS1302驱动程序中,有一个地方最容易出错,就是DS1302芯片的数据线DIO。我们编程时要特别留意这个IO口什么时候作为数据输入,什么时候作为数据输出,以便及时更改方向寄存器。对于51单片机,IO口在读取数据之前,要先置1。
这一节要教会大家六个知识点:
第一个:DS1302做实时时钟时,修改时间和读取时间的常见程序框架。
第二个:如何编写监控备用电池电量耗尽的监控程序。
第三个:在往DS1302写入数据修改时间之前,必须注意调整一下“日”的变量,因为每个月的最大天数是不一样的,有的一个月28天,有的一个月29天,有的一个月30天,有的一个月31天。
第四个:本程序第一次出现了电平按键,跟之前讲的下降沿按键不一样,请留意我是何如用软件滤波的,以及它具体的实现代码。
第五个:本程序第一次出现了一个按键按下去后,如果不松手就会触发两次事件,第一次是短按,第二次是长按3秒。请留意我是如何在之前的按键上略做修改就实现此功能的具体代码。
第六个:继续加深了解按键与显示是如何紧密关联起来的程序框架。
具体内容,请看源代码讲解。
(1) 硬件平台.
基于朱兆祺51单片机学习板。
旧版的朱兆祺51学习板在硬件上有一个bug,DS1302芯片附近的R43,R42两个电阻应该去掉,并且把R41的电阻换成0欧姆的电阻,或者直接短接起来。新版的朱兆祺51学习板已经改过来了。
(2)实现功能:
本程序有2两个窗口。
第1个窗口显示日期。显示格式“年-月-日”。注意中间有“-”分开。
第2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性写入DS1302芯片内部,达到修改日期时间的目的。
S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。
本程序在使用过程中的注意事项:
(a)第一次上电时,蜂鸣器会报警,只要DS1302芯片的备用电池电量充足,这个时候断电再重启一次,就不会报警了。
(b)第一次上电时,时间没有走动,需要重新设置一下日期时间才可以。长按S9按键可以进入修改日期时间的状态。
(3)源代码讲解如下:
#include "REG52.H"
#define const_dpy_time_half200//数码管闪烁时间的半值
#define const_dpy_time_all 400//数码管闪烁时间的全值 一定要比const_dpy_time_half 大
#define const_voice_short40 //蜂鸣器短叫的持续时间
#define const_key_time120 //按键去抖动延时的时间
#define const_key_time220 //按键去抖动延时的时间
#define const_key_time320 //按键去抖动延时的时间
#define const_key_time420 //按键去抖动延时的时间
#define const_key_time171200//长按超过3秒的时间
#define const_ds1302_0_5s200 //大概0.5秒的时间
#define const_ds1302_sampling_time 360 //累计主循环次数的时间,每次刷新采样时钟芯片的时间
#define WRITE_SECOND 0x80 //DS1302内部的相关地址
#define WRITE_MINUTE 0x82
#define WRITE_HOUR 0x84
#define WRITE_DATE 0x86
#define WRITE_MONTH 0x88
#define WRITE_YEAR 0x8C
#define WRITE_CHECK 0xC2//用来检查芯片的备用电池是否用完了的地址
#define READ_CHECK 0xC3//用来检查芯片的备用电池是否用完了的地址
#define READ_SECOND 0x81
#define READ_MINUTE 0x83
#define READ_HOUR 0x85
#define READ_DATE 0x87
#define READ_MONTH 0x89
#define READ_YEAR 0x8D
#define WRITE_PROTECT 0x8E
void initial_myself(void);
void initial_peripheral(void);
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
//驱动数码管的74HC595
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
void display_drive(void); //显示数码管字模的驱动函数
void display_service(void); //显示的窗口菜单服务程序
//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time(void);//定时中断函数
void key_service(void); //按键服务的应用程序
void key_scan(void);//按键扫描函数 放在定时中断里
void ds1302_alarm_service(void); //ds1302出错报警
void ds1302_sampling(void); //ds1302采样程序,内部每秒钟采集更新一次
void Write1302 ( unsigned char addr, unsigned char dat );//修改时间的驱动
unsigned char Read1302 ( unsigned char addr );//读取时间的驱动
unsigned char bcd_to_number(unsigned char ucBcdTemp);//BCD转原始数值
unsigned char number_to_bcd(unsigned char ucNumberTemp); //原始数值转BCD
//日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp); //日调整
sbit SCLK_dr =P1^3;
sbit DIO_dr_sr =P1^4;
sbit DS1302_CE_dr =P1^5;
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit eeprom_scl_dr=P3^7; //时钟线
sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线
sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;
sbit dig_hc595_ds_dr=P2^2;
sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;
sbit hc595_ds_dr=P2^5;
unsigned int uiSampingCnt=0; //采集Ds1302的计时器,每秒钟更新采集一次
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int uiKey4Cnt1=0;//在软件滤波中,用到的变量
unsigned int uiKey4Cnt2=0;
unsigned char ucKey4Sr=1;//实时反映按键的电平状态
unsigned char ucKey4SrRecord=0; //记录上一次按键的电平状态
unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁
unsigned char ucDigShow8;//第8位数码管要显示的内容
unsigned char ucDigShow7;//第7位数码管要显示的内容
unsigned char ucDigShow6;//第6位数码管要显示的内容
unsigned char ucDigShow5;//第5位数码管要显示的内容
unsigned char ucDigShow4;//第4位数码管要显示的内容
unsigned char ucDigShow3;//第3位数码管要显示的内容
unsigned char ucDigShow2;//第2位数码管要显示的内容
unsigned char ucDigShow1;//第1位数码管要显示的内容
unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
unsigned char ucDigShowTemp=0; //临时中间变量
unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量
unsigned char ucWd=2;//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。
unsigned char ucWd1Update=0; //窗口1更新显示标志
unsigned char ucWd2Update=1; //窗口2更新显示标志
unsigned char ucWd1Part1Update=0;//在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志
unsigned char ucWd2Part1Update=0;//在窗口2中,局部1的更新显示标志
unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志
unsigned char ucWd2Part3Update=0; //在窗口2中,局部3的更新显示标志
unsigned charucYear=0; //原始数据
unsigned charucMonth=0;
unsigned charucDate=0;
unsigned charucHour=0;
unsigned charucMinute=0;
unsigned charucSecond=0;
unsigned charucYearBCD=0; //BCD码的数据
unsigned charucMonthBCD=0;
unsigned charucDateBCD=0;
unsigned charucHourBCD=0;
unsigned charucMinuteBCD=0;
unsigned charucSecondBCD=0;
unsigned char ucTemp1=0;//中间过渡变量
unsigned char ucTemp2=0;//中间过渡变量
unsigned char ucTemp4=0;//中间过渡变量
unsigned char ucTemp5=0;//中间过渡变量
unsigned char ucTemp7=0;//中间过渡变量
unsigned char ucTemp8=0;//中间过渡变量
unsigned char ucDelayTimerLock=0; //原子锁
unsigned intuiDelayTimer=0;
unsigned char ucCheckDs1302=0;//检查Ds1302芯片是否正常
unsigned char ucDs1302Error=0; //Ds1302芯片的备用电池是否用完了的报警标志
unsigned char ucDs1302Lock=0;//原子锁
unsigned intuiDs1302Cnt=0; //间歇性蜂鸣器报警的计时器
unsigned char ucDpyTimeLock=0; //原子锁
unsigned intuiDpyTimeCnt=0;//数码管的闪烁计时器,放在定时中断里不断累加
//根据原理图得出的共阴数码管字模表
code unsigned char dig_table[]=
{
0x3f,//0 序号0
0x06,//1 序号1
0x5b,//2 序号2
0x4f,//3 序号3
0x66,//4 序号4
0x6d,//5 序号5
0x7d,//6 序号6
0x07,//7 序号7
0x7f,//8 序号8
0x6f,//9 序号9
0x00,//无 序号10
0x40,//- 序号11
0x73,//P 序号12
};
void main()
{
initial_myself();
delay_long(100);
initial_peripheral();
while(1)
{
key_service(); //按键服务的应用程序
ds1302_sampling(); //ds1302采样程序,内部每秒钟采集更新一次
display_service(); //显示的窗口菜单服务程序
ds1302_alarm_service(); //ds1302出错报警
}
}
/* 注释一:
* 系统不用时时刻刻采集ds1302的内部数据,每隔一段时间更新采集一次就可以了。
* 这个间隔时间应该小于或者等于1秒钟的时间,否则在数码管上看不到每秒钟的时间变化。
*/
void ds1302_sampling(void) //ds1302采样程序,内部每秒钟采集更新一次
{
if(ucPart==0)//当系统不是处于设置日期和时间的情况下
{
++uiSampingCnt;//累计主循环次数的时间
if(uiSampingCnt>const_ds1302_sampling_time)//每隔一段时间就更新采集一次Ds1302数据
{
uiSampingCnt=0;
ucYearBCD=Read1302(READ_YEAR); //读取年
ucMonthBCD=Read1302(READ_MONTH); //读取月
ucDateBCD=Read1302(READ_DATE); //读取日
ucHourBCD=Read1302(READ_HOUR); //读取时
ucMinuteBCD=Read1302(READ_MINUTE); //读取分
ucSecondBCD=Read1302(READ_SECOND); //读取秒
ucYear=bcd_to_number(ucYearBCD);//BCD转原始数值
ucMonth=bcd_to_number(ucMonthBCD);//BCD转原始数值
ucDate=bcd_to_number(ucDateBCD);//BCD转原始数值
ucHour=bcd_to_number(ucHourBCD);//BCD转原始数值
ucMinute=bcd_to_number(ucMinuteBCD);//BCD转原始数值
ucSecond=bcd_to_number(ucSecondBCD);//BCD转原始数值
ucWd2Update=1; //窗口2更新显示时间
}
}
}
//修改ds1302时间的驱动 ,注意,此处写入的是BCD码,
void Write1302 ( unsigned char addr, unsigned char dat )
{
unsigned char i,temp; //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
DS1302_CE_dr=0; //CE引脚为低,数据传送中止
delay_short(1);
SCLK_dr=0; //清零时钟总线
delay_short(1);
DS1302_CE_dr = 1; //CE引脚为高,逻辑控制有效
delay_short(1);
//发送地址
for ( i=0; i<8; i++ ) //循环8次移位
{
DIO_dr_sr = 0;
temp = addr;
if(temp&0x01)
{
DIO_dr_sr =1;
}
else
{
DIO_dr_sr =0;
}
delay_short(1);
addr >>= 1; //右移一位
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
//发送数据
for ( i=0; i<8; i++ ) //循环8次移位
{
DIO_dr_sr = 0;
temp = dat;
if(temp&0x01)
{
DIO_dr_sr =1;
}
else
{
DIO_dr_sr =0;
}
delay_short(1);
dat >>= 1; //右移一位
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
DS1302_CE_dr = 0;
delay_short(1);
}
//读取Ds1302时间的驱动 ,注意,此处读取的是BCD码,
unsigned char Read1302 ( unsigned char addr )
{
unsigned char i,temp,dat1;
DS1302_CE_dr=0; //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
delay_short(1);
SCLK_dr=0;
delay_short(1);
DS1302_CE_dr = 1;
delay_short(1);
//发送地址
for ( i=0; i<8; i++ ) //循环8次移位
{
DIO_dr_sr = 0;
temp = addr;
if(temp&0x01)
{
DIO_dr_sr =1;
}
else
{
DIO_dr_sr =0;
}
delay_short(1);
addr >>= 1; //右移一位
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
/* 注释二:
* 51单片机IO口的特点,在读取数据之前必须先输出高电平,
* 如果是PIC,AVR等单片机,这里应该把IO方向寄存器设置为输入
*/
DIO_dr_sr =1; //51单片机IO口的特点,在读取数据之前必须先输出高电平,
temp=0;
for ( i=0; i<8; i++ )
{
temp>>=1;
if(DIO_dr_sr==1)
{
temp=temp+0x80;
}
DIO_dr_sr =1;//51单片机IO口的特点,在读取数据之前必须先输出高电平
delay_short(1);
SCLK_dr = 1;
delay_short(1);
SCLK_dr = 0;
delay_short(1);
}
DS1302_CE_dr=0;
delay_short(1);
dat1=temp;
return (dat1);
}
unsigned char bcd_to_number(unsigned char ucBcdTemp)//BCD转原始数值
{
unsigned char ucNumberResult=0;
unsigned char ucBcdTemp10;
unsigned char ucBcdTemp1;
ucBcdTemp10=ucBcdTemp;
ucBcdTemp10=ucBcdTemp10>>4;
ucBcdTemp1=ucBcdTemp;
ucBcdTemp1=ucBcdTemp1&0x0f;
ucNumberResult=ucBcdTemp10*10+ucBcdTemp1;
return ucNumberResult;
}
unsigned char number_to_bcd(unsigned char ucNumberTemp) //原始数值转BCD
{
unsigned char ucBcdResult=0;
unsigned char ucNumberTemp10;
unsigned char ucNumberTemp1;
ucNumberTemp10=ucNumberTemp;
ucNumberTemp10=ucNumberTemp10/10;
ucNumberTemp10=ucNumberTemp10<<4;
ucNumberTemp10=ucNumberTemp10&0xf0;
ucNumberTemp1=ucNumberTemp;
ucNumberTemp1=ucNumberTemp1%10;
ucBcdResult=ucNumberTemp10|ucNumberTemp1;
return ucBcdResult;
}
//日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp) //日调整
{
unsigned char ucDayResult;
unsigned int uiYearTemp;
unsigned int uiYearYu;
ucDayResult=ucDateTemp;
switch(ucMonthTemp)//根据不同的月份来修正不同的日最大值
{
case 2://二月份要计算是否是闰年
uiYearTemp=2000+ucYearTemp;
uiYearYu=uiYearTemp%4;
if(uiYearYu==0) //闰年
{
if(ucDayResult>29)
{
ucDayResult=29;
}
}
else
{
if(ucDayResult>28)
{
ucDayResult=28;
}
}
break;
case 4:
case 6:
case 9:
case 11:
if(ucDayResult>30)
{
ucDayResult=30;
}
break;
}
return ucDayResult;
}
void ds1302_alarm_service(void) //ds1302出错报警
{
if(ucDs1302Error==1)//备用电池的电量用完了报警提示
{
if(uiDs1302Cnt>const_ds1302_0_5s)//大概0.5秒钟蜂鸣器响一次
{
ucDs1302Lock=1;//原子锁加锁
uiDs1302Cnt=0; //计时器清零
ucDs1302Lock=0;//原子锁解锁
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
}
}
}
void display_service(void) //显示的窗口菜单服务程序
{
switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
{
case 1: //显示日期窗口的数据数据格式 NN-YY-RR 年-月-日
if(ucWd1Update==1)//窗口1要全部更新显示
{
ucWd1Update=0;//及时清零标志,避免一直进来扫描
ucDigShow6=11;//显示一杠"-"
ucDigShow3=11;//显示一杠"-"
ucWd1Part1Update=1;//局部年更新显示
ucWd1Part2Update=1;//局部月更新显示
ucWd1Part3Update=1;//局部日更新显示
}
if(ucWd1Part1Update==1)//局部年更新显示
{
ucWd1Part1Update=0;
ucTemp8=ucYear/10;//年
ucTemp7=ucYear%10;
ucDigShow8=ucTemp8; //数码管显示实际内容
ucDigShow7=ucTemp7;
}
if(ucWd1Part2Update==1)//局部月更新显示
{
ucWd1Part2Update=0;
ucTemp5=ucMonth/10;//月
ucTemp4=ucMonth%10;
ucDigShow5=ucTemp5; //数码管显示实际内容
ucDigShow4=ucTemp4;
}
if(ucWd1Part3Update==1) //局部日更新显示
{
ucWd1Part3Update=0;
ucTemp2=ucDate/10;//日
ucTemp1=ucDate%10;
ucDigShow2=ucTemp2; //数码管显示实际内容
ucDigShow1=ucTemp1;
}
//数码管闪烁
switch(ucPart)//相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
{
case 0://都不闪烁
break;
case 1://年参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
ucDigShow8=ucTemp8; //数码管显示实际内容
ucDigShow7=ucTemp7;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock=1; //原子锁加锁
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDpyTimeLock=0;//原子锁解锁
ucDigShow8=10; //数码管显示空,什么都不显示
ucDigShow7=10;
}
break;
case 2: //月参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
ucDigShow5=ucTemp5; //数码管显示实际内容
ucDigShow4=ucTemp4;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock=1; //原子锁加锁
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDpyTimeLock=0;//原子锁解锁
ucDigShow5=10; //数码管显示空,什么都不显示
ucDigShow4=10;
}
break;
case 3: //日参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
ucDigShow2=ucTemp2; //数码管显示实际内容
ucDigShow1=ucTemp1;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock=1; //原子锁加锁
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDpyTimeLock=0;//原子锁解锁
ucDigShow2=10; //数码管显示空,什么都不显示
ucDigShow1=10;
}
break;
}
break;
case 2: //显示时间窗口的数据数据格式 SS FF MM 时 分 秒
if(ucWd2Update==1)//窗口2要全部更新显示
{
ucWd2Update=0;//及时清零标志,避免一直进来扫描
ucDigShow6=10;//显示空
ucDigShow3=10;//显示空
ucWd2Part3Update=1;//局部时更新显示
ucWd2Part2Update=1;//局部分更新显示
ucWd2Part1Update=1;//局部秒更新显示
}
if(ucWd2Part1Update==1)//局部时更新显示
{
ucWd2Part1Update=0;
ucTemp8=ucHour/10;//时
ucTemp7=ucHour%10;
ucDigShow8=ucTemp8; //数码管显示实际内容
ucDigShow7=ucTemp7;
}
if(ucWd2Part2Update==1)//局部分更新显示
{
ucWd2Part2Update=0;
ucTemp5=ucMinute/10;//分
ucTemp4=ucMinute%10;
ucDigShow5=ucTemp5; //数码管显示实际内容
ucDigShow4=ucTemp4;
}
if(ucWd2Part3Update==1) //局部秒更新显示
{
ucWd2Part3Update=0;
ucTemp2=ucSecond/10;//秒
ucTemp1=ucSecond%10;
ucDigShow2=ucTemp2; //数码管显示实际内容
ucDigShow1=ucTemp1;
}
//数码管闪烁
switch(ucPart)//相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
{
case 0://都不闪烁
break;
case 1://时参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
ucDigShow8=ucTemp8; //数码管显示实际内容
ucDigShow7=ucTemp7;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock=1; //原子锁加锁
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDpyTimeLock=0;//原子锁解锁
ucDigShow8=10; //数码管显示空,什么都不显示
ucDigShow7=10;
}
break;
case 2: //分参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
ucDigShow5=ucTemp5; //数码管显示实际内容
ucDigShow4=ucTemp4;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock=1; //原子锁加锁
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDpyTimeLock=0;//原子锁解锁
ucDigShow5=10; //数码管显示空,什么都不显示
ucDigShow4=10;
}
break;
case 3: //秒参数闪烁
if(uiDpyTimeCnt==const_dpy_time_half)
{
ucDigShow2=ucTemp2; //数码管显示实际内容
ucDigShow1=ucTemp1;
}
else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
{
ucDpyTimeLock=1; //原子锁加锁
uiDpyTimeCnt=0; //及时把闪烁记时器清零
ucDpyTimeLock=0;//原子锁解锁
ucDigShow2=10; //数码管显示空,什么都不显示
ucDigShow1=10;
}
break;
}
break;
}
}
void key_scan(void)//按键扫描函数 放在定时中断里
{
if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock1=0; //按键自锁标志清零
uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt1++; //累加定时中断次数
if(uiKeyTimeCnt1>const_key_time1)
{
uiKeyTimeCnt1=0;
ucKeyLock1=1;//自锁按键置位,避免一直触发
ucKeySec=1; //触发1号键
}
}
if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt2++; //累加定时中断次数
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0;
ucKeyLock2=1;//自锁按键置位,避免一直触发
ucKeySec=2; //触发2号键
}
}
/* 注释三:
* 注意,此处把一个按键的短按和长按的功能都实现了。
*/
if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock3=0; //按键自锁标志清零
uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3>const_key_time3)
{
uiKeyTimeCnt3=0;
ucKeyLock3=1;//自锁按键置位,避免一直触发
ucKeySec=3; //短按触发3号键
}
}
else if(uiKeyTimeCnt3<const_key_time17) //长按3秒
{
uiKeyTimeCnt3++; //累加定时中断次数
if(uiKeyTimeCnt3==const_key_time17)//等于3秒钟,触发17号长按按键
{
ucKeySec=17; //长按3秒触发17号键
}
}
/* 注释四:
* 注意,此处是电平按键的滤波抗干扰处理
*/
if(key_sr4==1)//对应朱兆祺学习板的S13键
{
uiKey4Cnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiKey4Cnt2++; //类似独立按键去抖动的软件抗干扰处理
if(uiKey4Cnt2>const_key_time4)
{
uiKey4Cnt2=0;
ucKey4Sr=1;//实时反映按键松手时的电平状态
}
}
else
{
uiKey4Cnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
uiKey4Cnt1++;
if(uiKey4Cnt1>const_key_time4)
{
uiKey4Cnt1=0;
ucKey4Sr=0;//实时反映按键按下时的电平状态
}
}
}
void key_service(void) //按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1:// 加按键 对应朱兆祺学习板的S1键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1://年
ucYear++;
if(ucYear>99)
{
ucYear=99;
}
ucWd1Part1Update=1;//更新显示
break;
case 2: //月
ucMonth++;
if(ucMonth>12)
{
ucMonth=12;
}
ucWd1Part2Update=1;//更新显示
break;
case 3: //日
ucDate++;
if(ucDate>31)
{
ucDate=31;
}
ucWd1Part3Update=1;//更新显示
break;
}
break;
case 2:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1://时
ucHour++;
if(ucHour>23)
{
ucHour=23;
}
ucWd2Part1Update=1;//更新显示
break;
case 2: //分
ucMinute++;
if(ucMinute>59)
{
ucMinute=59;
}
ucWd2Part2Update=1;//更新显示
break;
case 3: //秒
ucSecond++;
if(ucSecond>59)
{
ucSecond=59;
}
ucWd2Part3Update=1;//更新显示
break;
}
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2:// 减按键 对应朱兆祺学习板的S5键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1://年
ucYear--;
if(ucYear>99)
{
ucYear=0;
}
ucWd1Part1Update=1;//更新显示
break;
case 2: //月
ucMonth--;
if(ucMonth<1)
{
ucMonth=1;
}
ucWd1Part2Update=1;//更新显示
break;
case 3: //日
ucDate--;
if(ucDate<1)
{
ucDate=1;
}
ucWd1Part3Update=1;//更新显示
break;
}
break;
case 2:
switch(ucPart) //在不同的局部变量下,相当于二级菜单
{
case 1://时
ucHour--;
if(ucHour>23)
{
ucHour=0;
}
ucWd2Part1Update=1;//更新显示
break;
case 2: //分
ucMinute--;
if(ucMinute>59)
{
ucMinute=0;
}
ucWd2Part2Update=1;//更新显示
break;
case 3: //秒
ucSecond--;
if(ucSecond>59)
{
ucSecond=0;
}
ucWd2Part3Update=1;//更新显示
break;
}
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 3://短按设置按键 对应朱兆祺学习板的S9键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 1:
ucPart++;
if(ucPart>3)
{
ucPart=1;
ucWd=2; //切换到第二个窗口,设置时分秒
ucWd2Update=1;//窗口2更新显示
}
ucWd1Update=1;//窗口1更新显示
break;
case 2:
if(ucPart>0) //在窗口2的时候,要第一次激活设置时间,必须是长按3秒才可以,这里短按激活不了第一次
{
ucPart++;
if(ucPart>3)//设置时间结束
{
ucPart=0;
/* 注释五:
* 每个月份的天数最大值是不一样的,在写入ds1302时钟芯片内部数据前,应该做一次调整。
* 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
*/
ucDate=date_adjust(ucYear,ucMonth,ucDate); //日调整 避免日的数值在某个月份超范围
ucYearBCD=number_to_bcd(ucYear);//原始数值转BCD
ucMonthBCD=number_to_bcd(ucMonth); //原始数值转BCD
ucDateBCD=number_to_bcd(ucDate);//原始数值转BCD
ucHourBCD=number_to_bcd(ucHour);//原始数值转BCD
ucMinuteBCD=number_to_bcd(ucMinute);//原始数值转BCD
ucSecondBCD=number_to_bcd(ucSecond);//原始数值转BCD
Write1302 (WRITE_PROTECT,0X00); //禁止写保护
Write1302 (WRITE_YEAR,ucYearBCD); //年修改
Write1302 (WRITE_MONTH,ucMonthBCD); //月修改
Write1302 (WRITE_DATE,ucDateBCD); //日修改
Write1302 (WRITE_HOUR,ucHourBCD); //小时修改
Write1302 (WRITE_MINUTE,ucMinuteBCD); //分钟修改
Write1302 (WRITE_SECOND,ucSecondBCD); //秒位修改
Write1302 (WRITE_PROTECT,0x80); //允许写保护
}
ucWd2Update=1;//窗口2更新显示
}
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 17://长按3秒设置按键 对应朱兆祺学习板的S9键
switch(ucWd)//在不同的窗口下,设置不同的参数
{
case 2:
if(ucPart==0) //处于非设置时间的状态下,要第一次激活设置时间,必须是长按3秒才可以
{
ucWd=1;
ucPart=1;//进入到设置日期的状态下
ucWd1Update=1;//窗口1更新显示
}
break;
}
ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
break;
}
/* 注释六:
* 注意,此处就是第一次出现的电平按键程序,跟以往的下降沿按键不一样。
* ucKey4Sr是经过软件滤波处理后,直接反应IO口电平状态的变量.当电平发生
* 变化时,就会切换到不同的显示界面,这里多用了一个ucKey4SrRecord变量
* 记录上一次的电平状态,是为了避免一直刷新显示。
*/
if(ucKey4Sr!=ucKey4SrRecord)//说明S13的切换按键电平状态发生变化
{
ucKey4SrRecord=ucKey4Sr;//及时记录当前最新的按键电平状态避免一直进来触发
if(ucKey4Sr==1) //松手后切换到显示时间的窗口
{
ucWd=2; //显示时分秒的窗口
ucPart=0;//进入到非设置时间的状态下
ucWd2Update=1;//窗口2更新显示
}
else//按下去切换到显示日期的窗口
{
ucWd=1; //显示年月日的窗口
ucPart=0;//进入到非设置时间的状态下
ucWd1Update=1;//窗口1更新显示
}
}
}
void display_drive(void)
{
//以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
switch(ucDisplayDriveStep)
{
case 1://显示第1位
ucDigShowTemp=dig_table;
if(ucDigDot1==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfe);
break;
case 2://显示第2位
ucDigShowTemp=dig_table;
if(ucDigDot2==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfd);
break;
case 3://显示第3位
ucDigShowTemp=dig_table;
if(ucDigDot3==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xfb);
break;
case 4://显示第4位
ucDigShowTemp=dig_table;
if(ucDigDot4==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xf7);
break;
case 5://显示第5位
ucDigShowTemp=dig_table;
if(ucDigDot5==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xef);
break;
case 6://显示第6位
ucDigShowTemp=dig_table;
if(ucDigDot6==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xdf);
break;
case 7://显示第7位
ucDigShowTemp=dig_table;
if(ucDigDot7==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0xbf);
break;
case 8://显示第8位
ucDigShowTemp=dig_table;
if(ucDigDot8==1)
{
ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
}
dig_hc595_drive(ucDigShowTemp,0x7f);
break;
}
ucDisplayDriveStep++;
if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
{
ucDisplayDriveStep=1;
}
}
//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
dig_hc595_sh_dr=0;
dig_hc595_st_dr=0;
ucTempData=ucDigStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucDigStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)dig_hc595_ds_dr=1;
else dig_hc595_ds_dr=0;
dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
dig_hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
dig_hc595_st_dr=1;
delay_short(1);
dig_hc595_sh_dr=0; //拉低,抗干扰就增强
dig_hc595_st_dr=0;
dig_hc595_ds_dr=0;
}
//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
unsigned char i;
unsigned char ucTempData;
hc595_sh_dr=0;
hc595_st_dr=0;
ucTempData=ucLedStatusTemp16_09;//先送高8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
ucTempData=ucLedStatusTemp08_01;//再先送低8位
for(i=0;i<8;i++)
{
if(ucTempData>=0x80)hc595_ds_dr=1;
else hc595_ds_dr=0;
hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
delay_short(1);
hc595_sh_dr=1;
delay_short(1);
ucTempData=ucTempData<<1;
}
hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
delay_short(1);
hc595_st_dr=1;
delay_short(1);
hc595_sh_dr=0; //拉低,抗干扰就增强
hc595_st_dr=0;
hc595_ds_dr=0;
}
void T0_time(void) interrupt 1 //定时中断
{
TF0=0;//清除中断标志
TR0=0; //关中断
if(ucVoiceLock==0) //原子锁判断
{
if(uiVoiceCnt!=0)
{
uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
}
else
{
; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}
}
if(ucDs1302Error>0) //EEPROM出错
{
if(ucDs1302Lock==0)//原子锁判断
{
uiDs1302Cnt++;//间歇性蜂鸣器报警的计时器
}
}
if(ucDpyTimeLock==0) //原子锁判断
{
uiDpyTimeCnt++;//数码管的闪烁计时器
}
key_scan(); //按键扫描函数
display_drive();//数码管字模的驱动函数
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}
void delay_short(unsigned int uiDelayShort)
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
; //一个分号相当于执行一条空语句
}
}
void delay_long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i<uiDelayLong;i++)
{
for(j=0;j<500;j++)//内嵌循环的空指令数量
{
; //一个分号相当于执行一条空语句
}
}
}
void initial_myself(void)//第一区 初始化单片机
{
key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
}
void initial_peripheral(void) //第二区 初始化外围
{
ucDigDot8=0; //小数点全部不显示
ucDigDot7=0;
ucDigDot6=0;
ucDigDot5=0;
ucDigDot4=0;
ucDigDot3=0;
ucDigDot2=0;
ucDigDot1=0;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
/* 注释七:
* 检查ds1302芯片的备用电池电量是否用完了。
* 在上电的时候,在一个特定的地址里把数据读出来,如果发现不等于0x5a,
* 则是因为备用电池电量用完了,导致保存在ds1302内部RAM数据区的数据被更改,
* 与此同时,应该重新写入一次0x5a,为下一次通电判断做准备
*/
ucCheckDs1302=Read1302(READ_CHECK); //判断ds1302内部的数据是否被更改
if(ucCheckDs1302!=0x5a)
{
Write1302 (WRITE_PROTECT,0X00); //禁止写保护
Write1302 (WRITE_CHECK,0x5a); //重新写入标志数据,方便下一次更换新电池后的判断
Write1302 (WRITE_PROTECT,0x80); //允许写保护
ucDs1302Error=1;//表示ds1302备用电池没电了,报警提示更换新电池
}
}
总结陈词:
下一节开始讲单片机驱动温度传感器芯片的内容,欲知详情,请听下回分解-----利用DS18B20做一个温控器。
(未完待续,下节更精彩,不要走开哦)
10646686
发表于 2014-5-21 11:38:24
谢谢分享
zlxd1990
发表于 2014-5-22 11:16:21
强烈顶楼主啊!好东西
flyzouyao
发表于 2014-5-22 19:18:55
谢谢楼主分享你的经验,学习中,顶你!
huitong
发表于 2014-5-22 21:38:56
重温学习51的资料,又有了全新的认识!
xxzzhy
发表于 2014-5-24 00:11:47
学习中。楼主,你能说说无刷电机的控制与驱动吗?
牛东
发表于 2014-5-24 07:59:02
吴坚鸿 发表于 2014-5-19 18:02
原子锁的本质,就是通过一个原子锁变量的设置和判断,避免某个重要变量在还没完全更改完,或者某个重要过 ...
谢了。鸿哥!
chy
发表于 2014-5-24 18:39:48
非常好哎,认真看一下,楼主加油
zl_123
发表于 2014-5-24 19:42:07
关键是写的这么草根,很不错
页:
1
2
3
4
5
[6]
7
8
9
10
11
12
13
14
15