來源:公衆號【魚鷹談單片機】
作者:魚鷹Osprey
ID :emOsprey
注意了,魚鷹這裏說的測試只是初步測試,並不是真正意義上的測試。
所謂初步測試就是,能初步達到自己寫這份代碼的目的,但是在後期長時間測試遇到的那些問題暫時先不管,所以這裏說的半小時只是排除那些邏輯錯誤,基本功能錯誤,僅此而已。不是說你花半小時就能把這份代碼進行完完全全的測試,這幾乎是不可能的,因爲每一份代碼必須長時間經過實際測試才能真正用在產品上。
而且功能也有大有小,不是所有的功能都能很快完成測試。在這篇筆記中你不應該糾結這個時間,而應該從這篇筆記中學到一些調試的技巧,這纔是魚鷹寫這篇筆記的目的。
好了,爲了在半小時內完成測試,你必須掌握以下知識:
然後說說調試環境:
1、KEIL 5.x
2、STM32F103
3、JLINK調試器
當然了,紮實的C語言基礎,好的代碼風格也是很重要的。
待測試的代碼是功能是 DMA + FIFO + 串口發送,即需要掌握 DMA + FIFO + UART + 中斷系統,這份代碼差不多花了一天時間才寫完(不是簡單的串口發送,還包含了很多東西,這裏不細說),但是完成初步測試,魚鷹只花了很少的時間,所以對於魚鷹而言,寫代碼的時間佔比最大,因爲在寫的時候,會考慮很多東西,比如多線程使用情況,中斷和線程之間的相互作用等等,如果這些東西不在編寫的過程就考慮清楚,那麼你必然會花大量的時間去排查這些錯誤,魚鷹認爲這是得不償失的。
1、忘記初始化
寫完代碼後,最常見的錯誤就是沒有調用初始化函數。
魚鷹編寫代碼的習慣是,每一個功能模塊魚鷹都會設計一個數據結構,用於管理這個模塊,當魚鷹需要查看這個功能的情況時,都會把這個數據拖到 Watch 窗口進行查看:
比如上一篇關於 IO 濾波的代碼,就有一個數據結構,而且能用枚舉的都儘量用枚舉代替了,這樣 KEIL 能幫我們識別每一個枚舉值的含義,比如上面的 0,代表了 LEVEL_LOW,我們一看就知道是低電平,根本不用在看代碼中查看這個 0 代表什麼意思。
但是很多時候我們都會忘記對數據結構進行初始化,而且很多時候因爲我們知道全局變量會被系統默認初始化爲 0,所以很多時候我們都不會對那些默認爲 0 的全局變量進行顯式初始化,實際上這是很不好的習慣,或者說會給程序留下一些隱患。
因爲魚鷹會設計一個配套的數據結構來管理一個功能模塊,所以魚鷹自然而然的就會設計一個函數用來對這個數據結構進行初始化,同時魚鷹也要對所需的外設進行初始化,這是在寫功能模塊的時候就會很自然的就寫在模塊裏面的。
但是因爲心思都在功能的實現上,所以自然而然的,當寫完了要測試時,有一些函數就會忘記放在合適的地方調用,比如串口外設的初始化、DMA的初始化、中斷的初始化、數據結構的數據初始化等,所以魚鷹除了使用 watch 窗口外,還會打開UART、DMA外設窗口(F4對外設窗口支持不是特別好,但比直接看寄存器會好很多),因爲涉及到 DMA 完成中斷,魚鷹還會打開中斷窗口查看數據。
當然這些窗口不會同時打開,只會在某些功能出問題了纔會打開確認,不然的沒有雙屏的話就沒法看代碼了。
但是魚鷹卻不會對忘記初始化而感到自責,因爲對於魚鷹而言,這種問題很快就會查出來的,所以魚鷹也不會懊惱自己忘記初始化,也不會告誡自己下次一定不要忘記初始化,因爲這真的很容易忘記嘛,可以理解。
首先,如果因爲沒有調用外設初始化函數,必然無法實現你想要的功能,此時,你會自然的看有沒有進行外設初始化,當你打開外設,發現外設這些寄存器的值在程序運行後沒有任何變化,那麼可以確認,外設沒有進行初始化,或者調用外設初始化了,但是初始化函數有問題,那麼你就可以針對性的對外設初始化函數進行檢查了。
其次,就是你設計的數據結構沒有進行初始化,如果你的程序設計得足夠健壯的話,這個問題其實也很容易發現。
比如 FIFO 的緩存地址你沒有進行初始化就直接拿來用了,如果是魚鷹設計的程序,
利用前面魚鷹給道友準備的斷點設置知識點,在調試模式下,程序會自動在這個函數停止執行:
這樣只要一運行沒有初始化的代碼,KEIL 馬上就會告訴你哪裏出現了問題,進而快速的解決問題。
當然前提條件是,你會對函數傳入的參數進行檢查,並且開啓了斷言功能:
從這裏也可以看出,對入參進行檢查是多麼的有必要(標準庫都對參數的合理性進行了檢查),雖然多寫了一些代碼,但是它除了保證程序的健壯性外,也能讓你在調試時快速定位問題!
並且一般來說,除非產品量產了,否則都不建議關閉這個斷言功能,因爲它能幫你在運行出錯時快速檢查出來,以防產生更嚴重的問題,比如破壞棧數據(這種問題特別難查)。
2、非法訪問
如果你不小心沒有對數據進行初始化(全局變量),那也沒有關係,在魚鷹設計的程序裏面,只要程序對地址0 進行非法訪問,那麼程序很快會自動停止在 Hardfault_Hander 函數裏面,當然這個技巧還是使用的斷點設置,有了它,只要出錯了,程序很快就會停止運行,此時你就可以根據魚鷹前面的筆記快速的定位非法訪問的位置了。
這也是魚鷹爲什麼要爲公衆號的道友準備彙編和C語言兩個版本的原因了。
3、對外設理解不清
這個問題其實才是調試最耗時間,因爲對某些知識瞭解不清,導致寫出來的代碼存在問題,那麼爲了解決這個問題還是需要不少時間的。
比如,魚鷹一直以爲 DMA 傳輸完成後, 使能位 CE 應該會自動清除的,但是在運行過程中,出現了問題,魚鷹一查外設窗口,發現 CE 位並不爲0,那麼魚鷹以此作爲傳輸結束條件就有問題了(魚鷹爲了節省一個變量,沒有用變量鎖定DMA外設使用),所以只能打開F1的參考手冊查看相關章節,發現並沒有說正常模式下 DMA傳輸完成後就會自動清零 CE,只是會自動停止 DMA 傳輸,而 CNDTR 寄存器是等於 0 的,也就是說可以通過 CNDTR 寄存器來確定傳輸完成了,所以魚鷹很快修改了這個問題。
而對於多線程的使用、對中斷和線程同時使用 DMA 的情況,魚鷹在寫的時候就充分考慮了,在後續的測試的過程中並沒有發現問題,而對於函數可重入問題,魚鷹也是做了充分考慮的,但是智者千慮必有一失,在測試過程還是出現一個意外的問題,所以後來爲了解決這個問題,又花了半天時間修改。
所以說,代碼寫完之後,完成初步測試只是最基本的操作,除此之外,還要讓你的代碼在實際運行過程中進行長時間的充分測試,只有這樣,你的代碼纔算經受了考驗,可以真正運用在產品上了,否則沒有經過充分測試的代碼,只是廢代碼,只有參考價值,無法直接拿來使用,而即使經過了充分測試,如果你應用的環境和測試環境不同,那麼同樣需要經歷長時間的測試,只有這樣,你寫的代碼纔算是真正融入到系統中,可以放心使用了。
推薦閱讀:
-THE END-
如果對你有幫助,記得轉發分享哦
微信公衆號「魚鷹談單片機」
每週一更單片機知識
長按後前往圖中包含的公衆號關注
魚鷹,一個被嵌入式耽誤的暢銷書作家
個人微信「EmbeddedOsprey」
長按後打開對方的名片關注