新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > 第44节:从机的串口收发综合程序框架

第44节:从机的串口收发综合程序框架

作者: 时间:2016-11-22 来源:网络 收藏
开场白:
根据上一节的预告,本来这一节内容打算讲“利用AT24C02进行掉电后的数据保存”的,但是由于网友“261854681”强烈建议我讲一个完整的串口收发程序实例,因此我决定再花两节篇幅讲讲这方面的内容。
实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
这节先讲从机的收发端程序实例。要教会大家三个知识点:
第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:从机端的收发端程序框架
第三个:从机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板

(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用朱兆祺51单片机学习板中的S1,S5,S9,S13作为独立按键。
一共有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秒钟,否则认为通讯超时出错引发蜂鸣器报警。如果接收到得数据校验正确,
则返回校验正确应答:eb 00 55 f5 00 00 35,
否则返回校验出错应答::eb 00 55 fa 00 00 3a。
系统处于待机状态时,LED灯一直亮,
系统处于非待机状态时,LED灯闪烁,
系统处于通讯超时出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。


通过电脑的串口助手,依次发送以下测试数据,将会分别更改参数1,参数2,参数3,参数4。注意,每串数据之间的时间最大不能超过10秒,否则系统认为通讯超时报警。
把参数1更改为十进制的1: eb 00 55 01 00 02 00 01 44
把参数2更改为十进制的12:eb 00 55 02 00 02 00 0c 50
把参数3更改为十进制的123: eb 00 55 03 00 02 00 7b c0
把参数4更改为十进制的1234:eb 00 55 04 00 02 04 d2 1c

(3)源代码讲解如下:
  1. #include "REG52.H"
  2. #define const_voice_short40 //蜂鸣器短叫的持续时间
  3. #define const_key_time120 //按键去抖动延时的时间
  4. #define const_key_time220 //按键去抖动延时的时间
  5. #define const_key_time320 //按键去抖动延时的时间
  6. #define const_key_time420 //按键去抖动延时的时间
  7. #define const_led_0_5s200 //大概0.5秒的时间
  8. #define const_led_1s 400 //大概1秒的时间
  9. #define const_send_time_out 4000//通讯超时出错的时间 大概10秒
  10. #define const_rc_size20//接收串口中断数据的缓冲区数组大小
  11. #define const_receive_time5//如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小
  12. #define const_send_size10//串口发送数据的缓冲区数组大小
  13. void initial_myself(void);
  14. void initial_peripheral(void);
  15. void delay_short(unsigned int uiDelayShort);
  16. void delay_long(unsigned int uiDelaylong);
  17. //驱动数码管的74HC595
  18. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  19. void display_drive(void); //显示数码管字模的驱动函数
  20. void display_service(void); //显示的窗口菜单服务程序
  21. //驱动LED的74HC595
  22. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  23. void T0_time(void);//定时中断函数
  24. void usart_receive(void); //串口接收中断函数
  25. void usart_service(void);//串口服务程序,在main函数里
  26. void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时
  27. void key_service(void); //按键服务的应用程序
  28. void key_scan(void);//按键扫描函数 放在定时中断里
  29. void status_service(void);//状态显示的应用程序
  30. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  31. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  32. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  33. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
  34. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  35. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  36. sbit led_dr=P3^5;//作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态
  37. sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
  38. sbit dig_hc595_st_dr=P2^1;
  39. sbit dig_hc595_ds_dr=P2^2;
  40. sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
  41. sbit hc595_st_dr=P2^4;
  42. sbit hc595_ds_dr=P2^5;
  43. unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组
  44. unsigned intuiSendCnt=0; //用来识别串口是否接收完一串数据的计时器
  45. unsigned char ucSendLock=1; //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  46. unsigned intuiRcregTotal=0;//代表当前缓冲区已经接收了多少个数据
  47. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  48. unsigned intuiRcMoveIndex=0;//用来解析数据协议的中间变量
  49. unsigned charucSendCntLock=0; //串口计时器的原子锁
  50. unsigned char ucRcType=0;//数据类型
  51. unsigned intuiRcSize=0;//数据长度
  52. unsigned char ucRcCy=0;//校验累加和
  53. unsigned intuiLedCnt=0;//控制Led闪烁的延时计时器
  54. unsigned intuiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
  55. unsigned char ucSendTimeOutLock=0; //原子锁
  56. unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错
  57. unsigned char ucKeySec=0; //被触发的按键编号
  58. unsigned intuiKeyTimeCnt1=0; //按键去抖动延时计数器
  59. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  60. unsigned intuiKeyTimeCnt2=0; //按键去抖动延时计数器
  61. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  62. unsigned intuiKeyTimeCnt3=0; //按键去抖动延时计数器
  63. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  64. unsigned intuiKeyTimeCnt4=0; //按键去抖动延时计数器
  65. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志
  66. unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器
  67. unsigned charucVoiceLock=0;//蜂鸣器鸣叫的原子锁
  68. unsigned char ucDigShow8;//第8位数码管要显示的内容
  69. unsigned char ucDigShow7;//第7位数码管要显示的内容
  70. unsigned char ucDigShow6;//第6位数码管要显示的内容
  71. unsigned char ucDigShow5;//第5位数码管要显示的内容
  72. unsigned char ucDigShow4;//第4位数码管要显示的内容
  73. unsigned char ucDigShow3;//第3位数码管要显示的内容
  74. unsigned char ucDigShow2;//第2位数码管要显示的内容
  75. unsigned char ucDigShow1;//第1位数码管要显示的内容
  76. unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
  77. unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
  78. unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
  79. unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
  80. unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
  81. unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
  82. unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
  83. unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
  84. unsigned char ucDigShowTemp=0; //临时中间变量
  85. unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量
  86. unsigned char ucWd1Update=1; //窗口1更新显示标志
  87. unsigned char ucWd2Update=0; //窗口2更新显示标志
  88. unsigned char ucWd3Update=0; //窗口3更新显示标志
  89. unsigned char ucWd4Update=0; //窗口4更新显示标志
  90. unsigned char ucWd=1;//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  91. unsigned intuiSetData1=0;//本程序中需要被设置的参数1
  92. unsigned intuiSetData2=0;//本程序中需要被设置的参数2
  93. unsigned intuiSetData3=0;//本程序中需要被设置的参数3
  94. unsigned intuiSetData4=0;//本程序中需要被设置的参数4
  95. unsigned char ucTemp1=0;//中间过渡变量
  96. unsigned char ucTemp2=0;//中间过渡变量
  97. unsigned char ucTemp3=0;//中间过渡变量
  98. unsigned char ucTemp4=0;//中间过渡变量
  99. //根据原理图得出的共阴数码管字模表
  100. code unsigned char dig_table[]=
  101. {
  102. 0x3f,//0 序号0
  103. 0x06,//1 序号1
  104. 0x5b,//2 序号2
  105. 0x4f,//3 序号3
  106. 0x66,//4 序号4
  107. 0x6d,//5 序号5
  108. 0x7d,//6 序号6
  109. 0x07,//7 序号7
  110. 0x7f,//8 序号8
  111. 0x6f,//9 序号9
  112. 0x00,//无 序号10
  113. 0x40,//- 序号11
  114. 0x73,//P 序号12
  115. };
  116. void main()
  117. {
  118. initial_myself();
  119. delay_long(100);
  120. initial_peripheral();
  121. while(1)
  122. {
  123. key_service(); //按键服务的应用程序
  124. usart_service();//串口服务程序
  125. display_service(); //显示的窗口菜单服务程序
  126. status_service();//状态显示的应用程序
  127. }
  128. }
  129. void status_service(void)//状态显示的应用程序
  130. {
  131. if(ucStatus!=0) //处于非待机的状态,Led闪烁
  132. {
  133. if(uiLedCnt
  134. {
  135. led_dr=1;//前半秒亮
  136. if(ucStatus==2)//处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
  137. {
  138. ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  139. uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  140. ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  141. }
  142. }
  143. else if(uiLedCnt
  144. {
  145. led_dr=0; //前半秒灭
  146. }
  147. else
  148. {
  149. uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
  150. }
  151. }
  152. else//处于待机状态,Led一直亮
  153. {
  154. led_dr=1;
  155. }
  156. }
  157. void usart_service(void)//串口服务程序,在main函数里
  158. {
  159. unsigned int i;
  160. if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  161. {
  162. ucSendLock=0; //处理一次就锁起来,不用每次都进来,除非有新接收的数据
  163. //下面的代码进入数据协议解析和数据处理的阶段
  164. uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
  165. while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  166. {
  167. if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)//数据头eb 00 55的判断
  168. {
  169. ucRcType=ucRcregBuf[uiRcMoveIndex+3]; //数据类型一个字节
  170. uiRcSize=ucRcregBuf[uiRcMoveIndex+4]; //数据长度两个字节
  171. uiRcSize=uiRcSize<<8;
  172. uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  173. ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]; //记录最后一个字节的校验
  174. ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;//清零最后一个字节的累加和变量
  175. for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  176. {
  177. ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  178. }
  179. if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])//如果校验正确,则进入以下数据处理
  180. {
  181. switch(ucRcType) //根据不同的数据类型来做不同的数据处理
  182. {
  183. case 0x01: //设置参数1
  184. ucStatus=1; //从设置参数1开始,表示当前处于正在发送数据的状态
  185. uiSetData1=ucRcregBuf[uiRcMoveIndex+6];//把两个字节合并成一个int类型的数据
  186. uiSetData1=uiSetData1<<8;
  187. uiSetData1=uiSetData1+ucRcregBuf[uiRcMoveIndex+7];
  188. ucWd1Update=1; //窗口1更新显示
  189. break;
  190. case 0x02: //设置参数2
  191. uiSetData2=ucRcregBuf[uiRcMoveIndex+6];//把两个字节合并成一个int类型的数据
  192. uiSetData2=uiSetData2<<8;
  193. uiSetData2=uiSetData2+ucRcregBuf[uiRcMoveIndex+7];
  194. ucWd2Update=1; //窗口2更新显示
  195. break;
  196. case 0x03: //设置参数3
  197. uiSetData3=ucRcregBuf[uiRcMoveIndex+6];//把两个字节合并成一个int类型的数据
  198. uiSetData3=uiSetData3<<8;
  199. uiSetData3=uiSetData3+ucRcregBuf[uiRcMoveIndex+7];
  200. ucWd3Update=1; //窗口3更新显示
  201. break;
  202. case 0x04: //设置参数4
  203. ucStatus=0; //从设置参数4结束发送数据的状态,表示发送数据的过程成功,切换回待机状态
  204. uiSetData4=ucRcregBuf[uiRcMoveIndex+6];//把两个字节合并成一个int类型的数据
  205. uiSetData4=uiSetData4<<8;
  206. uiSetData4=uiSetData4+ucRcregBuf[uiRcMoveIndex+7];
  207. ucWd4Update=1; //窗口4更新显示
  208. break;
  209. }
  210. ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
  211. ucSendregBuf[1]=0x00;
  212. ucSendregBuf[2]=0x55;
  213. ucSendregBuf[3]=0xf5;//代表校验正确
  214. ucSendregBuf[4]=0x00;
  215. ucSendregBuf[5]=0x00;
  216. ucSendregBuf[6]=0x35;
  217. for(i=0;i<7;i++)//返回校验正确的应答指令
  218. {
  219. eusart_send(ucSendregBuf[i]);//发送一串数据给上位机
  220. }
  221. }
  222. else
  223. {
  224. ucSendTimeOutLock=1; //原子锁加锁
  225. uiSendTimeOutCnt=0;//超时计时器计时清零
  226. ucSendTimeOutLock=0; //原子锁解锁
  227. ucSendregBuf[0]=0xeb; //把准备发送的数据放入发送缓冲区
  228. ucSendregBuf[1]=0x00;
  229. ucSendregBuf[2]=0x55;
  230. ucSendregBuf[3]=0xfa; //代表校验错误
  231. ucSendregBuf[4]=0x00;
  232. ucSendregBuf[5]=0x00;
  233. ucSendregBuf[6]=0x3a;
  234. for(i=0;i<7;i++)//返回校验错误的应答指令
  235. {
  236. eusart_send(ucSendregBuf[i]);//发送一串数据给上位机
  237. }
  238. }
  239. ucSendTimeOutLock=1; //原子锁加锁
  240. uiSendTimeOutCnt=0;//超时计时器计时清零
  241. ucSendTimeOutLock=0; //原子锁解锁
  242. break; //退出循环
  243. }
  244. uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  245. }
  246. uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据
  247. }
  248. }
  249. void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
  250. {
  251. ES = 0; //关串口中断
  252. TI = 0; //清零串口发送完成中断请求标志
  253. SBUF =ucSendData; //发送一个字节
  254. delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
  255. TI = 0; //清零串口发送完成中断请求标志
  256. ES = 1; //允许串口中断
  257. }
  258. void display_service(void) //显示的窗口菜单服务程序
  259. {
  260. switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  261. {
  262. case 1: //显示P--1窗口的数据
  263. if(ucWd1Update==1)//窗口1要全部更新显示
  264. {
  265. ucWd1Update=0;//及时清零标志,避免一直进来扫描
  266. ucDigShow8=12;//第8位数码管显示P
  267. ucDigShow7=11;//第7位数码管显示-
  268. ucDigShow6=1; //第6位数码管显示1
  269. ucDigShow5=10;//第5位数码管显示无
  270. //先分解数据
  271. ucTemp4=uiSetData1/1000;
  272. ucTemp3=uiSetData1%1000/100;
  273. ucTemp2=uiSetData1%100/10;
  274. ucTemp1=uiSetData1%10;
  275. //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
  276. if(uiSetData1<1000)
  277. {
  278. ucDigShow4=10;//如果小于1000,千位显示无
  279. }
  280. else
  281. {
  282. ucDigShow4=ucTemp4;//第4位数码管要显示的内容
  283. }
  284. if(uiSetData1<100)
  285. {
  286. ucDigShow3=10;//如果小于100,百位显示无
  287. }
  288. else
  289. {
  290. ucDigShow3=ucTemp3;//第3位数码管要显示的内容
  291. }
  292. if(uiSetData1<10)
  293. {
  294. ucDigShow2=10;//如果小于10,十位显示无
  295. }
  296. else
  297. {
  298. ucDigShow2=ucTemp2;//第2位数码管要显示的内容
  299. }
  300. ucDigShow1=ucTemp1;//第1位数码管要显示的内容
  301. }
  302. break;
  303. case 2://显示P--2窗口的数据
  304. if(ucWd2Update==1)//窗口2要全部更新显示
  305. {
  306. ucWd2Update=0;//及时清零标志,避免一直进来扫描
  307. ucDigShow8=12;//第8位数码管显示P
  308. ucDigShow7=11;//第7位数码管显示-
  309. ucDigShow6=2;//第6位数码管显示2
  310. ucDigShow5=10; //第5位数码管显示无
  311. ucTemp4=uiSetData2/1000; //分解数据
  312. ucTemp3=uiSetData2%1000/100;
  313. ucTemp2=uiSetData2%100/10;
  314. ucTemp1=uiSetData2%10;
  315. if(uiSetData2<1000)
  316. {
  317. ucDigShow4=10;//如果小于1000,千位显示无
  318. }
  319. else
  320. {
  321. ucDigShow4=ucTemp4;//第4位数码管要显示的内容
  322. }
  323. if(uiSetData2<100)
  324. {
  325. ucDigShow3=10;//如果小于100,百位显示无
  326. }
  327. else
  328. {
  329. ucDigShow3=ucTemp3;//第3位数码管要显示的内容
  330. }
  331. if(uiSetData2<10)
  332. {
  333. ucDigShow2=10;//如果小于10,十位显示无
  334. }
  335. else
  336. {
  337. ucDigShow2=ucTemp2;//第2位数码管要显示的内容
  338. }
  339. ucDigShow1=ucTemp1;//第1位数码管要显示的内容
  340. }
  341. break;
  342. case 3://显示P--3窗口的数据
  343. if(ucWd3Update==1)//窗口3要全部更新显示
  344. {
  345. ucWd3Update=0;//及时清零标志,避免一直进来扫描
  346. ucDigShow8=12;//第8位数码管显示P
  347. ucDigShow7=11;//第7位数码管显示-
  348. ucDigShow6=3;//第6位数码管显示3
  349. ucDigShow5=10; //第5位数码管显示无
  350. ucTemp4=uiSetData3/1000; //分解数据
  351. ucTemp3=uiSetData3%1000/100;
  352. ucTemp2=uiSetData3%100/10;
  353. ucTemp1=uiSetData3%10;
  354. if(uiSetData3<1000)
  355. {
  356. ucDigShow4=10;//如果小于1000,千位显示无
  357. }
  358. else
  359. {
  360. ucDigShow4=ucTemp4;//第4位数码管要显示的内容
  361. }
  362. if(uiSetData3<100)
  363. {
  364. ucDigShow3=10;//如果小于100,百位显示无
  365. }
  366. else
  367. {
  368. ucDigShow3=ucTemp3;//第3位数码管要显示的内容
  369. }
  370. if(uiSetData3<10)
  371. {
  372. ucDigShow2=10;//如果小于10,十位显示无
  373. }
  374. else
  375. {
  376. ucDigShow2=ucTemp2;//第2位数码管要显示的内容
  377. }
  378. ucDigShow1=ucTemp1;//第1位数码管要显示的内容
  379. }
  380. break;
  381. case 4://显示P--4窗口的数据
  382. if(ucWd4Update==1)//窗口4要全部更新显示
  383. {
  384. ucWd4Update=0;//及时清零标志,避免一直进来扫描
  385. ucDigShow8=12;//第8位数码管显示P
  386. ucDigShow7=11;//第7位数码管显示-
  387. ucDigShow6=4;//第6位数码管显示4
  388. ucDigShow5=10; //第5位数码管显示无
  389. ucTemp4=uiSetData4/1000; //分解数据
  390. ucTemp3=uiSetData4%1000/100;
  391. ucTemp2=uiSetData4%100/10;
  392. ucTemp1=uiSetData4%10;
  393. if(uiSetData4<1000)
  394. {
  395. ucDigShow4=10;//如果小于1000,千位显示无
  396. }
  397. else
  398. {
  399. ucDigShow4=ucTemp4;//第4位数码管要显示的内容
  400. }
  401. if(uiSetData4<100)
  402. {
  403. ucDigShow3=10;//如果小于100,百位显示无
  404. }
  405. else
  406. {
  407. ucDigShow3=ucTemp3;//第3位数码管要显示的内容
  408. }
  409. if(uiSetData4<10)
  410. {
  411. ucDigShow2=10;//如果小于10,十位显示无
  412. }
  413. else
  414. {
  415. ucDigShow2=ucTemp2;//第2位数码管要显示的内容
  416. }
  417. ucDigShow1=ucTemp1;//第1位数码管要显示的内容
  418. }
  419. break;
  420. }
  421. }
  422. void key_scan(void)//按键扫描函数 放在定时中断里
  423. {
  424. if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  425. {
  426. ucKeyLock1=0; //按键自锁标志清零
  427. uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
  428. }
  429. else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  430. {
  431. uiKeyTimeCnt1++; //累加定时中断次数
  432. if(uiKeyTimeCnt1>const_key_time1)
  433. {
  434. uiKeyTimeCnt1=0;
  435. ucKeyLock1=1;//自锁按键置位,避免一直触发
  436. ucKeySec=1; //触发1号键
  437. }
  438. }
  439. if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  440. {
  441. ucKeyLock2=0; //按键自锁标志清零
  442. uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
  443. }
  444. else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  445. {
  446. uiKeyTimeCnt2++; //累加定时中断次数
  447. if(uiKeyTimeCnt2>const_key_time2)
  448. {
  449. uiKeyTimeCnt2=0;
  450. ucKeyLock2=1;//自锁按键置位,避免一直触发
  451. ucKeySec=2; //触发2号键
  452. }
  453. }
  454. if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  455. {
  456. ucKeyLock3=0; //按键自锁标志清零
  457. uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
  458. }
  459. else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  460. {
  461. uiKeyTimeCnt3++; //累加定时中断次数
  462. if(uiKeyTimeCnt3>const_key_time3)
  463. {
  464. uiKeyTimeCnt3=0;
  465. ucKeyLock3=1;//自锁按键置位,避免一直触发
  466. ucKeySec=3; //触发3号键
  467. }
  468. }
  469. if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  470. {
  471. ucKeyLock4=0; //按键自锁标志清零
  472. uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
  473. }
  474. else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  475. {
  476. uiKeyTimeCnt4++; //累加定时中断次数
  477. if(uiKeyTimeCnt4>const_key_time4)
  478. {
  479. uiKeyTimeCnt4=0;
  480. ucKeyLock4=1;//自锁按键置位,避免一直触发
  481. ucKeySec=4; //触发4号键
  482. }
  483. }
  484. }
  485. void key_service(void) //按键服务的应用程序
  486. {
  487. switch(ucKeySec) //按键服务状态切换
  488. {
  489. case 1:// 加按键 对应朱兆祺学习板的S1键
  490. switch(ucWd)//在不同的窗口下,设置不同的参数
  491. {
  492. case 1:
  493. uiSetData1++;
  494. if(uiSetData1>9999) //最大值是9999
  495. {
  496. uiSetData1=9999;
  497. }
  498. ucWd1Update=1;//窗口1更新显示
  499. break;
  500. case 2:
  501. uiSetData2++;
  502. if(uiSetData2>9999) //最大值是9999
  503. {
  504. uiSetData2=9999;
  505. }
  506. ucWd2Update=1;//窗口2更新显示
  507. break;
  508. case 3:
  509. uiSetData3++;
  510. if(uiSetData3>9999) //最大值是9999
  511. {
  512. uiSetData3=9999;
  513. }
  514. ucWd3Update=1;//窗口3更新显示
  515. break;
  516. case 4:
  517. uiSetData4++;
  518. if(uiSetData4>9999) //最大值是9999
  519. {
  520. uiSetData4=9999;
  521. }
  522. ucWd4Update=1;//窗口4更新显示
  523. break;
  524. }
  525. ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  526. uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  527. ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  528. ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
  529. break;
  530. case 2:// 减按键 对应朱兆祺学习板的S5键
  531. switch(ucWd)//在不同的窗口下,设置不同的参数
  532. {
  533. case 1:
  534. uiSetData1--;
  535. if(uiSetData1>9999)
  536. {
  537. uiSetData1=0;//最小值是0
  538. }
  539. ucWd1Update=1;//窗口1更新显示
  540. break;
  541. case 2:
  542. uiSetData2--;
  543. if(uiSetData2>9999)
  544. {
  545. uiSetData2=0;//最小值是0
  546. }
  547. ucWd2Update=1;//窗口2更新显示
  548. break;
  549. case 3:
  550. uiSetData3--;
  551. if(uiSetData3>9999)
  552. {
  553. uiSetData3=0;//最小值是0
  554. }
  555. ucWd3Update=1;//窗口3更新显示
  556. break;
  557. case 4:
  558. uiSetData4--;
  559. if(uiSetData4>9999)
  560. {
  561. uiSetData4=0;//最小值是0
  562. }
  563. ucWd4Update=1;//窗口4更新显示
  564. break;
  565. }
  566. ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  567. uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  568. ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  569. ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
  570. break;
  571. case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  572. ucWd++;//切换窗口
  573. if(ucWd>4)
  574. {
  575. ucWd=1;
  576. }
  577. switch(ucWd)//在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  578. {
  579. case 1:
  580. ucWd1Update=1;//窗口1更新显示
  581. break;
  582. case 2:
  583. ucWd2Update=1;//窗口2更新显示
  584. break;
  585. case 3:
  586. ucWd3Update=1;//窗口3更新显示
  587. break;
  588. case 4:
  589. ucWd4Update=1;//窗口4更新显示
  590. break;
  591. }
  592. ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  593. uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  594. ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  595. ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
  596. break;
  597. case 4:// 复位按键 对应朱兆祺学习板的S13键
  598. switch(ucStatus)//在不同的状态下,进行不同的操作
  599. {
  600. case 0://处于待机状态
  601. break;
  602. case 1://处于正在通讯的过程
  603. break;
  604. case 2: //发送数据出错,比如中间超时没有接收到数据
  605. ucStatus=0; //切换回待机的状态
  606. break;
  607. }
  608. ucVoiceLock=1;//原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  609. uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  610. ucVoiceLock=0;//原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  611. ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
  612. break;
  613. }
  614. }
  615. void display_drive(void)
  616. {
  617. //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  618. switch(ucDisplayDriveStep)
  619. {
  620. case 1://显示第1位
  621. ucDigShowTemp=dig_table[ucDigShow1];
  622. if(ucDigDot1==1)
  623. {
  624. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  625. }
  626. dig_hc595_drive(ucDigShowTemp,0xfe);
  627. break;
  628. case 2://显示第2位
  629. ucDigShowTemp=dig_table[ucDigShow2];
  630. if(ucDigDot2==1)
  631. {
  632. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  633. }
  634. dig_hc595_drive(ucDigShowTemp,0xfd);
  635. break;
  636. case 3://显示第3位
  637. ucDigShowTemp=dig_table[ucDigShow3];
  638. if(ucDigDot3==1)
  639. {
  640. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  641. }
  642. dig_hc595_drive(ucDigShowTemp,0xfb);
  643. break;
  644. case 4://显示第4位
  645. ucDigShowTemp=dig_table[ucDigShow4];
  646. if(ucDigDot4==1)
  647. {
  648. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  649. }
  650. dig_hc595_drive(ucDigShowTemp,0xf7);
  651. break;
  652. case 5://显示第5位
  653. ucDigShowTemp=dig_table[ucDigShow5];
  654. if(ucDigDot5==1)
  655. {
  656. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  657. }
  658. dig_hc595_drive(ucDigShowTemp,0xef);
  659. break;
  660. case 6://显示第6位
  661. ucDigShowTemp=dig_table[ucDigShow6];
  662. if(ucDigDot6==1)
  663. {
  664. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  665. }
  666. dig_hc595_drive(ucDigShowTemp,0xdf);
  667. break;
  668. case 7://显示第7位
  669. ucDigShowTemp=dig_table[ucDigShow7];
  670. if(ucDigDot7==1)
  671. {
  672. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  673. }
  674. dig_hc595_drive(ucDigShowTemp,0xbf);
  675. break;
  676. case 8://显示第8位
  677. ucDigShowTemp=dig_table[ucDigShow8];
  678. if(ucDigDot8==1)
  679. {
  680. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  681. }
  682. dig_hc595_drive(ucDigShowTemp,0x7f);
  683. break;
  684. }
  685. ucDisplayDriveStep++;
  686. if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
  687. {
  688. ucDisplayDriveStep=1;
  689. }
  690. }
  691. //数码管的74HC595驱动函数
  692. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  693. {
  694. unsigned char i;
  695. unsigned char ucTempData;
  696. dig_hc595_sh_dr=0;
  697. dig_hc595_st_dr=0;
  698. ucTempData=ucDigStatusTemp16_09;//先送高8位
  699. for(i=0;i<8;i++)
  700. {
  701. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  702. else dig_hc595_ds_dr=0;
  703. dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  704. delay_short(1);
  705. dig_hc595_sh_dr=1;
  706. delay_short(1);
  707. ucTempData=ucTempData<<1;
  708. }
  709. ucTempData=ucDigStatusTemp08_01;//再先送低8位
  710. for(i=0;i<8;i++)
  711. {
  712. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  713. else dig_hc595_ds_dr=0;
  714. dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  715. delay_short(1);
  716. dig_hc595_sh_dr=1;
  717. delay_short(1);
  718. ucTempData=ucTempData<<1;
  719. }
  720. dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  721. delay_short(1);
  722. dig_hc595_st_dr=1;
  723. delay_short(1);
  724. dig_hc595_sh_dr=0; //拉低,抗干扰就增强
  725. dig_hc595_st_dr=0;
  726. dig_hc595_ds_dr=0;
  727. }
  728. //LED灯的74HC595驱动函数
  729. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  730. {
  731. unsigned char i;
  732. unsigned char ucTempData;
  733. hc595_sh_dr=0;
  734. hc595_st_dr=0;
  735. ucTempData=ucLedStatusTemp16_09;//先送高8位
  736. for(i=0;i<8;i++)
  737. {
  738. if(ucTempData>=0x80)hc595_ds_dr=1;
  739. else hc595_ds_dr=0;
  740. hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  741. delay_short(1);
  742. hc595_sh_dr=1;
  743. delay_short(1);
  744. ucTempData=ucTempData<<1;
  745. }
  746. ucTempData=ucLedStatusTemp08_01;//再先送低8位
  747. for(i=0;i<8;i++)
  748. {
  749. if(ucTempData>=0x80)hc595_ds_dr=1;
  750. else hc595_ds_dr=0;
  751. hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  752. delay_short(1);
  753. hc595_sh_dr=1;
  754. delay_short(1);
  755. ucTempData=ucTempData<<1;
  756. }
  757. hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  758. delay_short(1);
  759. hc595_st_dr=1;
  760. delay_short(1);
  761. hc595_sh_dr=0; //拉低,抗干扰就增强
  762. hc595_st_dr=0;
  763. hc595_ds_dr=0;
  764. }
  765. void usart_receive(void) interrupt 4 //串口接收数据中断
  766. {
  767. if(RI==1)
  768. {
  769. RI = 0;
  770. ++uiRcregTotal;
  771. if(uiRcregTotal>const_rc_size)//超过缓冲区
  772. {
  773. uiRcregTotal=const_rc_size;
  774. }
  775. ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
  776. if(ucSendCntLock==0)//原子锁判断
  777. {
  778. ucSendCntLock=1; //加锁
  779. uiSendCnt=0;//及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  780. ucSendCntLock=0; //解锁
  781. }
  782. }
  783. else//我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  784. {
  785. TI = 0;//如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
  786. }
  787. }
  788. void T0_time(void) interrupt 1 //定时中断
  789. {
  790. TF0=0;//清除中断标志
  791. TR0=0; //关中断
  792. /* 注释一:
  793. * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  794. */
  795. if(ucSendCntLock==0)//原子锁判断
  796. {
  797. ucSendCntLock=1; //加锁
  798. if(uiSendCnt
  799. {
  800. uiSendCnt++; //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  801. ucSendLock=1; //开自锁标志
  802. }
  803. ucSendCntLock=0; //解锁
  804. }
  805. if(ucVoiceLock==0) //原子锁判断
  806. {
  807. if(uiVoiceCnt!=0)
  808. {
  809. uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  810. beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  811. }
  812. else
  813. {
  814. ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  815. beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  816. }
  817. }
  818. if(ucStatus!=0) //处于非待机的状态,Led闪烁
  819. {
  820. uiLedCnt++; //Led闪烁计时器不断累加
  821. }
  822. if(ucStatus==1) //处于正在通讯的状态,
  823. {
  824. if(ucSendTimeOutLock==0)//原子锁判断
  825. {
  826. uiSendTimeOutCnt++; //超时计时器累加
  827. if(uiSendTimeOutCnt>const_send_time_out)//超时出错
  828. {
  829. uiSendTimeOutCnt=0;
  830. ucStatus=2;//切换到出错报警状态
  831. }
  832. }
  833. }
  834. key_scan(); //按键扫描函数
  835. display_drive();//数码管字模的驱动函数
  836. TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
  837. TL0=0x0b;
  838. TR0=1;//开中断
  839. }
  840. void delay_short(unsigned int uiDelayShort)
  841. {
  842. unsigned int i;
  843. for(i=0;i
  844. {
  845. ; //一个分号相当于执行一条空语句
  846. }
  847. }
  848. void delay_long(unsigned int uiDelayLong)
  849. {
  850. unsigned int i;
  851. unsigned int j;
  852. for(i=0;i
  853. {
  854. for(j=0;j<500;j++)//内嵌循环的空指令数量
  855. {
  856. ; //一个分号相当于执行一条空语句
  857. }
  858. }
  859. }
  860. void initial_myself(void)//第一区 初始化单片机
  861. {
  862. /* 注释二:
  863. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  864. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  865. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  866. */
  867. key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  868. led_dr=1;//点亮独立LED灯
  869. beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  870. hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
  871. TMOD=0x01;//设置定时器0为工作方式1
  872. TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
  873. TL0=0x0b;
  874. //配置串口
  875. SCON=0x50;
  876. TMOD=0X21;
  877. /* 注释三:
  878. * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
  879. * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
  880. */
  881. IP =0x10;//把串口中断设置为最高优先级,必须的。
  882. TH1=TL1=-(11059200L/12/32/9600);//串口波特率为9600。
  883. TR1=1;
  884. }
  885. void initial_peripheral(void) //第二区 初始化外围
  886. {
  887. ucDigDot8=0; //小数点全部不显示
  888. ucDigDot7=0;
  889. ucDigDot6=0;
  890. ucDigDot5=0;
  891. ucDigDot4=0;
  892. ucDigDot3=0;
  893. ucDigDot2=0;
  894. ucDigDot1=0;
  895. EA=1; //开总中断
  896. ES=1; //允许串口中断
  897. ET0=1; //允许定时中断
  898. TR0=1; //启动定时中断
  899. }


总结陈词:
这节详细讲了从机收发端的程序框架,而主机端的程序则用电脑的串口助手来模拟。实际上,主机端的程序也有很多内容,它包括依次发送每一串数据,根据返回的应答来决定是否需要重发数据,重发三次如果没反应则进行报错,以及超时没接收到数据等等内容。主机收发端的程序框架是什么样的?欲知详情,请听下回分解-----主机的串口收发综合程序框架


评论


技术专区

关闭