原创 一个让人头痛而又有趣的软件bug

2020-6-11 22:56 2796 55 12 分类: MCU/ 嵌入式 文集: C语言
这是我前几天遇到的bug,RS485协议程序我已经做过无数次了,有无数个成功的先例。当开始做这个小程序的时候,我认为对我而言这只是小菜一碟,我挥挥手就可以把它搞定。
但是事情出乎我的意料!程序工作不正常。
先把有bug的代码贴出来:(代码很长,如果看起来眼睛脑壳痛,那你就直接跳过源代码,看后面的文字说明就好了,贴出代码是为了让你能感受到debug有时候是一件让人多么痛苦的事情)
  1. void RS485_Poll(void)
  2. {
  3. uint8_t byte;
  4. static uint8_t index = 0,len = 0;
  5. static uint16_t check;
  6. uint8_t i;
  7. if(!BSP_RxFifoOut(&byte))
  8. {
  9. if(s_u8BusIdleFlag == 1)
  10. {
  11. if((s_u8RetryCount > 0) && (s_u16DelaySendPack_Timer_1ms == 0))
  12. {
  13. s_u8CollisionDetecting = 1;
  14. s_u8DataVerifyIndex = 0;
  15. len = s_u8OutPack[PROTOCOL_STATE_LEN] + 2;
  16. for(i = 0;i < len;i++)
  17. {
  18. BSP_USART1Tx(s_u8OutPack[i]);
  19. }
  20. s_u8BusIdleFlag = 0;
  21. s_u8Tick_64us = 32;
  22. }
  23. }
  24. }
  25. else
  26. {
  27. s_u8BusIdleFlag = 0;
  28. s_u8Tick_64us = 32;
  29. if(s_u8CollisionDetecting == 1)
  30. {
  31. if(byte == s_u8OutPack[s_u8DataVerifyIndex++])
  32. {
  33. if(s_u8DataVerifyIndex == s_u8OutPack[PROTOCOL_STATE_LEN] + 2)
  34. {
  35. s_u8CollisionDetecting = 0;
  36. s_u8RetryCount = 0;
  37. }
  38. }
  39. else
  40. {
  41. s_u8CollisionDetecting = 0;
  42. if(s_u8RetryCount > 0)
  43. {
  44. s_u8RetryCount--;
  45. s_u16DelaySendPack_Timer_1ms = GetPeseudoRandom();
  46. }
  47. }
  48. }
  49. else
  50. {
  51. switch(s_ProtocolState)
  52. {
  53. case PROTOCOL_STATE_HEAD0:
  54. if(byte == 0xAA)
  55. {
  56. s_ProtocolState = PROTOCOL_STATE_HEAD1;
  57. s_u8Reset_Timer_1ms = 0;
  58. index = 0;
  59. s_u8InPack[index++] = byte;
  60. }
  61. break;
  62. case PROTOCOL_STATE_HEAD1:
  63. if(byte == 0xAA)
  64. {
  65. s_ProtocolState = PROTOCOL_STATE_LEN;
  66. s_u8InPack[index++] = byte;
  67. }
  68. else
  69. {
  70. s_ProtocolState = PROTOCOL_STATE_HEAD0;
  71. }
  72. break;
  73. case PROTOCOL_STATE_LEN:
  74. s_u8InPack[index++] = byte;
  75. len = byte;
  76. if((len <= (MAX_PACK_LEN - 2))
  77. && (len >= (MIN_PACK_LEN - 2)))
  78. {
  79. s_ProtocolState = PROTOCOL_STATE_SRC_NET;
  80. }
  81. else
  82. {
  83. s_ProtocolState = PROTOCOL_STATE_HEAD0;
  84. }
  85. break;
  86. case PROTOCOL_STATE_SRC_NET:
  87. s_u8InPack[index++] = byte;
  88. s_u8SrcNet = byte;
  89. s_ProtocolState = PROTOCOL_STATE_SRC_DEV;
  90. break;
  91. case PROTOCOL_STATE_SRC_DEV:
  92. s_u8InPack[index++] = byte;
  93. s_u8SrcDev = byte;
  94. s_ProtocolState = PROTOCOL_STATE_SRC_TYPE_H;
  95. break;
  96. case PROTOCOL_STATE_SRC_TYPE_H:
  97. s_u8InPack[index++] = byte;
  98. s_u16SrcType = ((uint16_t)byte)<<8;
  99. s_ProtocolState = PROTOCOL_STATE_SRC_TYPE_L;
  100. break;
  101. case PROTOCOL_STATE_SRC_TYPE_L:
  102. s_u8InPack[index++] = byte;
  103. s_u16SrcType |= byte;
  104. s_ProtocolState = PROTOCOL_STATE_CMD_H;
  105. break;
  106. case PROTOCOL_STATE_CMD_H:
  107. s_u8InPack[index++] = byte;
  108. s_u16Command = ((uint16_t)byte)<<8;
  109. s_ProtocolState = PROTOCOL_STATE_CMD_L;
  110. break;
  111. case PROTOCOL_STATE_CMD_L:
  112. s_u8InPack[index++] |= byte;
  113. s_u16Command |= byte;
  114. s_ProtocolState = PROTOCOL_STATE_DST_NET;
  115. break;
  116. case PROTOCOL_STATE_DST_NET:
  117. s_u8InPack[index++] = byte;
  118. s_u8DstNet = byte;
  119. s_ProtocolState = PROTOCOL_STATE_DST_DEV;
  120. break;
  121. case PROTOCOL_STATE_DST_DEV:
  122. s_u8InPack[index++] = byte;
  123. s_u8DstDev = byte;
  124. if(len > MIN_PACK_LEN)
  125. {
  126. s_ProtocolState = PROTOCOL_STATE_DAT;
  127. }
  128. else
  129. {
  130. s_ProtocolState = PROTOCOL_STATE_CRC_H;
  131. }
  132. break;
  133. case PROTOCOL_STATE_DAT:
  134. s_u8InPack[index++] = byte;
  135. if(index >= len)
  136. {
  137. s_ProtocolState = PROTOCOL_STATE_CRC_H;
  138. }
  139. break;
  140. case PROTOCOL_STATE_CRC_H:
  141. s_u8InPack[index++] = byte;
  142. check = ((uint16_t)byte)<<8;
  143. s_ProtocolState = PROTOCOL_STATE_CRC_L;
  144. break;
  145. case PROTOCOL_STATE_CRC_L:
  146. s_u8InPack[index++] = byte;
  147. check |= byte;
  148. if(check == CalcCRC16(s_u8InPack + 2,len - 2))
  149. {
  150. CommandHandle();
  151. }
  152. s_ProtocolState = PROTOCOL_STATE_HEAD0;
  153. break;
  154. default:
  155. break;
  156. }
  157. }
  158. }
  159. }
  160. static void CommandHandle(void)
  161. {
  162. if(((s_u8DstNet == s_u8LocalNet) && (s_u8DstDev == s_u8LocalDev))
  163. || ((s_u8DstNet == 0xff) && (s_u8DstDev == 0xff)))
  164. {
  165. if(s_u16Command == 0xE3E0)
  166. {
  167. if((((s_u8InPack[PROTOCOL_STATE_DAT + 2] == 0) || (s_u8InPack[PROTOCOL_STATE_LEN] == 13))
  168. &&(s_u8InPack[PROTOCOL_STATE_DAT] < 0x11))
  169. || (s_u8InPack[PROTOCOL_STATE_DAT + 2] == 1))
  170. {
  171. if(s_u8InPack[PROTOCOL_STATE_DAT + 1] == 0)
  172. {
  173. Message_FifoIn(MSG_SERIAL_STOP);
  174. }
  175. else if(s_u8InPack[PROTOCOL_STATE_DAT + 1] == 1)
  176. {
  177. Message_FifoIn(MSG_SERIAL_OPEN);
  178. }
  179. else if(s_u8InPack[PROTOCOL_STATE_DAT + 1] == 2)
  180. {
  181. Message_FifoIn(MSG_SERIAL_CLOSE);
  182. }
  183. }
  184. else if((((s_u8InPack[PROTOCOL_STATE_DAT + 2] == 0) || (s_u8InPack[PROTOCOL_STATE_LEN] == 13))
  185. &&(s_u8InPack[PROTOCOL_STATE_DAT] >= 0x11))
  186. || (s_u8InPack[PROTOCOL_STATE_DAT + 2] == 2))
  187. {
  188. if(s_u8InPack[PROTOCOL_STATE_DAT + 1] <= 0)
  189. {
  190. s_u8TargetPercent = s_u8InPack[PROTOCOL_STATE_DAT + 1];
  191. Message_FifoIn(MSG_SERIAL_TP);
  192. }
  193. }
  194. if(!((s_u8LocalNet == 0xff) && (s_u8LocalDev == 0xff)))
  195. {
  196. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xE3;
  197. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0xE1; // 注释掉此行后,可以连续接收命令
  198. BuildInPack();
  199. SendInPack();
  200. }
  201. }
  202. else if(s_u16Command == 0xE3E2)
  203. {
  204. if((((s_u8InPack[PROTOCOL_STATE_DAT + 2] == 0) || (s_u8InPack[PROTOCOL_STATE_LEN] == 13))
  205. &&(s_u8InPack[PROTOCOL_STATE_DAT] < 0x11))
  206. || (s_u8InPack[PROTOCOL_STATE_DAT + 2] == 1))
  207. {
  208. s_u8InPack[PROTOCOL_STATE_DAT + 1] = GetCurtainControlState();
  209. }
  210. else if((((s_u8InPack[PROTOCOL_STATE_DAT + 2] == 0) || (s_u8InPack[PROTOCOL_STATE_LEN] == 13))
  211. &&(s_u8InPack[PROTOCOL_STATE_DAT] >= 0x11))
  212. || (s_u8InPack[PROTOCOL_STATE_DAT + 2] == 2))
  213. {
  214. s_u8InPack[PROTOCOL_STATE_DAT + 1] = Curtain_GetPercent();
  215. }
  216. if(!((s_u8LocalNet == 0xff) && (s_u8LocalDev == 0xff)))
  217. {
  218. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xE3;
  219. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0xE3;
  220. BuildInPack();
  221. SendInPack();
  222. }
  223. }
  224. else if(s_u16Command == 0xF001)
  225. {
  226. if(CurtainController_IsProgState())
  227. {
  228. uint8_t i;
  229. for(i = 0;i < 8;i++)
  230. {
  231. s_u8Mac[i] = s_u8InPack[PROTOCOL_STATE_DAT + i];
  232. eeprom_write_byte(EEPROM_HDL_MAC + i,s_u8Mac[i]);
  233. }
  234. s_u8InPack[PROTOCOL_STATE_DAT] = 0xF8;
  235. Message_FifoIn(MSG_SERIAL_LEAVE_LEARN);
  236. }
  237. else
  238. {
  239. s_u8InPack[PROTOCOL_STATE_DAT] = 0xF5;
  240. }
  241. s_u8InPack[PROTOCOL_STATE_LEN] = 12;
  242. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xF0;
  243. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0x02;
  244. BuildInPack();
  245. SendInPack();
  246. }
  247. else if(s_u16Command == 0xF003)
  248. {
  249. uint8_t i;
  250. for(i = 0;i < 8;i++)
  251. {
  252. s_u8InPack[PROTOCOL_STATE_DAT + i] = s_u8Mac[i];
  253. }
  254. s_u8InPack[PROTOCOL_STATE_DAT + i++] = s_u8LocalNet;
  255. s_u8InPack[PROTOCOL_STATE_DAT + i++] = s_u8LocalDev;
  256. s_u8InPack[PROTOCOL_STATE_DAT + i++] = 0x02;
  257. s_u8InPack[PROTOCOL_STATE_DAT + i++] = 0xC1;
  258. s_u8InPack[PROTOCOL_STATE_LEN] = 23;
  259. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xF0;
  260. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0x04;
  261. BuildInPack();
  262. SendInPack();
  263. }
  264. else if(s_u16Command == 0x000E)
  265. {
  266. uint8_t i;
  267. for(i = 0;i < 20;i++)
  268. {
  269. s_u8InPack[PROTOCOL_STATE_DAT + i] = s_u8Remark[i];
  270. }
  271. s_u8InPack[PROTOCOL_STATE_LEN] = 31;
  272. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0x00;
  273. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0x0F;
  274. BuildInPack();
  275. SendInPack();
  276. }
  277. else if(s_u16Command == 0x0010)
  278. {
  279. if((s_u8LocalNet == s_u8InPack[PROTOCOL_STATE_DST_NET]) && (s_u8LocalDev == s_u8InPack[PROTOCOL_STATE_DST_DEV]))
  280. {
  281. uint8_t i;
  282. for(i = 0;i < 20;i++)
  283. {
  284. s_u8Remark[i] = s_u8InPack[PROTOCOL_STATE_DAT + i];
  285. eeprom_write_byte(EEPROM_HDL_REMARK + i,s_u8Mac[i]);
  286. }
  287. s_u8InPack[PROTOCOL_STATE_LEN] = 12;
  288. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0x00;
  289. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0x11;
  290. BuildInPack();
  291. SendInPack();
  292. }
  293. }
  294. else if(s_u16Command == 0xE5F5)
  295. {
  296. if(CurtainController_IsProgState())
  297. {
  298. s_u8InPack[PROTOCOL_STATE_DAT + 0] = s_u8LocalNet;
  299. s_u8InPack[PROTOCOL_STATE_DAT + 1] = s_u8LocalDev;
  300. s_u8InPack[PROTOCOL_STATE_LEN] = 13;
  301. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xE5;
  302. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0xF6;
  303. BuildInPack();
  304. SendInPack();
  305. Message_FifoIn(MSG_SERIAL_LEAVE_LEARN);
  306. }
  307. }
  308. else if(s_u16Command == 0xE5F7)
  309. {
  310. if(CurtainController_IsProgState())
  311. {
  312. s_u8LocalNet = s_u8InPack[PROTOCOL_STATE_DAT + 0];
  313. s_u8LocalDev = s_u8InPack[PROTOCOL_STATE_DAT + 1];
  314. eeprom_write_byte(EEPROM_HDL_NET,s_u8LocalNet);
  315. eeprom_write_byte(EEPROM_HDL_DEV,s_u8LocalDev);
  316. s_u8InPack[PROTOCOL_STATE_LEN] = 13;
  317. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xE5;
  318. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0xF8;
  319. BuildInPack();
  320. SendInPack();
  321. Message_FifoIn(MSG_SERIAL_LEAVE_LEARN);
  322. }
  323. }
  324. else if(s_u16Command == 0xE8F8)
  325. {
  326. if(CurtainController_IsProgState())
  327. {
  328. /* s_u8InPack[PROTOCOL_STATE_DAT] = 0xF8;
  329. s_u8InPack[PROTOCOL_STATE_LEN] = 12;
  330. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xE8;
  331. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0xF9;
  332. BuildInPack();
  333. SendInPack(); */
  334. eeprom_write_byte(EEPROM_PROTOCOL_TYPE,PROTOCOL_TYPE_RAEX_485);
  335. SerialProtocol_init();
  336. Message_FifoIn(MSG_SERIAL_LEAVE_LEARN);
  337. }
  338. }
  339. else if(s_u16Command == 0xFE05)
  340. {
  341. uint8_t i;
  342. uint8_t flag = 1;
  343. for(i = 0;i < 8;i++)
  344. {
  345. if(s_u8InPack[PROTOCOL_STATE_DAT + i] != s_u8Mac[i])
  346. {
  347. flag = 0;
  348. }
  349. }
  350. if(flag == 1)
  351. {
  352. s_u8InPack[PROTOCOL_STATE_LEN] = 12;
  353. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xFE;
  354. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0x06;
  355. s_u8InPack[PROTOCOL_STATE_DAT] = 0xF8;
  356. BuildInPack();
  357. SendInPack();
  358. }
  359. }
  360. else if(s_u16Command == 0xF005)
  361. {
  362. uint8_t i;
  363. uint8_t flag = 1;
  364. for(i = 0;i < 8;i++)
  365. {
  366. if(s_u8InPack[PROTOCOL_STATE_DAT + i] != s_u8Mac[i])
  367. {
  368. flag = 0;
  369. }
  370. }
  371. if(flag == 1)
  372. {
  373. s_u8LocalNet = s_u8InPack[PROTOCOL_STATE_DAT + 8];
  374. s_u8LocalDev = s_u8InPack[PROTOCOL_STATE_DAT + 9];
  375. eeprom_write_byte(EEPROM_HDL_NET,s_u8LocalNet);
  376. eeprom_write_byte(EEPROM_HDL_DEV,s_u8LocalDev);
  377. s_u8InPack[PROTOCOL_STATE_DAT] = 0xF8;
  378. s_u8InPack[PROTOCOL_STATE_LEN] = 12;
  379. s_u8InPack[PROTOCOL_STATE_CMD_H] = 0xF0;
  380. s_u8InPack[PROTOCOL_STATE_CMD_L] = 0x06;
  381. BuildInPack();
  382. SendInPack();
  383. }
  384. }
  385. }
  386. }

