目錄
1 ESP NOW
樂鑫官方網址:https://esp-idf.readthedocs.io/en/latest/api-reference/wifi/esp_now.html
譯文如下:
1.1 ESP NOW簡介
ESP-NOW是由樂鑫定義的一種無連接WiFi通訊協議。在ESP-NOW協議下,應用數據都是被封裝在”vendor-specific action”(VSA)幀且在設備間的傳輸無需連接。CCMP協議用於保護VSA幀的安全性。ESP-NOW協議廣泛應用與智能燈具,遠程控制,傳感器等。
1.2 ESP NOW幀格式
ESP-NOW使用VSA幀傳輸數據,VSA幀格式如下:
Category code:類別碼,設定爲127以標識該幀爲VSA幀;
Organization Indentifier:組織認證碼,該部分是樂鑫設定的0x18fe34;
Vendor Specific Content:幀數據區,其格式如下:
Element ID:設置爲221;
Length:Organization Identifier, Type, Version and Body三部分的長度之和;
Organization Indentifier:組織認證碼,該部分是樂鑫設定的0x18fe34;
Type:設置爲4,代表ESP-NOW協議;
Version:ESP-NOW協議版本;
Body:ESP-NOW數據區
ESP-NOW的VSA幀的MAC頭部分與常規VSA幀有不同:
MAC頭的FrameControl部分的FromDS位於ToDS位均被設置爲0;
MAC頭的第一個地址區設置爲目的地址;第一個地址區設置爲源地址;第三個地址區爲廣播地址0xFF: 0xFF: 0xFF: 0xFF: 0xFF: 0xFF
1.3 ESP NOW安全性
ESP-NOW使用CCMP方法加密VSA幀。CCMP可以參見IEEE Std. 802.11-2012標準。
WiFi設備持有一個PMK(Primary Master Key)與數個LMK(Local Master Key),這些Key的長度均爲16位。PWK用於通過AES-128算法加密LMK,調用接口esp_now_set_pmk()設置。若PWM未被用戶設置,會採用默認的PWK。LMK被設置後用來加密VSA幀。最多支持6個LMK,且不支持加密組播的VSA幀。
1.4 ESP NOW初始化與反初始化
調用接口esp_now_init()初始化ESP-NOW;調用接口esp_now_deinit()反初始化;
建議在啓動WiFi後初始化ESP-NOW,在停止WiFi前反初始化ESP-NOW協議棧;一旦
esp_now_deinit被調用,所有相關的配對設備信息將被刪除。
1.5 添加配對設備
在向某設備發送數據之前,首先需要調用接口esp_now_add_peer()將這個設備添加到配對設備列表。配對設備列表最大支持20個設備。若使能安全性傳輸,LMK必須被設置。ESP-NOW數據可以從STA模式接口或者softAP模式接口發送,所以發送數據前需要保證這些接口已被使能。發送廣播數據前,必須添加廣播的MAC地址(到配對設備列表?)。配對設備的通信信道範圍是0-14,若信道被設置爲0,則數據會被在當前信道發送;否則,(收發設備的)通信信道必須設置一致。
1.6 發送ESP-NOW數據
調用esp_now_send()發送數據;調用esp_now_register_send_cb註冊發送回調函數。若發送的數據在MAC層被成功接收,則發送回調函數會返回ESP_NOW_SEND_SUCCESS;否則會返回ESP_NOW_SEND_FAIL。
發送ESP-NOW數據失敗有多種可能,例如:目標端設備不存在;收發設備信道不相同,VSA幀傳輸中丟失等等。因此無法保證應用層接收到數據。如果需要的話,在接收到ESP-NOW數據時返回ACK數據用以確認;若接收ACK數據超時,則重發本次數據。也可以在ESP-NOW數據中加入序列號用於丟棄重複的數據。
若有大量數據需要發送,則調用esp_now_send()接口每次發送不多於250個字節。需要注意的是,兩次發送之間的間隔過短可能會導致發送回調函數的錯序。因此,推薦在上一次發送數據的回調函數返回後再進行本次發送操作。發送回調函數被一個高優先級的WiFi任務調用,因此不要在發送回調函數內進行過多的操作佔用系統;最好是將必要的數據放入隊列中並在低優先級的任務內進行相應的處理。
1.7 接收ESP-NOW數據
調用接口esp_now_register_recv_cb註冊ESP-NOW數據接收回調函數。與發送回調函數相同,接收回調函數也運行在高優先級的WiFi任務內,因此不要在接收回調函數內做過多操作,並且最好將數據傳入隊列在低優先級的任務內再進行處理。
1.8 ESP-NOW API參考
ESP-NOW在ESP-IDF(ESP開發架構)下是以庫的形式提供的,其頭文件路徑:
\esp-idf\components\esp32\include\esp_now.h
重要API羅列如下:
<1> esp_now_init/esp_now_deinit:初始化與反初始化ESP-NOW協議棧
<2> esp_now_register_recv_cb/esp_now_unregister_recv_cb:註冊與解除接收回調函數
<3> esp_now_register_send_cb/esp_now_unregister_send_cb:註冊與解除發送回調函數
<4> esp_now_add_peer/esp_now_del_peer/esp_now_mod_peer/esp_now_get_peer/
esp_now_fetch_peer/esp_now_is_peer_exist/esp_now_get_peer_num:向配對列表添加/刪除/設備/調整配對列表/從配對列表獲取MAC匹配的設備/從配對列表(遍歷)獲取一個設備/檢測配對設備是否存在/獲取配對列表內的設備個數
重要的數據結構:
其含義已經非常清晰了:peer_addr即是設備的MAC地址;wifi_interface_t代表設備使用softAP接口/STA接口/ETH接口收發數據。
2 ESP-MDF對ESP-NOW的應用
ESP-MDF是ESP-Mesh Development Framework的縮寫,也就是“ESP Mesh開發架構”,其結構如下:
ESP-MDF下對ESP-NOW的基本功能(本文第1章)進行了擴展應用:
<1> 紅框部分是ESP-MDF對ESP-NOW的API進行的封裝,其代碼路徑位於:
\esp-mdf\components\protocol_stacks\mdf_espnow\mdf_espnow.c
\esp-mdf\components\protocol_stacks\mdf_espnow\include\mdf_espnow.h
該部分稱爲MDF-ESPNOW
<2> 紫框部分是對mdf_espnow的應用,主要用於ESP WiFi Mesh的調試與配網,其代碼路徑位於:
\esp-mdf\components\functions\mdf_debug\mdf_espnow_debug.c
\esp-mdf\components\functions\mdf_debug\include\mdf_espnow_debug.h
\esp-mdf\components\functions\mdf_network_config\mdf_network_config.c
\esp-mdf\components\functions\mdf_network_config\mdf_network_config.h
3 MDF- ESPNOW
針對ESP提供的ESP-NOW簡單API(本文1.8節所述),ESP WiFi Mesh的開發架構ESP-MDF下進行了一次封裝,以便於MDF下進行配網調試等功能,其代碼路徑:
\esp-mdf\components\protocol_stacks\mdf_espnow\mdf_espnow.c
\esp-mdf\components\protocol_stacks\mdf_espnow\include\mdf_espnow.h
3.1 MDF-ESPNOW數據包類型
MDF-ESPNOW對ESP-NOW擴展加入了與ESP WiFi Mesh應用相關的幾種數據包類型:
由此可見,ESP-NOW機制在ESP WiFi Mesh內可以用於調試,配網,以及一些控制數據的傳輸。
另外相關的宏定義:
MDF_ESPNOW_INTERFACE:ESP-NOW數據收發接口,默認爲STA模式
CONFIG_MDF_ESPNOW_PMK:PMK,見1.3節,可由用戶定義
CONFIG_MDF_ESPNOW_LMK:LMK,見1.3節,可由用戶定義
3.2 MDF-ESPNOW數據包結構
單個數據包最大:ESP_NOW_MAX_DATA_LEN 250
單個數據包數據區最大數據:(ESP_NOW_MAX_DATA_LEN - sizeof(mdf_espnow_pkt_t))
也即是一個ESP-NOW數據包包括MDF-ESPNOW數據頭與數據區。
3.3 MDF-ESPNOW API
mdf_espnow_init
mdf_espnow_read:接收MDF-ESPNOW數據包
mdf_espnow_write:發送MDF-ESPNOW數據包
mdf_espnow_add_peer_base
mdf_espnow_del_peer
3.4 mdf_espnow_init
初始化ESP-NOW: esp_now_init
註冊接收與發送回調函數:mdf_espnow_recv_cb/ mdf_espnow_send_cb
設置PMK:MDF_ESPNOW_PMK
3.5 mdf_espnow_write
mdf_espnow_write發送函數實現流程如下:
前文說過,單次發送數據包的最大250個字節,且推薦做法是等待發送回調函數返回再進行下一次發送,這裏全部都滿足。
值得注意的是等待發送回調函數標誌位時會一直阻塞,因此mdf_espnow_write函數可能會阻塞任務。
3.6 mdf_espnow_read
mdf_espnow_read接收的實現複雜一些:
<1> 對於MDF-ESPNOW支持的數據包類型mdf_espnow_pkt_type_t,每種類型用戶可以通過調用mdf_espnow_enable來初始化一個接收隊數組g_mdf_espnow_queue[i],隊列的下標i是其類型,隊列大小定義在g_mdf_espnow_queue_size[i];
<2> 當註冊的接收回調函數mdf_espnow_recv_cb被調用,首先判斷接收到的數據包類型,然後分配空間拷貝數據包,並將數據包地址發送到對應的接收隊列;
<3> 調用mdf_espnow_read接口時,會根據用戶傳入的數據包類型從相應的隊列獲取接收的數據包地址,再根據數據包的序列號(序列號代表一個完整的數據包被分割爲多少個傳輸)分配空間並繼續從隊列依次獲取餘下的數據包拷入以生成完整的數據包,生成完整的數據包後再拷入用戶傳入的地址空間內。
所以從ESP-NOW的接收回調觸發到用戶主動獲取完畢,發生了三次內存分配與拷貝:
A:接收回調函數內
B:用戶調用mdf_espnow_read接口,分配空間並拷貝每個子數據包以獲取完整的
C:當獲取完整的數據包完畢,還要拷貝數據到用戶傳入的空間
這裏有個巧妙的地方,mdf_espnow_send將大的數據包拆分發送時的序列號是從大到小倒着發的,這樣在接收時,從隊列中獲取的第一個子數據包的序列號就能得知整個包的大小以分配空間:
發送時:
trans_pkt->seq = (pkt_count - 1) - i;
接收時:
uint8_t *recv_data = mdf_calloc(1, (pkt_count + 1) * MDF_ESPNOW_MAX_DATA_LEN);
3.7 其它接口
其它接口與1.8節所述的ESP-NOW接口內容基本相似。