新闻中心

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

第45节:主机的串口收发综合程序框架

作者: 时间:2016-11-22 来源:网络 收藏
开场白:
在大部分的项目中,串口都需要“一收一应答”的握手协议,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
上一节已经讲了从机,这节就讲主机的收发端程序实例。要教会大家四个知识点:

第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置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.
通讯协议:EB00 55GG 00 02 XX XXCY
其中第1,2,3位EB00 55就是数据头
其中第4位GG就是数据类型。01代表更改参数1,02代表更改参数2,03代表更改参数3,04代表更改参数4,
其中第5,6位0002就是有效数据长度。高位在左,低位在右。
其中从第7,8位XXXX是被更改的参数。高位在左,低位在右。
第9位CY是累加和,前面所有字节的累加。
一个完整的通讯必须发送完4串数据,每串数据之间的间隔时间不能超过10秒钟,否则认为通讯超时主机会重发数据,如果连续三次都没有返回,则引发蜂鸣器报警。如果接收到得数据校验正确,主机继续发送新的一串数据,直到把4串数据发送完毕为止。

系统处于待机状态时,LED灯一直亮,
系统处于非待机状态时,LED灯闪烁,
系统处于出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。

通过电脑的串口助手来模拟从机,返回不同的应答
从机返回校验正确应答:eb 00 55 f5 00 00 35
从机返回校验出错应答:eb00 55 fa 00 00 3a

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


总结陈词:
前面花了大量篇幅详细地讲解了串口收发数据的程序框架,从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。


评论


技术专区

关闭