第一个函数void RS485_Poll(void)是用来处理数据包的,第二个函数static void CommandHandle(void)是用来处理数据包里面的命令的。
当我用串口工具向mcu发送控制命令的时候,总是第一个命令可以正常响应,随后的命令都不能正常响应。反复检查源代码,没有发现错误。这种代码我编写过无数此了,我想即使首次运行能不能通过,我也能极快找出bug,半小时之内一定能拿下这段代码的调试。好久没有遇到过让人头痛的bug了,我以为这个bug三五分钟也可以搞定。
我带着满满的信息,查看源代码。第一遍,看不出问题原因出自哪里。第二遍,更仔细的看,还是看不出来。第三、第四、第五......还是看不出来。
怎么办呢?一下子有点束手无策,我应该怎么办才能定位bug在哪里?
是不是数据包接收出错了?最有可能出错的就是crc校验把?带着这个疑问,我注释掉void RS485_Poll(void)函数中对CommandHandle()函数的调用,并且在CommandHandle()函数原来的地方插入BSP_ToggleLed()。发现数据包接收没有任何错误。
到这里,又开始觉得束手无策了!
不知道什么原因,后来我认为可以试试把应答数据包的代码注释掉再看看有没有什么线索。想到了就干,立马注释掉tatic void CommandHandle(void)中发送应答数据包对应的代码,在测试发现每次发送命令都可以正常执行了!如获至宝!我想应该离成功不远了,心情瞬间愉快了许多。
在逐行减少注释代码的函数,最后发现问题“位于”下面一行代码:
s_u8InPack[PROTOCOL_STATE_CMD_L] = 0xE1; // 注释掉此行后,可以连续接收命令
根据以往的调试经验,一旦定位了错误在哪一行,改正这一行里面的错我,debug就通过。
可是翻来覆去的看这一行代码,却看不出一丁点的疑问来!
束手无策了N久,才想到试试把接收到的数据包原封不动的返回到串口助手,看看能不能发现这行代码是如何影响程序的正确执行的。
想到就干,void RS485_Poll(void)函数中对调用函数CommandHandle()之后插入如下返回接收到的数据包的代码:
  1. for(i = 0;i < pack_len;i++)
  2. {
  3. Bsp_uart_send_byte(s_u8InPack[i]);
  4. }
