嵌入式系統開發中,軟體測試至關重要,本文涵蓋單元測試、除錯測試、建立測試以及快閃記憶體測試等不同測試型別。快閃記憶體測試程式碼範例展示如何讀取、寫入和擦除快閃記憶體資料,確保資料完整性。此外,自動化測試能提高測試效率和覆寫率,而命令處理則簡化了系統互動。本文也說明如何利用函式指標實作命令模式,將命令請求與執行邏輯分離,增加系統的彈性與可擴充套件性。同時,穩健的錯誤處理機制也是嵌入式系統不可或缺的一環,我們需要制定錯誤碼標準、設計錯誤檢查流程,並結合斷點指令進行除錯,以確保系統的穩定性和可靠性。
單元測試
單元測試應該在每次軟體發布之前執行,但它們可能不適合在每次啟動時執行。這些測試驗證軟體和硬體是否按預期一起工作。
除錯測試
除錯測試是在除錯過程中建立的,通常是因為子系統未按預期工作。這些測試可能是臨時的,可以在問題解決後刪除。
建立測試
建立測試需要寫程式碼來控制外圍裝置和相關測試。好的訊息是您剛剛花時間研究了資料表,因此您對如何實作程式碼有了一個很好的想法。不好的訊息是,在等待板子到達期間,您可能會為六個外圍裝置寫驅動程式,而沒有機會將軟웨어與硬體整合在一起。
快閃記憶體測試
快閃記憶體是一種非易失性儲存器,可以擦除和寫入。快閃記憶體通常比EEPROM有更多空間,且功耗更低。但是,EEPROM仍然有用,因為它們可以以更小的尺寸提供。
快閃記憶體測試程式碼
快閃記憶體測試程式碼需要三個引數:目標快閃記憶體地址、指向記憶體的指標和記憶體長度。記憶體長度是快閃記憶體測試將儲存到RAM的資料量。如果記憶體與扇區大小相同,則不會丟失任何資料。
int FlashTest(uint32_t address, uint8_t *memory, uint16_t memLength);
uint16_t FlashRead(uint32_t addr, uint8_t *data, uint16_t dataLen);
uint16_t FlashWrite(uint32_t addr, uint8_t *data, uint16_t dataLen);
測試1:讀取現有資料
測試1驗證是否可以從快閃記憶體中讀取資料,這實際上驗證了I/O線是否組態為SPI埠,SPI埠是否組態正確,以及您是否理解快閃記憶體命令協定的基礎。
// 測試1:讀取現有資料
dataLen = FlashRead(startAddress, memory, memLength);
if (dataLen!= memLength) {
// 讀取少於所需,記錄錯誤
Log(LogUnitTest, LogLevelError, "Flash test: truncation on byte read");
memLength = dataLen;
error++;
}
測試2:位元組存取
測試2首先擦除扇區,然後用遞增值填充快閃記憶體,每次寫入一個位元組。我們希望寫入一個會改變的值,以便確保命令有效。我喜歡寫入一個根據地址的偏移量(偏移量告訴我我不是意外地讀取地址)。
FlashEraseSector(startAddress);
// 想要放入一個遞增值,但不要它是地址
addValue = 0x55;
for (i=0; i< memLength; i++) {
value = i + addValue;
dataLen = FlashWrite(startAddress + i, &value, 1);
if (dataLen!= 1) {
Log (LogUnitTest, LogLevelError, "Flash test: byte write error.");
error++;
}
}
測試3:塊存取
現在快閃記憶體包含我們新寫入的資料,確認塊存取是否有效,方法是再次擦除扇區:
FlashEraseSector(startAddress);
dataLen = FlashWrite(startAddress, memory, memLength);
自動化測試和命令處理
在嵌入式系統開發中,自動化測試和命令處理是兩個非常重要的方面。自動化測試可以幫助我們確保系統的可靠性和穩定性,而命令處理可以讓我們更容易地與系統進行互動。
自動化測試
自動化測試是指使用程式碼來自動執行測試任務。這種方法可以幫助我們快速地測試系統的各個部分,從而確保系統的可靠性和穩定性。
在上面的程式碼中,我們可以看到一個簡單的自動化測試範例。這個範例使用了一個 if 陳述式來檢查是否有錯誤發生,如果有錯誤發生,就會輸出一個錯誤訊息。
if (dataLen!= memLength) {
LogWithNum(LogUnitTest, LogLevelError,
"Flash test: block write error, len ", dataLen);
error++;
}
這個範例展示瞭如何使用自動化測試來檢查系統的功能是否正常。
命令處理
命令處理是指使用程式碼來處理使用者的命令。這種方法可以讓我們更容易地與系統進行互動。
在上面的程式碼中,我們可以看到一個簡單的命令處理範例。這個範例使用了一個 struct 來定義一個命令的結構,包括命令的名稱、執行函式和幫助字串。
typedef void(*functionPointerType)(void);
struct commandStruct {
char const *name;
functionPointerType execute;
char const *help;
};
這個範例展示瞭如何使用命令處理來讓使用者可以更容易地與系統進行互動。
實作命令處理
要實作命令處理,我們需要建立一個命令表,並使用函式指標來呼叫相應的函式。
// 建立一個命令表
struct commandStruct commands[] = {
{"version", versionCommand, "Outputs the version information"},
{"test_flash", testFlashCommand, "Runs the flash unit test"},
{"blink_led", blinkLedCommand, "Sets an LED to blink at a given frequency"},
{"help", helpCommand, "Lists available commands with one-line descriptions"},
};
// 使用函式指標來呼叫相應的函式
void executeCommand(char const *name) {
for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
if (strcmp(name, commands[i].name) == 0) {
commands[i].execute();
return;
}
}
}
這個範例展示瞭如何使用命令表和函式指標來實作命令處理。
內容解密:
- 自動化測試可以幫助我們快速地測試系統的各個部分,從而確保系統的可靠性和穩定性。
- 命令處理可以讓我們更容易地與系統進行互動。
- 使用函式指標和命令表可以實作命令處理。
圖表翻譯:
graph LR
A[自動化測試] --> B[命令處理]
B --> C[實作命令處理]
C --> D[使用函式指標和命令表]
這個圖表展示了自動化測試、命令處理和實作命令處理之間的關係。
命令模式與函式指標
在嵌入式系統中,命令模式是一種常見的設計模式,用於解耦命令處理和實際動作。這種模式允許系統根據不同的命令執行不同的動作,而無需修改系統的核心邏輯。
命令模式的組成
命令模式由四個部分組成:
- Client:客戶端,負責建立具體的命令物件和建立接收者與命令物件之間的關聯。
- Invoker:呼叫者,負責確定何時執行命令並執行它。呼叫者不需要知道接收者的具體細節,只需執行命令即可。
- Command:命令,定義了一個執行操作的介面。命令可以是C++類別、Java介面或C結構體,其中包含函式指標。
- Receiver:接收者,實際執行命令的物件。
函式指標
函式指標是一種特殊的指標,指向函式而非資料。它允許在程式執行期間動態地決定要呼叫哪個函式。
函式指標的宣告
宣告函式指標需要指定函式的原型,包括傳回型別、引數列表等。例如:
void (*filter)(uint16_t* data, uint16_t dataLen);
函式指標的使用
函式指標可以用來呼叫函式,方法有兩種:隱式呼叫和顯式呼叫。
filter = fir; // 或 filter = &fir;
filter(data, dataLen); // 或 (*filter)(data, dataLen);
命令模式的優點
命令模式具有以下優點:
- 解耦: 命令模式解耦了命令處理和實際動作,使得系統更容易維護和擴充套件。
- 靈活性: 命令模式允許在程式執行期間動態地決定要執行哪個命令。
- 復用性: 命令模式允許復用命令物件和接收者物件。
錯誤處理的重要性
在軟體開發中,錯誤處理是一個至關重要的環節。無論是如何小心謹慎地撰寫程式碼,錯誤總是可能發生。因此,瞭解如何正確地處理錯誤是每個軟體開發人員必須掌握的技能。
錯誤處理的兩種方式
一般來說,錯誤處理有兩種方式:優雅降級(graceful degradation)和立即失敗(fail loudly and immediately)。優雅降級指的是當系統遇到錯誤時,嘗試繼續執行並提供最好的服務;而立即失敗則是指當系統遇到錯誤時,立即停止執行並報告錯誤。
錯誤處理的實作
在實作錯誤處理時,函式應該盡可能地處理錯誤。例如,如果變數可能超出範圍,函式應該修正範圍並記錄錯誤。函式也可以傳回錯誤碼,以便呼叫者能夠處理錯誤。呼叫者應該檢查並處理錯誤,這可能涉及將錯誤傳遞給上層應用程式。
錯誤碼的標準化
為了方便錯誤處理,應該標準化錯誤碼。可以建立一個高階別的錯誤碼檔案(例如 errorCodes.h),以列舉格式提供一致的錯誤碼。一些建議的錯誤碼包括:
- 沒有錯誤(應該始終為 0)
- 未知錯誤(或未識別的錯誤)
- 壞引數
- 壞索引(指標超出範圍或為 null)
- 未初始化的變數或子系統
- 災難性失敗(可能導致處理器重置,除非在開發模式下,否則可能導致中斷或迴圈)
錯誤檢查流程
傳回錯誤碼對於沒有錯誤處理的應用程式沒有任何幫助。太多時候,我們只為了正常執行的情況編寫程式碼,而忽略了其他路徑。是否希望錯誤永遠不會發生?這是愚蠢的。
另一方面,當函式應該跳轉到結尾並傳回自己的錯誤時,個別處理錯誤會很冗長。雖然有些人使用巢狀 if/else 陳述式,但我通常更喜歡在函式中流暢地進行錯誤檢查,嘗試使錯誤足夠冗餘,以便在正常路徑中可以忽略它。
error = FunctionSay();
斷點指令
斷點指令告訴處理器如果偵錯程式已經附加上,就停止執行。可以編譯斷點函式到你的程式碼中,通常是以組合語言函式形式存在,常常被隱藏起來。
#define BKPT() __asm__("BKPT")
#define BKPT() __asm__("bkpt #0")
#define BKPT() __asm__("break")
這些斷點會被編譯到你的程式碼中(它們不會佔用你有限的硬體斷點預算!)。這使得在某些條件下只出現錯誤時走過程式碼變得更容易。
命令模式
命令模式是一種設計模式,它使得請求的傳送者和接收者之間的解耦成為可能。命令模式由三個部分組成:請求傳送者、命令和接收者。
請求傳送者負責傳送請求,命令負責封裝請求,而接收者負責執行請求。這種模式可以使得系統更容易擴充套件和維護。
嵌入式系統開發對於自動化測試和高效命令處理的需求日益增長。本文深入探討了從單元測試、除錯測試到建立測試的不同測試型別,並以快閃記憶體測試為例,闡述了測試程式碼的設計思路與實踐方法。此外,文章還介紹了錯誤處理的重要性、不同錯誤處理方式以及錯誤碼標準化的必要性,提供了一個更全面的嵌入式系統開發的測試和除錯框架。
分析測試程式碼的結構和功能可以發現,有效的測試策略需要涵蓋各個層面,從底層硬體互動到高層應用邏輯。快閃記憶體測試案例清晰地展現瞭如何透過讀取現有資料、位元組存取和塊存取等多維度測試來驗證快閃記憶體功能的完整性。同時,文章提出的錯誤處理流程和斷點指令的應用技巧,能有效提升開發效率,降低錯誤排查的難度。然而,目前的測試方法仍存在侷限性,例如缺乏對複雜系統和多執行緒環境的充分考慮。
展望未來,嵌入式系統測試將更趨向於自動化和智慧化。隨著物聯網和邊緣計算的快速發展,嵌入式裝置的複雜度和數量都將大幅增加,這對測試技術提出了更高的要求。預計未來將出現更多根據模型的測試方法、自動化測試框架和智慧化錯誤診斷工具,以應對日益增長的測試需求。
玄貓認為,嵌入式系統開發者應積極探索和應用新的測試技術和工具,並將自動化測試和錯誤處理融入到開發流程的每個環節,才能確保系統的品質和可靠性。對於資源有限的團隊,建議優先將自動化測試應用於核心模組和關鍵功能,逐步提升測試覆寫率。