本文將講述Nordic nRF5 SDK的主要調試手段,以幫助你們快速定位問題,並解決問題。通常來講,你能夠經過打log方式,IDE的debug模式,SDK自帶的app_error_check函數,以及命令行方式等多種手段來調試你的代碼。前端
nRF5 SDK支持UART和SWD J-Link(RTT)兩種底層通訊方式來打印日誌,SDK14以後日誌也能夠經過藍牙或者Flash進行輸出和存儲打印,通常來講,UART和SWD用得比較多,其中UART使用串口助手來查看日誌,SWD使用J-Link RTT Viewer(僅適用Windows)或者J-Link RTT Client(Windows/Mac/Linux系統)來查看打印日誌。因爲UART日誌打印方式會佔用一個UART口,而大部分nRF5芯片都只有一個UART口,從而致使資源衝突,爲此推薦你們使用RTT方式來打印日誌,從而能夠將UART口留給正常應用,更重要的是RTT打印方式功耗很是低(幾乎能夠忽略不計),你們能夠在正式release的產品中也使能它,從而發現產品部署後有可能會出現的問題(UART打印方式功耗很是高,在正式release產品中必須關掉它)。後端
若是使用SWD接口進行日誌打印,那麼你可使用J-Link RTT Viewer或者RTTClient來顯示日誌,兩者選其一便可。在Windows平臺推薦使用RTT viewer,不然使用RTT Client。app
RTT viewer配置方式以下所示:函數
RTT viewer日誌打印窗口以下所示:(使用的是SDK15.3中的ble_app_hrs例子,下同)spa
RTT client配置方式以下所示(直接使用jlink命令進行配置):命令行
RTT client打印窗口以下所示:debug
串口助手配置方式以下所示:3d
隨便選擇一款你熟悉的串口助手,好比Putty或者Termite,Termite打印窗口以下所示:調試
Putty打印窗口以下所示:日誌
nRF5 SDK日誌打印功能是經過nRF_Log模塊實現的(上面展現的日誌都是經過nRF_Log打印出來的),SDK包含的大部分例子都自帶打印功能,也就是說包含了nRF_Log模塊。通常來講,例子都是默認使用UART進行打印的,若是須要改成RTT進行打印,須要對nRF_Log模塊進行配置。在具體講述nRF_Log模塊的配置選項以前,先大概講述一下nRF_Log的工做原理。
nRF_Log工做原理
nRF_Log模塊包含前端和後端兩部分代碼。前端是面向用戶的打印接口,好比NRF_LOG_INFO,它把用戶要打印的數據放在一塊RAM中。後端用來具體實現打印功能,即把前端RAM中的數據經過不一樣的後端接口打印出去。目前nRF_Log支持的後端接口有:UART,RTT,Flash和藍牙。無論採用哪種後端接口,對用戶來講,其調用的前端API是同樣的,nRF_Log模塊會根據用戶的配置自動適配相應後端接口,並且用戶能夠同時使能多個後端接口,即把日誌同時打印到多個後端端口上。nRF_Log模塊能夠單獨使能或禁止某一個模塊的打印功能,好比advertising模塊,當你調試advertising模塊的時候,能夠把log打開,調試完畢,把log關閉以讓log界面變得更清爽。nRF_Log模塊還能夠設置打印的級別(Level),若是不分級別的話,打印界面會包含不少打印信息,讓咱們每次調試都要花費不少時間去尋找本身想要的日誌上。設定級別後,咱們能夠有選擇的打印須要的日誌信息,沒問題時,咱們只打印info級別的日誌;有問題時,咱們就能夠把全部debug級別的日誌都打印出來。nRF_Log還有一個功能:Deferred,若是不使能Deferred,那麼調用NRF_LOG_INFO等API的時候,立馬就Flush,即把日誌打印出去;若是使能了Deferred,那麼調用NRF_LOG_INFO等API的時候,只是把打印數據放在RAM中,真正的打印由main函數中的NRF_LOG_PROCESS完成,這樣能夠最大程度下降打印對應用自己的影響,尤爲在執行一些時序很關鍵的操做的時候。nRF_Log還支持時間戳打印,即在每條日誌以前加上時間戳信息。
nRF_Log配置
從SDK12之後,nRF_Log模塊的配置主要放在sdk_config.h文件中,以工程nRF5_SDK_15.3.0_59ac345\examples\ble_peripheral\ble_app_hrs\pca10040\s132\arm5_no_packs爲例,nRF_Log的配置選項以下所示:
注意:nRF5 SDK v11.0.0及之前版本是沒有sdk_config.h文件的,此時你須要到options for target->C/C++->define裏面定義一個宏(Keil工程),若是定義「NRF_LOG_USES_UART=1」選擇UART日誌打印;若是定義」NRF_LOG_USES_RTT=1」 則選擇RTT日誌打印,以下:
仍是以nRF5_SDK_15.3.0_59ac345\examples\ble_peripheral\ble_app_hrs\pca10040\s132\arm5_no_packs爲例,當nRF_Log配置爲info級別,無timestamp,打印信息以下(與前面配置同樣)
當nRF_Log配置爲debug級別,打開timestamp,打印信息以下所示:
Nordic產品支持單步,斷點,寄存器查看,內存查看,call stack查看等全部常規調試手段。須要注意的是,因爲softdevice在底層要維持必定的時鐘以及處理不少中斷事件,一旦藍牙跑起來後,只能用一個斷點進行全速跑,也就是說,執行完一個斷點後,程序內部邏輯和時序已經紊亂,此時不能再繼續全速跑第二個斷點。若是要看第二個斷點的內容,只能刪掉第一個斷點,從新開始執行。
經過如下方式打開nRF5 SoC內部寄存器查看窗口:
用得比較多的幾個Keil窗口:
SES的調試界面以下所示(跟Keil很像):
這裏要特別強調一下,全部SES工程都包含2個版本:Release版和Debug版,Release是沒有包含debug信息以節省代碼空間,因此若是你要debug一個工程,請先選擇debug版本,而後再進行debug,以下所示:
APP_ERROR_CHECK是nRF5 SDK定義的一個用來檢查API返回值是否正確的函數,在nRF5 SDK中,NRF_SUCCESS(0)爲正確返回值,其它返回值皆爲錯誤值。nRF5 SDK全部協議棧API調用,以及SDK庫函數調用,都會用APP_ERROR_CHECK去檢查調用的返回值。當出現非法調用時,好比傳入的實參不對,API返回值就不會爲NRF_SUCCESS,此時APP_ERROR_CHECK就會派上大用場。經過查看APP_ERROR_CHECK函數定義,以下所示:
NRF_BREAKPOINT_COND; // On assert, the system can only recover with a reset. #ifndef DEBUG NRF_LOG_WARNING("System reset"); NVIC_SystemReset(); #else app_error_save_and_stop(id, pc, info); #endif // DEBUG
你會發現APP_ERROR_CHECK行爲受宏DEBUG和有沒有掛仿真器兩個因素的影響,當沒有定義DEBUG宏時,系統將直接產生軟復位;當定義了DEBUG宏而且沒有掛仿真器時,系統將把錯誤信息保存在call stack中。無論怎麼配置,APP_ERROR_CHECK都會把相應的錯誤信息打印出來,以方便你去排查問題,以下所示。經過打印出來的錯誤信息,你就能夠知道是哪一個文件哪一行代碼出了什麼類型的錯誤。
注意在SDK14之前,app_error_check不會主動把錯誤信息打印出來,而是把錯誤信息存在RAM中,而後經過debug模式能夠直接查看相關錯誤信息,以下所示:
默認狀況下,nRF5 SDK是沒有定義DEBUG宏的,因此一旦函數返回值不對,系統就會復位。這裏要特別指出的是,在你開發調試過程當中,常常會碰到復位的狀況,這其中大部分都是由APP_ERROR_CHECK引發的。針對這種狀況的復位,你只須要在options for target->C/C++->define裏面定義一個宏:DEBUG,就能夠快速找出是哪個文件哪一行代碼引出的復位,以及復位緣由是什麼,以下所示:
打log方式只能單方面輸出,而不能接受終端的輸入,在不少狀況下,咱們須要動態調整log信息,好比動態更改log的級別,動態更改log的顏色等等,這個時候就須要用到nrf_cli模塊,跟nRF_Log類似,nrf_cli同時支持5種後端接口:UART,RTT,BLE,Flash和USB CDC。因爲nrf_cli也有log輸出功能,所以nRF_Log模塊能夠直接選擇nrf_cli爲其後端接口。這裏再次強調一下,nrf_log和nrf_cli是兩個徹底獨立的模塊,可是nrf_log能夠選用nrf_cli爲其後端接口,由nrf_cli完成後端打印功能。SDK中提供了三個cli的例子,分別爲:
若是你們的應用須要命令行交互方式,能夠參考上面3個例子,以examples\peripheral\cli\pca10040\blank\arm5_no_packs爲例,交互成功後的界面以下所示: