新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > 第88节:单片机靠关键字快速截取有效数据串

第88节:单片机靠关键字快速截取有效数据串

作者: 时间:2016-11-22 来源:网络 收藏
开场白:
我前面串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,有一些串口项目的协议是固定不变的,而且也不需要从机反馈任何应答信号,这类项目只需根据特定关键字来快速识别数据串是否接收完毕即可。比如现在有一种电子称,它的测量范围是0.00克到500.00克,他是靠串口不断对外发送当前重量数据的,每串数据固定长度26个字节,最后两个字节是回车换行符0x0d 0x0a,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,其它的数据可以忽略不计。这类串口框架的思路是:根据数据尾是否有0x0d 0x0a来判断数据串是否有效的,一旦发现有此关键字,再判断总的数据长度是否等于或者大于一串数据的固定长度,如果满足,则把相关标志位置位,通知主函数中的串口服务程序进行处理。同时也及时关闭串口中断,避免在处理串口数据期间受到串口数据的中断干扰,等串口服务程序处理完毕再打开。
具体内容,请看源代码讲解。
(1) 硬件平台:
基于朱兆祺51单片机学习板
(2) 实现功能:
波特率是:9600。把当前电子称的重量数据显示在数码管上,在电脑上用串口助手软件来模拟电子称发送以下格式协议的3串数据,它的协议很简单,每串数据固定长度26个字节,最后两个字节是回车换行符0x0d 0x0a,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,其它的数据可以忽略不计。
(a)字符是:
ST,GS,+ 0.77 g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 37 37 20 2020 20 20 67 0D 0A
数码管显示:0.77
(b)
字符是:
ST,GS,+ 136.39 g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 31 33 36 2E 33 39 20 2020 20 20 67 0D 0A
数码管显示:136.39
(c)
字符是:
ST,GS,+ 0.00 g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 30 30 20 2020 20 20 67 0D 0A
数码管显示:0.00

(3)源代码讲解如下:
  1. #include "REG52.H"
  2. #define const_rc_size36//接收串口中断数据的缓冲区数组大小
  3. #define const_least_size 26 //一串标准数据的大小
  4. void initial_myself();
  5. void initial_peripheral();
  6. void delay_short(unsigned int uiDelayShort);
  7. void delay_long(unsigned int uiDelaylong);
  8. //驱动数码管的74HC595
  9. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  10. void display_drive(); //显示数码管字模的驱动函数
  11. void display_service(); //显示的窗口菜单服务程序
  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  14. void usart_service(void);//串口接收服务程序,在main函数里
  15. void usart_receive(void); //串口接收中断函数
  16. void T0_time();//定时中断函数
  17. sbit dig_hc595_sh_dr=P2^0; //数码管的74HC595程序
  18. sbit dig_hc595_st_dr=P2^1;
  19. sbit dig_hc595_ds_dr=P2^2;
  20. sbit hc595_sh_dr=P2^3; //LED灯的74HC595程序
  21. sbit hc595_st_dr=P2^4;
  22. sbit hc595_ds_dr=P2^5;
  23. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  24. sbit led_dr=P3^5;//独立LED灯
  25. //根据原理图得出的共阴数码管字模表
  26. code unsigned char dig_table[]=
  27. {
  28. 0x3f,//0 序号0
  29. 0x06,//1 序号1
  30. 0x5b,//2 序号2
  31. 0x4f,//3 序号3
  32. 0x66,//4 序号4
  33. 0x6d,//5 序号5
  34. 0x7d,//6 序号6
  35. 0x07,//7 序号7
  36. 0x7f,//8 序号8
  37. 0x6f,//9 序号9
  38. 0x00,//无 序号10
  39. 0x40,//- 序号11
  40. 0x73,//P 序号12
  41. };
  42. unsigned intuiRcregTotal=0;//代表当前缓冲区已经接收了多少个数据
  43. unsigned intuiRcregTotalTemp=0;//代表当前缓冲区已经接收了多少个数据的中间变量
  44. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  45. unsigned char ucReceiveFlag=0; //接收成功标志
  46. unsigned char ucDigShow8;//第8位数码管要显示的内容
  47. unsigned char ucDigShow7;//第7位数码管要显示的内容
  48. unsigned char ucDigShow6;//第6位数码管要显示的内容
  49. unsigned char ucDigShow5;//第5位数码管要显示的内容
  50. unsigned char ucDigShow4;//第4位数码管要显示的内容
  51. unsigned char ucDigShow3;//第3位数码管要显示的内容
  52. unsigned char ucDigShow2;//第2位数码管要显示的内容
  53. unsigned char ucDigShow1;//第1位数码管要显示的内容
  54. unsigned char ucDigDot8;//数码管8的小数点是否显示的标志
  55. unsigned char ucDigDot7;//数码管7的小数点是否显示的标志
  56. unsigned char ucDigDot6;//数码管6的小数点是否显示的标志
  57. unsigned char ucDigDot5;//数码管5的小数点是否显示的标志
  58. unsigned char ucDigDot4;//数码管4的小数点是否显示的标志
  59. unsigned char ucDigDot3;//数码管3的小数点是否显示的标志
  60. unsigned char ucDigDot2;//数码管2的小数点是否显示的标志
  61. unsigned char ucDigDot1;//数码管1的小数点是否显示的标志
  62. unsigned char ucDigShowTemp=0; //临时中间变量
  63. unsigned char ucDisplayDriveStep=1;//动态扫描数码管的步骤变量
  64. unsigned char ucWd1Part1Update=1; //8位数码管更新显示标志
  65. unsigned long ulWeightCurrent=12345; //显示当前实际的重量
  66. void main()
  67. {
  68. initial_myself();
  69. delay_long(100);
  70. initial_peripheral();
  71. while(1)
  72. {
  73. usart_service();//串口接收服务程序
  74. display_service(); //显示的窗口菜单服务程序
  75. }
  76. }
  77. /* 注释一:
  78. * 本节内容处理串口数据是根据数据尾是否有0x0d 0x0a来判断数据串是否有效的,一旦发现有此关键字,
  79. * 再判断总的数据长度是否等于或者大于一串数据的固定长度,如果满足,则把相关标志位置位,通知主函数中
  80. * 的串口服务程序进行处理。同时也及时关闭串口中断,避免在处理串口数据期间受到串口数据的中断干扰,
  81. * 等串口服务程序处理完毕再打开。
  82. */
  83. void usart_receive(void) interrupt 4 //串口接收数据中断函数
  84. {
  85. if(RI==1)
  86. {
  87. RI = 0;
  88. ++uiRcregTotal;
  89. ucRcregBuf[uiRcregTotal-1]=SBUF; //将串口接收到的数据缓存到接收缓冲区里
  90. if(uiRcregTotal>=2&&ucRcregBuf[uiRcregTotal-2]==0x0d&&ucRcregBuf[uiRcregTotal-1]==0x0a)//一旦发现后缀是0x0d 0x0a关键字的就进去处理判断
  91. {
  92. if(uiRcregTotal
  93. {
  94. uiRcregTotal=0;
  95. }
  96. else
  97. {
  98. uiRcregTotalTemp=uiRcregTotal; //把接收到的总数据传递给一个中间变量,在主函数那边处理这个中间变量
  99. ucReceiveFlag=1; //通知主程序接收成功
  100. ES=0; // 禁止接收中断,等主函数处理完接收的数据后再打开串口中断,避免在处理串口数据期间受到串口数据的中断干扰。
  101. }
  102. }
  103. else if(uiRcregTotal>=const_rc_size)//超过缓冲区
  104. {
  105. uiRcregTotal=0;
  106. }
  107. }
  108. else //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
  109. {
  110. TI = 0;
  111. }
  112. }
  113. void usart_service(void)//串口接收服务程序,在main函数里
  114. {
  115. //加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样有可能减少了一点指令消耗的时间。
  116. static unsigned long ulReceiveData10000; //定义成long类型,是为了方便后面换算的乘法运算,让它不会溢出而出错。
  117. static unsigned long ulReceiveData1000;
  118. static unsigned long ulReceiveData100;
  119. static unsigned long ulReceiveData10;
  120. static unsigned long ulReceiveData1;
  121. if(ucReceiveFlag==1)//说明有数据接收成功,进入数据处理分析
  122. {
  123. ulReceiveData10000=0;
  124. ulReceiveData1000=0;
  125. ulReceiveData100=0;
  126. ulReceiveData10=0;
  127. ulReceiveData1=0;
  128. /* 注释二:
  129. * 根据协议,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,因此省略不写。
  130. */
  131. if(ucRcregBuf[uiRcregTotalTemp-9]>=0x30)
  132. {
  133. ulReceiveData1=ucRcregBuf[uiRcregTotalTemp-9]-0x30; //接收到的ASCII码数字减去0x30变成实际数值.
  134. }
  135. if(ucRcregBuf[uiRcregTotalTemp-10]>=0x30)
  136. {
  137. ulReceiveData10=ucRcregBuf[uiRcregTotalTemp-10]-0x30;
  138. ulReceiveData10=ulReceiveData10*10;
  139. }
  140. if(ucRcregBuf[uiRcregTotalTemp-12]>=0x30)
  141. {
  142. ulReceiveData100=ucRcregBuf[uiRcregTotalTemp-12]-0x30;
  143. ulReceiveData100=ulReceiveData100*100;
  144. }
  145. if(ucRcregBuf[uiRcregTotalTemp-13]>=0x30)
  146. {
  147. ulReceiveData1000=ucRcregBuf[uiRcregTotalTemp-13]-0x30;
  148. ulReceiveData1000=ulReceiveData1000*1000;
  149. }
  150. if(ucRcregBuf[uiRcregTotalTemp-14]>=0x30)
  151. {
  152. ulReceiveData10000=ucRcregBuf[uiRcregTotalTemp-14]-0x30;
  153. ulReceiveData10000=ulReceiveData10000*10000;
  154. }
  155. ulWeightCurrent=ulReceiveData10000+ulReceiveData1000+ulReceiveData100+ulReceiveData10+ulReceiveData1;
  156. ucWd1Part1Update=1; //更新显示
  157. uiRcregTotalTemp=0;//清零实际接收到的字节数的中间变量
  158. uiRcregTotal=0;//清零实际接收到的字节数
  159. ucReceiveFlag=0;//清零完成标志
  160. ES = 1; // 允许接收中断
  161. }
  162. }
  163. void display_service() //显示的窗口菜单服务程序
  164. {
  165. //加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样有可能减少了一点指令消耗的时间。
  166. static unsigned char ucTemp5; //中间过渡变量
  167. static unsigned char ucTemp4; //中间过渡变量
  168. static unsigned char ucTemp3; //中间过渡变量
  169. static unsigned char ucTemp2; //中间过渡变量
  170. static unsigned char ucTemp1; //中间过渡变量
  171. if(ucWd1Part1Update==1)//更新显示
  172. {
  173. ucWd1Part1Update=0;//及时清零标志,避免一直进来扫描
  174. //先分解数据用来显示每一位
  175. ucTemp5=ulWeightCurrent%100000/10000;
  176. ucTemp4=ulWeightCurrent%10000/1000;
  177. ucTemp3=ulWeightCurrent%1000/100;
  178. ucTemp2=ulWeightCurrent%100/10;
  179. ucTemp1=ulWeightCurrent%10;
  180. ucDigDot3=1;//显示第3位数码管的小数点,实际数据带2位小数点。
  181. ucDigShow8=10;//没有用到第8位数码管,因此显示无。10代表显示空。
  182. ucDigShow7=10;//没有用到第7位数码管,因此显示无。10代表显示空。
  183. ucDigShow6=10;//没有用到第6位数码管,因此显示无。10代表显示空。
  184. if(ulWeightCurrent<10000)
  185. {
  186. ucDigShow5=10;//如果小于1000,千位显示无
  187. }
  188. else
  189. {
  190. ucDigShow5=ucTemp5;//第5位数码管要显示的内容
  191. }
  192. if(ulWeightCurrent<1000)
  193. {
  194. ucDigShow4=10;//如果小于1000,千位显示无
  195. }
  196. else
  197. {
  198. ucDigShow4=ucTemp4;//第4位数码管要显示的内容
  199. }
  200. //因为带2位小数点,因此最前面3位数据都是有效数,必然要显示,不要判断去0的空显示处理。
  201. ucDigShow3=ucTemp3;//第3位数码管要显示的内容
  202. ucDigShow2=ucTemp2;//第2位数码管要显示的内容
  203. ucDigShow1=ucTemp1;//第1位数码管要显示的内容
  204. }
  205. }
  206. void display_drive()
  207. {
  208. //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  209. switch(ucDisplayDriveStep)
  210. {
  211. case 1://显示第1位
  212. ucDigShowTemp=dig_table[ucDigShow1];
  213. if(ucDigDot1==1)
  214. {
  215. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  216. }
  217. dig_hc595_drive(ucDigShowTemp,0xfe);
  218. break;
  219. case 2://显示第2位
  220. ucDigShowTemp=dig_table[ucDigShow2];
  221. if(ucDigDot2==1)
  222. {
  223. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  224. }
  225. dig_hc595_drive(ucDigShowTemp,0xfd);
  226. break;
  227. case 3://显示第3位
  228. ucDigShowTemp=dig_table[ucDigShow3];
  229. if(ucDigDot3==1)
  230. {
  231. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  232. }
  233. dig_hc595_drive(ucDigShowTemp,0xfb);
  234. break;
  235. case 4://显示第4位
  236. ucDigShowTemp=dig_table[ucDigShow4];
  237. if(ucDigDot4==1)
  238. {
  239. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  240. }
  241. dig_hc595_drive(ucDigShowTemp,0xf7);
  242. break;
  243. case 5://显示第5位
  244. ucDigShowTemp=dig_table[ucDigShow5];
  245. if(ucDigDot5==1)
  246. {
  247. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  248. }
  249. dig_hc595_drive(ucDigShowTemp,0xef);
  250. break;
  251. case 6://显示第6位
  252. ucDigShowTemp=dig_table[ucDigShow6];
  253. if(ucDigDot6==1)
  254. {
  255. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  256. }
  257. dig_hc595_drive(ucDigShowTemp,0xdf);
  258. break;
  259. case 7://显示第7位
  260. ucDigShowTemp=dig_table[ucDigShow7];
  261. if(ucDigDot7==1)
  262. {
  263. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  264. }
  265. dig_hc595_drive(ucDigShowTemp,0xbf);
  266. break;
  267. case 8://显示第8位
  268. ucDigShowTemp=dig_table[ucDigShow8];
  269. if(ucDigDot8==1)
  270. {
  271. ucDigShowTemp=ucDigShowTemp|0x80;//显示小数点
  272. }
  273. dig_hc595_drive(ucDigShowTemp,0x7f);
  274. break;
  275. }
  276. ucDisplayDriveStep++;
  277. if(ucDisplayDriveStep>8)//扫描完8个数码管后,重新从第一个开始扫描
  278. {
  279. ucDisplayDriveStep=1;
  280. }
  281. }
  282. //数码管的74HC595驱动函数
  283. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  284. {
  285. unsigned char i;
  286. unsigned char ucTempData;
  287. dig_hc595_sh_dr=0;
  288. dig_hc595_st_dr=0;
  289. ucTempData=ucDigStatusTemp16_09;//先送高8位
  290. for(i=0;i<8;i++)
  291. {
  292. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  293. else dig_hc595_ds_dr=0;
  294. dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  295. delay_short(1);
  296. dig_hc595_sh_dr=1;
  297. delay_short(1);
  298. ucTempData=ucTempData<<1;
  299. }
  300. ucTempData=ucDigStatusTemp08_01;//再先送低8位
  301. for(i=0;i<8;i++)
  302. {
  303. if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  304. else dig_hc595_ds_dr=0;
  305. dig_hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  306. delay_short(1);
  307. dig_hc595_sh_dr=1;
  308. delay_short(1);
  309. ucTempData=ucTempData<<1;
  310. }
  311. dig_hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  312. delay_short(1);
  313. dig_hc595_st_dr=1;
  314. delay_short(1);
  315. dig_hc595_sh_dr=0; //拉低,抗干扰就增强
  316. dig_hc595_st_dr=0;
  317. dig_hc595_ds_dr=0;
  318. }
  319. //LED灯的74HC595驱动函数
  320. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  321. {
  322. unsigned char i;
  323. unsigned char ucTempData;
  324. hc595_sh_dr=0;
  325. hc595_st_dr=0;
  326. ucTempData=ucLedStatusTemp16_09;//先送高8位
  327. for(i=0;i<8;i++)
  328. {
  329. if(ucTempData>=0x80)hc595_ds_dr=1;
  330. else hc595_ds_dr=0;
  331. hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  332. delay_short(1);
  333. hc595_sh_dr=1;
  334. delay_short(1);
  335. ucTempData=ucTempData<<1;
  336. }
  337. ucTempData=ucLedStatusTemp08_01;//再先送低8位
  338. for(i=0;i<8;i++)
  339. {
  340. if(ucTempData>=0x80)hc595_ds_dr=1;
  341. else hc595_ds_dr=0;
  342. hc595_sh_dr=0; //SH引脚的上升沿把数据送入寄存器
  343. delay_short(1);
  344. hc595_sh_dr=1;
  345. delay_short(1);
  346. ucTempData=ucTempData<<1;
  347. }
  348. hc595_st_dr=0;//ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  349. delay_short(1);
  350. hc595_st_dr=1;
  351. delay_short(1);
  352. hc595_sh_dr=0; //拉低,抗干扰就增强
  353. hc595_st_dr=0;
  354. hc595_ds_dr=0;
  355. }
  356. void T0_time() interrupt 1
  357. {
  358. TF0=0;//清除中断标志
  359. TR0=0; //关中断
  360. display_drive();//数码管字模的驱动函数
  361. TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
  362. TL0=0x0b;
  363. TR0=1;//开中断
  364. }
  365. void delay_short(unsigned int uiDelayShort)
  366. {
  367. unsigned int i;
  368. for(i=0;i
  369. {
  370. ; //一个分号相当于执行一条空语句
  371. }
  372. }
  373. void delay_long(unsigned int uiDelayLong)
  374. {
  375. unsigned int i;
  376. unsigned int j;
  377. for(i=0;i
  378. {
  379. for(j=0;j<500;j++)//内嵌循环的空指令数量
  380. {
  381. ; //一个分号相当于执行一条空语句
  382. }
  383. }
  384. }
  385. void initial_myself()//第一区 初始化单片机
  386. {
  387. beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  388. led_dr=0;//关闭独立LED灯
  389. hc595_drive(0x00,0x00);//关闭所有经过另外两个74HC595驱动的LED灯
  390. TMOD=0x01;//设置定时器0为工作方式1
  391. TH0=0xfe; //重装初始值(65535-500)=65035=0xfe0b
  392. TL0=0x0b;
  393. //配置串口
  394. SCON=0x50;
  395. TMOD=0X21;
  396. /* 注释三:
  397. * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
  398. * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
  399. */
  400. IP =0x10;//把串口中断设置为最高优先级,必须的。
  401. TH1=TL1=-(11059200L/12/32/9600);//串口波特率为9600。
  402. TR1=1;
  403. }
  404. void initial_peripheral() //第二区 初始化外围
  405. {
  406. ucDigDot8=0; //初始化小数点全部不显示
  407. ucDigDot7=0;
  408. ucDigDot6=0;
  409. ucDigDot5=0;
  410. ucDigDot4=0;
  411. ucDigDot3=0;
  412. ucDigDot2=0;
  413. ucDigDot1=0;
  414. EA=1; //开总中断
  415. ES=1; //允许串口中断
  416. ET0=1; //允许定时中断
  417. TR0=1; //启动定时中断
  418. }
总结陈词:
前面我在第48节里讲过用ds1302做的时钟程序,但是后来很多网友建议,为了方便初学者学习编程思路,我应该用单片机定时器做一个时钟程序。因此,我决定下一节讲这方面的内容。欲知详情,请听下回分解----用单片机内部定时器做一个时钟。


评论


技术专区

关闭