最近做的專案採用的主控晶片是8位元的MCU,在但是在寫下3行程式碼後,就出現了無法解決的Bug,而且導師告訴我,之前去華為的那個工程師當時也遇到過這個問題,具體是怎麼解決的他也不清楚,好吧,只能靠自己了。首先告訴你們,初始化設定是沒有任何問題的,但是就這3行程式碼的Bug讓我找了一星期。廢話不多說,下面是程式碼。
unsigned short int time_count = 0;
int main()
{
tim0_init();/* 5ms定時器初始化 */
uart1_init();/* 串列埠1初始化 */
printf("uart init ok!\r\n");
while(1)
{
if(time_count >= 400)
{
time_count = 0;
printf("time is end!\r\n");/* 時間到了 */
}
}
}
void tim0_isr() interrupt 1/* 5ms定時器中斷 */
{
time_count++;
}
程式碼十分簡單,定義了個16位元的變數,在5Ms的定時器中斷中自加,當這個變數大於等於400,即2S時,通過串列埠列印輸出時間到了。
但是,但是,請注意,在實際測試時發現,偶爾,是偶爾,沒有規律,這個數到了256後就列印輸出時間到了,而且這個出現的次數是隨機的,沒有任何規律。
我瞬間懵了,這個程式碼有問題?學了這麼多年C餵了狗了?一剎那我都恍惚了,於是向別人請教,他人經過一番思索,折騰了一上午,最後也沒辦法解決。上網百度更是搜尋不到,但是搜尋到不少 「你相信玄學嗎?」,我可是一個崇尚科學的三好少年,我就不相信真的是所謂的玄學。我相信只要學的深,就能解決。
在經過不斷的思索以及上網查詢資料,後來和廠家聯絡,最後終於找到了問題所在。
因為使用的是8位元的微控制器,8位元,顧名思義,一個指令週期CPU只能處理8位元資料,在程式碼中我定義的是16位元的變數,也就是說在對這個變數進行操作時,需要兩個指令週期,但是每一個指令週期結束後,cpu都會判斷有沒有中斷產生,從而去響應中斷事件。
問題就出在這裡,如果在操作這個16位元變數時,恰巧有中斷產生,就可能會有問題。好,明確了問題的方向,找起來就比較簡單了。在主函數中,採用if條件判斷,判斷 count_time >= 400;一般編譯器對if條件判斷基本是採用作差的形式,即判斷 count_time - 400 是否大於0,條件為真,就成立。由於變數是16位元,8位元的CPU需要兩個指令週期,即兩次才能判斷出來。
第一次,用變數 count_time 的低8位元 - 400的低8位元,400的低8位元是 (0b10010000)。如果這時候剛好定時器中斷來了,那麼去響應中斷事件,對 count_time這個變數自加,假如中斷來之前,count_time這個變數的值為255,即(0b11111111),那麼進入中斷後,這個數就變為256,即(0b00000001 00000000),產生了高位進位。中斷處理結束後,繼續回去處理主函數,由於上一次判斷了低8位元,這次判斷高8位元,由於產生了進位,所以變數的高8位元是1,恰巧400的高8位元也是1,然後就判斷這兩個數相等,這個if條件就成立了。
這裡可能有小夥伴會問,為什麼要先判斷低8位元呢,如果先判斷高8位元,不就不會出現這個問題了嗎,這個就和編譯器有關係了,因為51微控制器採用的儲存模式是大端模式,即高位資料存放在高位地址。當從這個變數的起始地址取資料時,優先取到的是低地址的資料,即變數的低位元組,因此會優先判斷低8位元。
問題到此已經找到了,要解決的話就比較容易了,程式碼如下:
int main()
{
unsigned short int time = 0;
tim0_init();/* 5ms定時器初始化 */
uart1_init();/* 串列埠1初始化 */
printf("uart init ok!\r\n");
while(1)
{
time = time_count;
if(time == time_count)
{
if(time >= 400)
{
time_count = 0;
printf("time is end!\r\n");/* 時間到了 */
}
}
}
}
這樣就可以了,先把這個值賦給一個臨時變數,然後判斷這兩個值是否相等,判斷的目的就是為了防止在賦值後出現進位的情況,如果出現了,那麼這兩個數就不相等,條件不成立,等待下一次迴圈到來時,就一定會相等,因為定時器中斷剛剛發生,不會立馬再進中斷,所以這樣就可以解決時間未到2s就進去的情況發生。
到此為止,問題解決。所以,還是要相信科學,我們口中所謂的玄學不過是無知罷了,哈哈。
如有錯誤之處,還請及時指正,在此謝過!