Nordic nRF52 BLE firmware programming tutorial
開始使用Nordic nRF52系列晶片,弄清楚如何開發這個平台的firmware,也有數周了。當然Nordic官網有提供許多的文件,包括開發板的手冊,nRFSDK的文件,softDevice的文件,晶片SoC的規格書,還有開發者論檀。其實文件數量多的驚人!
但到現在我個人覺得最適合給入門者看的文件是:
1. ble-advertising-a-beginners-tutorial
2. ble-services-a-beginners-tutorial
3. ble-characteristics-a-beginners-tutorial
這3篇文章在2015,2016年間寫的,後來也有幾度小修改,目前大概維持在nRFSDK 15.0.0,可現在2020/5, Nordic nRFSDK已經出到16.0.0了。
個人看文件的習㥜是從前往後看,結果在下載範例程式後,一編譯就出現許多錯誤訊息,試著去解,發現根本解不完。後來想想,文章裡說範例程式是由examples/ble_peripherial/ble_app_template修改簡化而來,那麼同樣由nRFSDK 16.0.0裡的這個ble_app_template範例程式複製一個出來再改成其他名字,當然也是可以正常編譯的。然後試著把文章的範例裡的main.c複製過去取代原本的main.c,結果可以正常編譯了!這樣就可以往下繼續閱讀和試著依文件改程式看BLE的行為如何變化了。結果一路順利看到文章結束。沒想到文章後的留言也很有看頭!
有人抱怨範例程式不能編譯,但也有人說可以解,而解法就和我想的一樣!所以其實文章真的不要只從前往後看,這種貼文,因為常有更新,也許先看看後文或者前後都翻閱一下,才不會浪費了許多時間。
除了這個版本問題外,這幾篇真得寫得不錯,是很好的入門文件。
以下筆記幾個重點:
---------------------------------------------------
第1篇 廣播
(1) 範例程式如前文所述,由最新SDK的ble_app_template專案複製,再蓋上範例程式的main.c就可以。
(2) 設備名稱由 #define DEVICE_NAME 定義
(3) BT ADDRESS可以在gap_params_init()中寫程式改為private resolvable address
(4) 廣播封包只能包含最多31 bytes, 可以init.advdata.name_type = BLE_ADVDATA_SHORT_NAME來使用短名稱,其長度可由init.advdata.short_name_len來指定
(5) Manufacturer Specific Data, 其結構型態名稱為ble_advdata_manuf_data_t, 由init.advdata.p_manuf_specific_data指向
(6) TX power level, 則由init.advdata.p_tx_power_level指向
(7) Appearance可由init.advdata.include_appearance = true, sd_ble_gap_appearance_set(BLE_APPERANCE_xxxxxxx)設定
(8) scan response 封包可由init.srdata.xxxx指定
---------------------------------------------------
第2篇 服務
(1) 因為要加入自訂的UUID到SoftDevice的UUID表格,在新的SDK:
1. sdk_config.h #define NRF_SDH_BLE_VS_UUID_COUNT 1
2. RAM SIZE調整, RAM_START + 16 bytes, RAM_SIZE - 16 bytes
(2) 修正除錯功能:
1. sdk_config.h NRF_LOG_BACKEND_RTT_ENABLED 1
2. sdk_config.h NRF_FPRINTF_FLAG_AUTOMATIC_CR_ON_LF_ENABLED 0
這樣,當(1)的調整沒做對的時候,例如又加了一個UUID卻忘了做相對調整時,可以由除錯功能告訴我們要修正什麼。
(3) 可以將自訂服務的UUID (長度為16 bytes),放在scan response封包,避免廣播封包太大
如果更簡要的說, 要新增一個服務只要4個步驟:
1. 宣告1個服務結構 ble_os_t, 並實例化一個此型態的全域變數 m_our_service
typedef struct
{
uint16_t service_handle;
} ble_os_t;
ble_os_t m_our_service;
2. 初始化服務, 在main.c裡, 原本就有的services_init()裡, 加入叫用our_service_init()
out_service_init(&m_our_service);
3. 加入UUID到BLE stack的表格中去, 因為我們是要加入自訂UUID的服務
uint32_t err_code;
ble_uuid_t service_uuid;
ble_uuid128_t base_uuid = BLE_UUID_OUR_BASE_UUID;
service_uuid.uuid = BLE_UUID_OUR_SERVICE;
err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
APP_ERROR_CODE(err_code);
4. 加入服務
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&service_uuid,
&p_our_service->service_handle);
APP_ERROR_CHECK(err_code);
---------------------------------------------------
第3篇 特徵值
只有服務沒有特徵值, 雖然Central端可以看到, 但沒有作用, 必要有特徵值才能在其上讀寫值。這裡有兩個協定在作用: Attribute Protocol (ATT), Generic Attribute Profile (GATT)
服務和特徵值及相關資料保存在一個表格(Attribute Table)中
此表格主要欄位為
Attribute Handle
Attribute Type (UUID)
Attribute Permissions
Attribute Values
Handle用來唯一辨別表格中的每一筆記錄, 可以視為每筆編號, 但不一定是連號。
Type用來表明此筆記錄的用途, 例如是服務, 特徵值, 或是數值本身, 或是描述子(descriptor)等。這個部份也是SIG有標準規定的, 可以是16或128位元值。例如服務宣告為0x2800, 特徴值宣告為0x2803, 心律特徵值數值宣告為0x2A37, 描述子宣告則為0x2902
Permissions則定義此記錄的數值是否可讀可寫
Values則儲存此記錄的數值部份, 例如可儲存代表温度的數值, 開關狀態的數值, 或是字串也可以, 但有時用來儲存指向其他記錄的數值, 例如特徵值宣告的數值部份, 存的是(properties, handle, type), 其中handle部份是用來指向後續對應的特徴值數值宣告的記錄。
說真的, 這篇有點長, 看來有點累, 但值得一看, 特別是Step 2是其中的重點, 寫的很精采哦! 別的文件會告訴你要加特徵值必須寫那些那些程式碼, 通常是數十或數百行, 然後會解說一下, 這些程式碼的意思是什麼, 最後跑一下程式, 此時只有兩個結果, 成功或失敗。成功了很好, 但真的沒感覺做了什麼 (因為程式碼太多), 那失敗就更挫折了。
這篇則在Step 2E把特徵值加上去後, 讓你看一下在手機或電腦上看得到出現在服務下的特徵值, 也讓你知道此時無法讀也無法寫的原因是什麼。接下來在Step 2F, 讓你加入一小段程式碼, 讓你在手機或電腦的nRF Connect上看到這個特徵值出現了讀寫的按鈕, 但是真正去讀寫卻又失敗! 也告訴您為什麼會這樣。在Step 2G, 再加上幾行程式碼, 此時手機或電腦的nRF Connect就可以讀取數值了, 但只能讀到空的值。在Step 2H, 再告訴你因為數值的長度沒有設定, 加上幾行程式碼把長度設上去後, 在nRF Connect上就可以正常的讀寫數值了。
透過這樣來回互動的方式, 對特徵值的行為和其程式碼寫法有了更深的印象。
STEP 3則讓此特徴值加入CCCD, 讓它具有notify的功能, 並且加上一個Timer, 每秒把MCU的温度值主動的利用notify的功能傳送出去。
但到現在我個人覺得最適合給入門者看的文件是:
1. ble-advertising-a-beginners-tutorial
2. ble-services-a-beginners-tutorial
3. ble-characteristics-a-beginners-tutorial
這3篇文章在2015,2016年間寫的,後來也有幾度小修改,目前大概維持在nRFSDK 15.0.0,可現在2020/5, Nordic nRFSDK已經出到16.0.0了。
個人看文件的習㥜是從前往後看,結果在下載範例程式後,一編譯就出現許多錯誤訊息,試著去解,發現根本解不完。後來想想,文章裡說範例程式是由examples/ble_peripherial/ble_app_template修改簡化而來,那麼同樣由nRFSDK 16.0.0裡的這個ble_app_template範例程式複製一個出來再改成其他名字,當然也是可以正常編譯的。然後試著把文章的範例裡的main.c複製過去取代原本的main.c,結果可以正常編譯了!這樣就可以往下繼續閱讀和試著依文件改程式看BLE的行為如何變化了。結果一路順利看到文章結束。沒想到文章後的留言也很有看頭!
有人抱怨範例程式不能編譯,但也有人說可以解,而解法就和我想的一樣!所以其實文章真的不要只從前往後看,這種貼文,因為常有更新,也許先看看後文或者前後都翻閱一下,才不會浪費了許多時間。
除了這個版本問題外,這幾篇真得寫得不錯,是很好的入門文件。
以下筆記幾個重點:
---------------------------------------------------
第1篇 廣播
(1) 範例程式如前文所述,由最新SDK的ble_app_template專案複製,再蓋上範例程式的main.c就可以。
(2) 設備名稱由 #define DEVICE_NAME 定義
(3) BT ADDRESS可以在gap_params_init()中寫程式改為private resolvable address
(4) 廣播封包只能包含最多31 bytes, 可以init.advdata.name_type = BLE_ADVDATA_SHORT_NAME來使用短名稱,其長度可由init.advdata.short_name_len來指定
(5) Manufacturer Specific Data, 其結構型態名稱為ble_advdata_manuf_data_t, 由init.advdata.p_manuf_specific_data指向
(6) TX power level, 則由init.advdata.p_tx_power_level指向
(7) Appearance可由init.advdata.include_appearance = true, sd_ble_gap_appearance_set(BLE_APPERANCE_xxxxxxx)設定
(8) scan response 封包可由init.srdata.xxxx指定
---------------------------------------------------
第2篇 服務
(1) 因為要加入自訂的UUID到SoftDevice的UUID表格,在新的SDK:
1. sdk_config.h #define NRF_SDH_BLE_VS_UUID_COUNT 1
2. RAM SIZE調整, RAM_START + 16 bytes, RAM_SIZE - 16 bytes
(2) 修正除錯功能:
1. sdk_config.h NRF_LOG_BACKEND_RTT_ENABLED 1
2. sdk_config.h NRF_FPRINTF_FLAG_AUTOMATIC_CR_ON_LF_ENABLED 0
這樣,當(1)的調整沒做對的時候,例如又加了一個UUID卻忘了做相對調整時,可以由除錯功能告訴我們要修正什麼。
(3) 可以將自訂服務的UUID (長度為16 bytes),放在scan response封包,避免廣播封包太大
如果更簡要的說, 要新增一個服務只要4個步驟:
1. 宣告1個服務結構 ble_os_t, 並實例化一個此型態的全域變數 m_our_service
typedef struct
{
uint16_t service_handle;
} ble_os_t;
ble_os_t m_our_service;
2. 初始化服務, 在main.c裡, 原本就有的services_init()裡, 加入叫用our_service_init()
out_service_init(&m_our_service);
3. 加入UUID到BLE stack的表格中去, 因為我們是要加入自訂UUID的服務
uint32_t err_code;
ble_uuid_t service_uuid;
ble_uuid128_t base_uuid = BLE_UUID_OUR_BASE_UUID;
service_uuid.uuid = BLE_UUID_OUR_SERVICE;
err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
APP_ERROR_CODE(err_code);
4. 加入服務
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&service_uuid,
&p_our_service->service_handle);
APP_ERROR_CHECK(err_code);
---------------------------------------------------
第3篇 特徵值
只有服務沒有特徵值, 雖然Central端可以看到, 但沒有作用, 必要有特徵值才能在其上讀寫值。這裡有兩個協定在作用: Attribute Protocol (ATT), Generic Attribute Profile (GATT)
服務和特徵值及相關資料保存在一個表格(Attribute Table)中
此表格主要欄位為
Attribute Handle
Attribute Type (UUID)
Attribute Permissions
Attribute Values
Handle用來唯一辨別表格中的每一筆記錄, 可以視為每筆編號, 但不一定是連號。
Type用來表明此筆記錄的用途, 例如是服務, 特徵值, 或是數值本身, 或是描述子(descriptor)等。這個部份也是SIG有標準規定的, 可以是16或128位元值。例如服務宣告為0x2800, 特徴值宣告為0x2803, 心律特徵值數值宣告為0x2A37, 描述子宣告則為0x2902
Permissions則定義此記錄的數值是否可讀可寫
Values則儲存此記錄的數值部份, 例如可儲存代表温度的數值, 開關狀態的數值, 或是字串也可以, 但有時用來儲存指向其他記錄的數值, 例如特徵值宣告的數值部份, 存的是(properties, handle, type), 其中handle部份是用來指向後續對應的特徴值數值宣告的記錄。
說真的, 這篇有點長, 看來有點累, 但值得一看, 特別是Step 2是其中的重點, 寫的很精采哦! 別的文件會告訴你要加特徵值必須寫那些那些程式碼, 通常是數十或數百行, 然後會解說一下, 這些程式碼的意思是什麼, 最後跑一下程式, 此時只有兩個結果, 成功或失敗。成功了很好, 但真的沒感覺做了什麼 (因為程式碼太多), 那失敗就更挫折了。
這篇則在Step 2E把特徵值加上去後, 讓你看一下在手機或電腦上看得到出現在服務下的特徵值, 也讓你知道此時無法讀也無法寫的原因是什麼。接下來在Step 2F, 讓你加入一小段程式碼, 讓你在手機或電腦的nRF Connect上看到這個特徵值出現了讀寫的按鈕, 但是真正去讀寫卻又失敗! 也告訴您為什麼會這樣。在Step 2G, 再加上幾行程式碼, 此時手機或電腦的nRF Connect就可以讀取數值了, 但只能讀到空的值。在Step 2H, 再告訴你因為數值的長度沒有設定, 加上幾行程式碼把長度設上去後, 在nRF Connect上就可以正常的讀寫數值了。
透過這樣來回互動的方式, 對特徵值的行為和其程式碼寫法有了更深的印象。
STEP 3則讓此特徴值加入CCCD, 讓它具有notify的功能, 並且加上一個Timer, 每秒把MCU的温度值主動的利用notify的功能傳送出去。
留言