开场白:
上一节讲了经过串口用delay延时办法发送一串数据,这种办法要求发送一串数据的时分趁热打铁,期间不能履行其它使命,由于delay(400)这个时刻还不算很长,所以能够应用在许多简略使命的体系中。可是在某些使命量许多的体系中,实时运转的主使命不答应被长时刻和常常性地中止,这个时分就需求用计数延时来代替delay延时。本节要教会咱们两个知识点:
榜首个:用计数延时办法发送一串数据的程序结构。
第二个:环形音讯行列的程序结构。
具体内容,请看源代码解说。
(1)硬件渠道:
依据朱兆祺51单片机学习板。
(2)完结功用:
波特率是:9600.
用朱兆祺51单片机学习板中的S1,S5,S9,S13作为独立按键。
按一次按键S1,发送EB 00 55 01 00 00 00 00 41
按一次按键S5,发送EB 00 55 02 00 00 00 00 42
按一次按键S9,发送EB 00 55 03 00 00 00 00 43
按一次按键S13,发送EB 00 55 04 00 00 00 00 44
(3)源代码解说如下:
- #include “REG52.H”
- #define const_send_time100//累计主循环次数的计数延时 请依据项目实践状况来调整此数据巨细
- #define const_send_size10//串口发送数据的缓冲区数组巨细
- #define const_Message_size10//环形音讯行列的缓冲区数组巨细
- #define const_key_time120 //按键去颤动延时的时刻
- #define const_key_time220 //按键去颤动延时的时刻
- #define const_key_time320 //按键去颤动延时的时刻
- #define const_key_time420 //按键去颤动延时的时刻
- #define const_voice_short40 //蜂鸣器短叫的持续时刻
- void initial_myself(void);
- void initial_peripheral(void);
- //void delay_short(unsigned int uiDelayshort);
- void delay_long(unsigned int uiDelaylong);
- void eusart_send(unsigned char ucSendData);//发送一个字节,内部没有每个字节之间的延时
- void send_service(void);//使用累计主循环次数的计数延时办法来发送一串数据
- void T0_time(void);//守时中止函数
- void usart_receive(void); //串口接纳中止函数
- void key_service(void); //按键服务的应用程序
- void key_scan(void); //按键扫描函数 放在守时中止里
- void insert_message(unsigned char ucMessageTemp);//刺进新的音讯到环形音讯行列里
- unsigned char get_message(void);//从环形音讯行列里提取音讯
- sbit led_dr=P3^5;//Led的驱动IO口
- sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
- 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,因而有必要一向输出低电平
- unsigned char ucSendregBuf[const_send_size]; //串口发送数据的缓冲区数组
- unsigned char ucMessageBuf[const_Message_size]; //环形音讯行列的缓冲区数据
- unsigned intuiMessageCurrent=0;//环形音讯行列的取数据当时方位
- unsigned intuiMessageInsert=0;//环形音讯行列的刺进新音讯时分的方位
- unsigned intuiMessageCnt=0;//计算环形音讯行列的音讯数量等于0时表明音讯行列里没有音讯
- unsigned char ucMessage=0; //当时获取到的音讯
- unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时刻计数器
- unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁
- 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 char ucSendStep=0;//发送一串数据的运转过程
- unsigned intuiSendTimeCnt=0; //累计主循环次数的计数延时器
- unsigned int uiSendCnt=0; //发送数据时的中心变量
- void main()
- {
- initial_myself();
- delay_long(100);
- initial_peripheral();
- while(1)
- {
- key_service(); //按键服务的应用程序
- send_service();//使用累计主循环次数的计数延时办法来发送一串数据
- }
- }
- /* 注释一:
- * 经过判别数组下标是否超范围的条件,把一个数组的首尾衔接起来,就像一个环形,
- * 因而命名为环形音讯行列。环形音讯行列有刺进音讯,获取音讯两个中心函数,以及一个
- * 计算音讯总数的uiMessageCnt中心变量,经过此变量,咱们能够知道音讯行列里边是否有音讯需求处理.
- * 我在做项目中很少用音讯行列的,印象中我只在两个项目中用过音讯行列这种办法。大部分的单片机
- * 项目其实直接用一两个中心变量就能够起到传递音讯的效果,就能满意体系的要求。以下是各变量的意义:
- * #define const_Message_size10//环形音讯行列的缓冲区数组巨细
- * unsigned char ucMessageBuf[const_Message_size]; //环形音讯行列的缓冲区数据
- * unsigned intuiMessageCurrent=0;//环形音讯行列的取数据当时方位
- * unsigned intuiMessageInsert=0;//环形音讯行列的刺进新音讯时分的方位
- * unsigned intuiMessageCnt=0;//计算环形音讯行列的音讯数量等于0时表明音讯行列里没有音讯
- */
- void insert_message(unsigned char ucMessageTemp)//刺进新的音讯到环形音讯行列里
- {
- if(uiMessageCnt
- {
- ucMessageBuf[uiMessageInsert]=ucMessageTemp;
- uiMessageInsert++;//刺进新音讯时分的方位
- if(uiMessageInsert>=const_Message_size) //到了缓冲区结尾,则从缓冲区的最初重新开端。数组的首尾衔接,看起来就像环形
- {
- uiMessageInsert=0;
- }
- uiMessageCnt++; //音讯数量累加等于0时表明音讯行列里没有音讯
- }
- }
- unsigned char get_message(void)//从环形音讯行列里提取音讯
- {
- unsigned char ucMessageTemp=0;//回来的音讯中心变量,默以为0
- if(uiMessageCnt>0)//只要音讯数量大于0时才能够提取音讯
- {
- ucMessageTemp=ucMessageBuf[uiMessageCurrent];
- uiMessageCurrent++;//环形音讯行列的取数据当时方位
- if(uiMessageCurrent>=const_Message_size) //到了缓冲区结尾,则从缓冲区的最初重新开端。数组的首尾衔接,看起来就像环形
- {
- uiMessageCurrent=0;
- }
- uiMessageCnt–; //每提取一次,音讯数量就减一等于0时表明音讯行列里没有音讯
- }
- return ucMessageTemp;
- }
- void send_service(void)//使用累计主循环次数的计数延时办法来发送一串数据
- {
- switch(ucSendStep)//发送一串数据的运转过程
- {
- case 0: //从环形音讯行列里提取音讯
- if(uiMessageCnt>0)//阐明有音讯需求处理
- {
- ucMessage=get_message();
- switch(ucMessage) //音讯处理
- {
- case 1:
- ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x01; //01代表1号键
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x00;
- ucSendregBuf[6]=0x00;
- ucSendregBuf[7]=0x00;
- ucSendregBuf[8]=0x41;
- uiSendCnt=0; //发送数据的中心变量清零
- uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
- ucSendStep=1; //切换到下一步发送一串数据
- break;
- case 2:
- ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x02; //02代表2号键
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x00;
- ucSendregBuf[6]=0x00;
- ucSendregBuf[7]=0x00;
- ucSendregBuf[8]=0x42;
- uiSendCnt=0; //发送数据的中心变量清零
- uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
- ucSendStep=1; //切换到下一步发送一串数据
- break;
- case 3:
- ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x03; //03代表3号键
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x00;
- ucSendregBuf[6]=0x00;
- ucSendregBuf[7]=0x00;
- ucSendregBuf[8]=0x43;
- uiSendCnt=0; //发送数据的中心变量清零
- uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
- ucSendStep=1; //切换到下一步发送一串数据
- break;
- case 4:
- ucSendregBuf[0]=0xeb; //把预备发送的数据放入发送缓冲区
- ucSendregBuf[1]=0x00;
- ucSendregBuf[2]=0x55;
- ucSendregBuf[3]=0x04; //04代表4号键
- ucSendregBuf[4]=0x00;
- ucSendregBuf[5]=0x00;
- ucSendregBuf[6]=0x00;
- ucSendregBuf[7]=0x00;
- ucSendregBuf[8]=0x44;
- uiSendCnt=0; //发送数据的中心变量清零
- uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
- ucSendStep=1; //切换到下一步发送一串数据
- break;
- default://假如没有符合要求的音讯,则不处理
- ucSendStep=0; //保持现状,不切换
- break;
- }
- }
- break;
- case 1://使用累加主循环次数的计数延时办法来发送一串数据
- /* 注释二:
- * 这儿的计数延时为什么不必累计守时中止次数的延时,而用累计主循环次数的计数延时?
- * 由于本程序守时器中止一次需求500个指令时刻,时刻分辨率太低,不方便微调时刻。因而我
- * 就用累计主循环次数的计数延时办法,在做项目的时分,各位读者应该依据体系的实践状况
- * 来调整const_send_time的巨细。
- */
- uiSendTimeCnt++;//累计主循环次数的计数延时,为每个字节之间添加延时,
- if(uiSendTimeCnt>const_send_time)//请依据实践体系的状况,调整const_send_time的巨细
- {
- uiSendTimeCnt=0;
- eusart_send(ucSendregBuf[uiSendCnt]);//发送一串数据给上位机
- uiSendCnt++;
- if(uiSendCnt>=9) //阐明数据现已发送结束
- {
- uiSendCnt=0;
- ucSendStep=0; //回来到上一步,处理其它未处理的音讯
- }
- }
- break;
- }
- }
- void eusart_send(unsigned char ucSendData)
- {
- ES = 0; //关串口中止
- TI = 0; //清零串口发送完结中止请求标志
- SBUF =ucSendData; //发送一个字节
- /* 注释三:
- * 依据我个人的经历,在发送一串数据中,每个字节之间有必要添加一个延时,用来等候串口发送完结。
- * 当然,也有一些朋友可能不添加延时,直接靠单片机自带的发送完结标志位来判别,可是我曾经
- * 在做项目中,感觉单单靠发送完结标志位来判别仍是简单犯错(当然也有可能是我本身程序的问题),
- * 所以后来在大部分的项目中我就爽性靠延时来等候它发送完结。我在51,PIC单片机中都是这么做的。
- * 可是,凭我的经历,在stm32单片机中,能够不添加延时,直接靠单片机自带的标志位来判别就很牢靠。
- */
- //delay_short(400);//由于外部在每个发送字节之间用了累计主循环次数的计数延时,因而不要此行的delay延时
- TI = 0; //清零串口发送完结中止请求标志
- ES = 1; //答应串口中止
- }
- 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:// 1号键 对应朱兆祺学习板的S1键
- insert_message(0x01);//把新音讯刺进到环形音讯行列里等候处理
- ucVoiceLock=1;//原子锁加锁,维护中止与主函数的同享数据
- uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁
- ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
- break;
- case 2:// 2号键 对应朱兆祺学习板的S5键
- insert_message(0x02);//把新音讯刺进到环形音讯行列里等候处理
- ucVoiceLock=1;//原子锁加锁,维护中止与主函数的同享数据
- uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁
- ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
- break;
- case 3:// 3号键 对应朱兆祺学习板的S9键
- insert_message(0x03);//把新音讯刺进到环形音讯行列里等候处理
- ucVoiceLock=1;//原子锁加锁,维护中止与主函数的同享数据
- uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁
- ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
- break;
- case 4:// 4号键 对应朱兆祺学习板的S13键
- insert_message(0x04);//把新音讯刺进到环形音讯行列里等候处理
- ucVoiceLock=1;//原子锁加锁,维护中止与主函数的同享数据
- uiVoiceCnt=const_voice_short; //按键声响触发,滴一声就停。
- ucVoiceLock=0; //原子锁解锁
- ucKeySec=0;//呼应按键服务处理程序后,按键编号清零,防止共同触发
- break;
- }
- }
- 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();//按键扫描函数
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- TR0=1;//开中止
- }
- void usart_receive(void) interrupt 4 //串口中止
- {
- if(RI==1)
- {
- RI = 0; //接纳中止,及时把接纳中止标志位清零
- }
- else
- {
- TI = 0; //发送中止,及时把发送中止标志位清零
- }
- }
- //void delay_short(unsigned int uiDelayShort)
- //{
- // unsigned int i;
- // for(i=0;i
- // {
- // ; //一个分号相当于履行一条空句子
- // }
- //}
- void delay_long(unsigned int uiDelayLong)
- {
- unsigned int i;
- unsigned int j;
- for(i=0;i
- {
- for(j=0;j<500;j++)//内嵌循环的空指令数量
- {
- ; //一个分号相当于履行一条空句子
- }
- }
- }
- void initial_myself(void)//榜首区 初始化单片机
- {
- /* 注释五:
- * 矩阵键盘也能够做独立按键,条件是把某一根公共输出线输出低电平,
- * 模仿独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
- * 朱兆祺51学习板的S1和S5两个按键便是本程序中用到的两个独立按键。
- */
- key_gnd_dr=0; //模仿独立按键的地GND,因而有必要一向输出低电平
- led_dr=0; //关Led灯
- beep_dr=1; //用PNP三极管操控蜂鸣器,输出高电平时不叫。
- //装备守时器
- TMOD=0x01;//设置守时器0为工作办法1
- TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
- TL0=0x0b;
- //装备串口
- SCON=0x50;
- TMOD=0X21;
- TH1=TL1=-(11059200L/12/32/9600);//串口波特率9600。
- TR1=1;
- }
- void initial_peripheral(void) //第二区 初始化外围
- {
- EA=1; //开总中止
- ES=1; //答应串口中止
- ET0=1; //答应守时中止
- TR0=1; //发动守时中止
- }
总结陈词:
前面几个章节中,每个章节要么独登时解说串口收数据,要么独登时解说发数据,实践上在大部分的项目中,串口都需求“一收一应对”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判别,假如校验正确则回来正确应对指令,假如校验过错则回来过错应对指令,主机收到应对指令后,假如发现是正确应对指令则持续发送其它的新数据,假如发现是过错应对指令,或许超时没有接纳到任何应对指令,则持续重发,假如接连重发三次都是过错应对或许无应对,主机就进行报错处理。读者只要把我的串口收发程序结合起来,就很简单完结这样的功用,我就不再具体解说了。从下一节开端我解说单片机掉电后数据保存的内容,欲知概况,请听下回分解—–使用AT24C02进行掉电后的数据保存。