发现第一次返回的数据包和串口助手发出的数据包相同,随后返回的数据包的s_u8InPack[PROTOCOL_STATE_CMD_L] 错误。
再次查看,void RS485_Poll(void)函数中写入s_u8InPack[PROTOCOL_STATE_CMD_L]那一行语句:


  1. case PROTOCOL_STATE_CMD_L:
  2. s_u8InPack[index++] |= byte;
  3. s_u16Command |= byte;
  4. s_ProtocolState = PROTOCOL_STATE_DST_NET;
  5. break;

发现代码录入出错了!正确的代码应该是“s_u8InPack[index++] = byte;”但错写成了“s_u8InPack[index++] |= byte;”
|= 与=之间的差别导致了一个初看起来让人无比困惑的bug!为了找出这个bug,我从中午11点开始,午休也不休息,直忙到下午两点。
这真的是挺折磨人的,要不是对编程有浓厚的兴趣,我想肯定无法正确找到原因。这个过程虽然折磨人,但是对困难的挑战,和挑战成功,却让人从中找到莫名的满足感。回顾整个过程,又觉得饶有趣味。
其中有一点很值得深思:如何才能避免类似的录入错误,如何才能更快速的定位类似的错误?

作者: Qeecoda, 来源:面包板社区

链接: https://mbb.eet-china.com/blog/uid-me-1099225.html

版权声明:本文为博主原创,未经本人允许,禁止转载!

文章评论14条评论)

登录后参与讨论

追忆流年寻梦少年 2020-6-30 13:34

这种问题是比较烦;
但对于解决问题、发现bug,过程可以优化,一般串口调试的第一步就是原发原收,或者是自发自收,看发送、接收情况,这样子就可以快速定位收端代码异常;然后根据收端的数据,再进一步设计发送0000,1111,1234等,根据接收数据异常分析,再进一步定位代码。
当然,这一般都是事后的总结,发现前面浪费了时间,可以优化的。

Qeecoda 2020-6-20 13:40

allen_zhan: 很难有人会犯这种&quot;录入错误&quot;吧.
这种代码移植, 工程师拿手的是 copy - paste.
很多时候,单片机代码并没有一个‘原板’可供移植。

Qeecoda 2020-6-20 13:39

allen_zhan: 很难有人会犯这种&quot;录入错误&quot;吧.
这种代码移植, 工程师拿手的是 copy - paste.
即使已经做过的代码,有时候也难免手痒会重构代码的。

allen_zhan 2020-6-19 17:10

很难有人会犯这种"录入错误"吧.
这种代码移植, 工程师拿手的是 copy - paste.

Qeecoda 2020-6-16 21:04

majunling606: 这个,折磨人了
痛并快乐着吧

Qeecoda 2020-6-16 21:02

southcreek: 有一条原则,计算机总是对的。如果出错了,一定是人为因素。
但如果计算机的设计原理使大多数的人总是出错,那么计算机的设计本身也需要改进。

Qeecoda 2020-6-16 21:00

southcreek: 有一条原则,计算机总是对的。如果出错了,一定是人为因素。
绝大多数情况是这样!

majunling606 2020-6-15 19:35

这个,折磨人了

southcreek 2020-6-14 08:44

有一条原则,计算机总是对的。如果出错了,一定是人为因素。

Qeecoda 2020-6-13 11:43

FPFA兔兔爸: 自己挖的坑,埋的就是自己;
自己埋的雷,含着泪也要挖完!
辛酸啊   
相关推荐阅读
Qeecoda 2020-05-17 16:02
电子产品常见故障现象、原因分析及改进措施(一)
1. 故障现象:通电无反应。原因分析:(1)接插件接不良;(2)470压敏电阻击穿;(3)开关电源的高压整流桥击穿;(4)开关电源的高压整理滤波电容损坏;(5)开关电源的开关管损坏;(6)开关电源的高...
Qeecoda 2020-03-28 11:11
欧姆定律的推导过程
...
Qeecoda 2020-03-23 18:18
环环紧扣,从傅里叶级数到Z变换的推导过程(六)
...
Qeecoda 2020-03-21 16:18
环环紧扣,从傅里叶级数到Z变换的推导过程(五)
...
Qeecoda 2020-03-18 20:58
环环紧扣,从傅里叶级数到Z变换的推导过程(四)
...
广告
我要评论
14
55
广告
关闭 热点推荐上一条 /5 下一条