# 13.系统API ## 13.1 OTA升级 OTA —— Over the air update of the firmware,也就是无线固件更新,通过此方法我们可以不连接 USB,而通过无线方式下载程序。 Edge101WE 主板具备多种的 OTA 方式。 ### 13.1.1 ArduinoOTA [ArduinoOTA](https://github.com/espressif/arduino-esp32/tree/master/libraries/ArduinoOTA) 方式需要结合Arduino IDE,比较适合开发工程师使用。 #### API参考 ##### onStart() 注册要在OTA更新开始时调用的回调函数。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::onStart(THandlerFunction fn); ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :----------------------------------------------------------: | :----: | | fn | 回调函数。
THandlerFunction类型的定义如下。
typedef std::function THandlerFunction; | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | 需要预先定义Arduino OTA类的对象Arduino OTA,并使用该对象对其进行操作。 ##### getCommand() 获取用于OTA更新的命令。 **语法** ```c++ #include int ArduinoOTAClass::getCommand(); ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :----: | :---------------- | ------ | | | U_FLASH或U_SPIFFS | | ##### handle() 处理OTA更新执行。 **语法** ```c++ #include void ArduinoOTAClass::handle(); ``` **参数** 无 **返回** 无 ##### onEnd() 在OTA更新结束时注册要调用的回调函数。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::onEnd(THandlerFunction fn); ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :----------------------------------------------------------: | :----: | | fn | 回调函数。
THandlerFunction类型的定义如下。
typedef std::function THandlerFunction; | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | ##### onError() 注册发生OTA更新错误时要执行的回调函数。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::onError(THandlerFunction_Error fn); ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :----------------------------------------------------------: | :----: | | fn | 回调函。
THandlerFunction_Error类型的定义如下。
typedef std::function THandlerFunction_Error;
ota_error_t类型的定义如下。
typedef enum { OTA_AUTH_ERROR, OTA_BEGIN_ERROR, OTA_CONNECT_ERROR,
OTA_RECEIVE_ERROR, OTA_END_ERROR } ota_error_t; | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | ##### onProgress() 在OTA更新中注册要在更新期间执行的回调函。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::onProgress(THandlerFunction_Progress fn); ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :----------------------------------------------------------: | :----: | | fn | 回调函。
THandlerFunction_Progress类型的定义如下。
typedef std::function THandlerFunction_Progress;
第一个参数是固件当前数据大小,第二个参数是固件总大小。单位byte。 | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | ##### setHostname() 设置用于OTA更新的主机名。如果未设置,默认值为esp32- [MAC address]。主要用于mDNS的域名映射。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::setHostname(const char * hostname); ``` **参数** | 传入值 | 说明 | 值范围 | | :------: | :----: | :----: | | hostname | 主机名 | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | 注意:在begin方法之前调用。 ##### setPassword() 设置用于OTA更新的访问密码。如果未设置,将不使用密码。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::setPassword(const char * password); void ArduinoOTAClass::setPassword(const char * password) { if (!_initialized && !_password.length() && password) { //MD5编码 建议用这个方法更好 MD5Builder passmd5; passmd5.begin(); passmd5.add(password); passmd5.calculate(); _password = passmd5.toString(); } } ``` **参数** | 传入值 | 说明 | 值范围 | | :------: | :------------------: | :----: | | password | 上传密码,默认为NULL | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | 注意:在begin方法之前调用。 ##### setPasswordHash() 以md5哈希格式设置用于OTA更新的密码。如果未设置,将不使用密码。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::setPasswordHash(const char * password); void ArduinoOTAClass::setPasswordHash(const char * password) { if (!_initialized && !_password.length() && password) { //md5编码的password _password = password; } } ``` **参数** | 传入值 | 说明 | 值范围 | | :------: | :-------------: | :----: | | password | 密码(md5哈希) | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | 注意:在begin方法之前调用。 ##### setPort() 设置用于OTA更新的端口。如果未设置,则默认值为3232。 **语法** ```c++ #include ArduinoOTAClass& ArduinoOTAClass::setPort(uint16_t port); ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :------: | :----: | | port | 服务端口 | | **返回** | 返回值 | 说明 | 值范围 | | :-------------------: | :--- | ------ | | ArduinoOTAClass *this | | | 注意:在begin方法之前调用。 ##### begin() 启动OTA更新服务。 **语法** ```c++ #include void ArduinoOTAClass::begin(); ``` **参数** 无 **返回** 无 ##### handle() 处理固件更新,这个方法需要在loop方法中不断检测调用。 **语法** ```c++ void ArduinoOTAClass::handle() { if (_state == OTA_RUNUPDATE) { //处理固件传输更新 _runUpdate(); _state = OTA_IDLE; } } ``` **参数** 无 **返回** 无 ##### setRebootOnSuccess() 设置固件更新完毕是否自动重启。 **语法** ```c++ void setRebootOnSuccess(bool reboot); ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :------------------: | :----: | | reboot | 设置成true,自动重启 | | **返回** 无 ##### updateCredentials() 校验用户信息,用于OTAWebUpdater。 **语法** ```c++ void updateCredentials(const char * username, const char * password) ``` **参数** | 传入值 | 说明 | 值范围 | | :------: | :------: | :----: | | username | 用户名称 | | | password | 用户密码 | | **返回** 无 ##### setup() 配置WebOTA。 ```c++ void setup(WebServer *server); void setup(WebServer *server, const char * path); void setup(WebServer *server, const char * username, const char * password); void setup(WebServer *server, const char * path, const char * username, const char * password); ``` **参数** | 传入值 | 说明 | 值范围 | | :-------: | :-----------------: | :----: | | webserver | 需要绑定的webserver | | | path | 注册uri | | | username | 用户名称 | | | password | 用户密码 | | **返回** 无 #### Arduino OTA更新流程: - 连接WiFi - 配置 ArduinoOTA 对象的事件函数 - 启动 ArduinoOTA 服务 ArduinoOTA.begin() - 在 loop() 函数将处理权交由 ArduinoOTA.handle() 项目的代码可以在OTA代码上增加,从而保留OTA功能。也可以通过按钮、软件控制等方式让主板在开机时选择运行正常工作模式或更新模式。 ```c++ void loop() { if (flag ==0 ) { // 正常工作状态的代码 } else { ArduinoOTA.handle(); } } ``` #### 例程:BasicOTA (参考Arduino IDE例程 Examples -> Examples for Edge101WE ->ArduinoOTA\examples\BasicOTA) ```c++ #include #include #include #include const char* ssid = "your_ssid"; const char* password = "your_password"; void setup() { Serial.begin(115200); Serial.println("Booting"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] // ArduinoOTA.setHostname("myesp32"); // No authentication by default // 密码设置,默认无认证方式 // ArduinoOTA.setPassword("admin"); // Password can be set with it's md5 value as well // MD5编码 建议用这个方法更好 // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); ArduinoOTA.onStart([]() { String type; //判断OTA内容 if (ArduinoOTA.getCommand() == U_FLASH) type = "sketch"; else // U_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) .onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); ArduinoOTA.begin(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { ArduinoOTA.handle(); // 处理固件更新,在loop方法中不断检测调用 } ``` 串口打印出IP地址 ``` Booting Ready IP address: 192.168.0.242 ``` 修改代码,在loop中增加串口打印 ```c++ void loop() { ArduinoOTA.handle(); Serial.println("The code has changed"); delay(100); } ``` Port 选择 新出现的 IP地址为前面串口打印出地址。点击上传程序。 ![image-20210514123530986](./pictures/image-20210514123530986.png) 上传成功后,切换为COM串口方式,可在串口终端打印出程序修改程序后的结果。 ``` rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0030,len:1420 ho 0 tail 12 room 4 load:0x40078000,len:13540 load:0x40080400,len:3604 entry 0x400805f0 Booting Ready IP address: 192.168.0.242 The code has changed The code has changed The code has changed The code has changed ``` #### 例程:OTAWebUpdater OTA Web Updater 方式适合有一定经验的最终客户使用,通过网页的形式进行升级。 其操作过程如下: - 用 Edge101WE 主板先建立一个Web服务器然后提供一个web更新界面。 - 通过Arduino将源文件编译为.bin的二进制文件。 - 通过mDNS功能在浏览器中访问主板的服务器页面,默认服务地址为:http://esp32.local - 通过Web界面将本地编译好的.bin二进制固件文件上传到主板中。 - 上传完成编译文件后主板将固件写入Flash中。 (参考Arduino IDE例程 Examples -> Examples for Edge101WE ->ArduinoOTA\examples\OTAWebUpdater) ```c++ #include #include #include #include #include const char* host = "esp32"; const char* ssid = "your_ssid"; const char* password = "your_password"; WebServer server(80); /* * Login page */ const char* loginIndex = "
" "" "" "" "
" "
" "" "" "" "" "" "
" "
" "" "" "" "
" "
" "" "" "" "" "
" "
ESP32 Login Page
" "
" "
Username:
Password:
" "
" ""; /* * Server Index Page */ const char* serverIndex = "" "
" "" "" "
" "
progress: 0%
" ""; /* * setup function */ void setup(void) { Serial.begin(115200); // Connect to WiFi network WiFi.begin(ssid, password); Serial.println(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); /*use mdns for host name resolution*/ if (!MDNS.begin(host)) { // http://esp32.local Serial.println("Error setting up MDNS responder!"); while (1) { delay(1000); } } Serial.println("mDNS responder started"); /*return index page which is stored in serverIndex */ server.on("/", HTTP_GET, []() { server.sendHeader("Connection", "close"); server.send(200, "text/html", loginIndex); }); server.on("/serverIndex", HTTP_GET, []() { server.sendHeader("Connection", "close"); server.send(200, "text/html", serverIndex); }); /*handling uploading firmware file */ server.on("/update", HTTP_POST, []() { server.sendHeader("Connection", "close"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.printf("Update: %s\n", upload.filename.c_str()); if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { /* flashing firmware to ESP*/ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { // true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } } }); server.begin(); Serial.printf("UpdateServer ready! Open http://%s.local in your browser\n", host); } void loop(void) { server.handleClient(); delay(1); } ``` 登录 http://esp32.local 输入程序中设置好的用户名 admin ,密码 admin。 ![image-20210518142343280](./pictures/image-20210518142343280.png) 点击登录,此时进入固件上传界面 ![image-20210518143324975](./pictures/image-20210518143324975.png) 修改loop函数 ```c++ void loop(void) { server.handleClient(); Serial.println("The code has changed"); delay(100); } ``` 点击Sketch -> Export compiled Binary,编译并导出.bin文件 ![image-20210518142954113](./pictures/image-20210518142954113.png) 编译完成后显示Done compilng,在信息窗可看到.bin文件所在的文件夹 ![image-20210518143959210](./pictures/image-20210518143959210.png) 点击选择OTAWebUpdater.ino.bin文件,点击Update上传。 ![image-20210518143324975](./pictures/image-20210518143324975.png) ![image-20210518144220481](./pictures/image-20210518144220481.png) ### 13.1.2 Web Update Web Update 用于在局域网内,远程更新主板固件。 #### 例程:WebUpdate (参考Arduino IDE例程 Examples -> Examples for Edge101WE ->WebServer\examples\WebUpdate) ```c++ /* To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update */ #include #include #include #include #include const char* host = "esp32-webupdate"; const char *ssid = "your_ssid"; const char *password = "your_password"; WebServer server(80); const char* serverIndex = "
"; void setup(void) { Serial.begin(115200); Serial.println(); Serial.println("Booting Sketch..."); WiFi.mode(WIFI_AP_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() == WL_CONNECTED) { MDNS.begin(host); server.on("/", HTTP_GET, []() { server.sendHeader("Connection", "close"); server.send(200, "text/html", serverIndex); }); server.on("/update", HTTP_POST, []() { server.sendHeader("Connection", "close"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.setDebugOutput(true); Serial.printf("Update: %s\n", upload.filename.c_str()); if (!Update.begin()) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } Serial.setDebugOutput(false); } else { Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status); } }); server.begin(); MDNS.addService("http", "tcp", 80); Serial.printf("Ready! Open http://%s.local in your browser\n", host); } else { Serial.println("WiFi Failed"); } } void loop(void) { server.handleClient(); delay(2);//allow the cpu to switch to other tasks } ``` 修改程序的里面的 ssid 和 password,通过USB口烧写代码到 Edge101WE 主板。 烧写完成后,串口打印出 URL:http://esp32-webupdate.local ![image-20210423140357947](./pictures/image-20210423140357947.png) 使用网页浏览器访问这个网址。 ![image-20210423140519937](./pictures/image-20210423140519937.png) 此时我们将代码 WebUpdate 另存为 WebUpdateTest 项目,在 loop 循环增加 Serial.println("The code has been modified"); 语句。 ![image-20210423141027554](./pictures/image-20210423141027554.png) 在 Arduino IDE 点击 Sketch --- Export compiled Binary,导出bin二进制文件。 ![image-20210423155817809](./pictures/image-20210423155817809.png) 此时 Arduino IDE 将执行编译。编译完成后可以看到 bin 文件的位置。 ![image-20210423160620011](./pictures/image-20210423160620011.png) 浏览器打开 http://esp32-webupdate.local 点击 **选择文件** 按钮 ![image-20210423141818568](./pictures/image-20210423141818568.png) 在刚才串口打印出的文件夹位置选择 WebUpdateTest.ino.bin ,然后点击 **Update** 。 ![image-20210423141648360](./pictures/image-20210423141648360.png) 等待更新完成将 会显示 **OK** ![image-20210423155101697](./pictures/image-20210423155101697.png) 在串口打印出的 debug 信息也可以看到 Update 的过程。最终会在串口持续打印 The code has been modified。说明新代码运行起来了。 这样我们就可以通过无线的形式来更新代码,这对于已经安装到现场的产品的程序更新非常有用。 ![image-20210423155342505](./pictures/image-20210423155342505.png) **注意:如果更新了不带 Web Update功能的代码,将不能进行下一次的 Web Update。** ### 13.1.3 HTTP Update #### 例程:HTTP Update 通过本地更新程序 ```c++ #include #include #include #include #include WiFiMulti WiFiMulti; void setup() { Serial.begin(115200); // Serial.setDebugOutput(true); Serial.println(); Serial.println(); Serial.println(); for (uint8_t t = 4; t > 0; t--) { Serial.printf("[SETUP] WAIT %d...\n", t); Serial.flush(); delay(1000); } WiFi.mode(WIFI_STA); WiFiMulti.addAP("your_ssid", "your_pasword"); while (WiFiMulti.run() != WL_CONNECTED) //等待网络连接成功 { delay(500); Serial.print("."); } Serial.println("Connected"); Serial.print("IP Address:"); Serial.println(WiFi.localIP()); } void loop() { // wait for WiFi connection if ((WiFiMulti.run() == WL_CONNECTED)) { WiFiClient client; // The line below is optional. It can be used to blink the LED on the board during flashing // The LED will be on during download of one buffer of data from the network. The LED will // be off during writing that buffer to flash // On a good connection the LED should flash regularly. On a bad connection the LED will be // on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second // value is used to put the LED on. If the LED is on with HIGH, that value should be passed // httpUpdate.setLedPin(LED_BUILTIN, LOW); t_httpUpdate_return ret = httpUpdate.update(client, "http://firmware.oss-cn-beijing.aliyuncs.com/httpUpdate.ino.bin"); // Or: // t_httpUpdate_return ret = httpUpdate.update(client, "http://192.168.0.56", 80, "/httpUpdate.ino.bin"); switch (ret) { case HTTP_UPDATE_FAILED: Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); break; case HTTP_UPDATE_NO_UPDATES: Serial.println("HTTP_UPDATE_NO_UPDATES"); break; case HTTP_UPDATE_OK: Serial.println("HTTP_UPDATE_OK"); break; } } delay(100); Serial.println("Code version: v0.1"); // 修改打印内容,通过 httpUpdate方式更新 } ``` ![image-20210510121852823](./pictures/image-20210510121852823.png) 修改打印内容,代码版本 v1.2 ![image-20210510121818732](./pictures/image-20210510121818732.png) 点击 Export compiled Binary 编译出bin文件 ![image-20210510122130598](./pictures/image-20210510122130598.png) 编译出的 bin文件地址可在编译对话框中找到 ![image-20210510120304814](./pictures/image-20210510120304814.png) 注册阿里云账户,进入对象存储OSS页面 https://oss.console.aliyun.com/ 创建一个Bucket,选项如下 ![image-20210518154421328](./pictures/image-20210518154421328.png) 在阿里OSS上传文件 ![image-20210518155811922](./pictures/image-20210518155811922.png) 点击详情 ![image-20210518160030802](./pictures/image-20210518160030802.png) 关闭HTTPS,拷贝URL,修改程序中的连接 ![image-20210518160417972](./pictures/image-20210518160417972.png) 将 httpUpdate.ino.bin 拖动到 HFS软件处 ![image-20210510120131192](./pictures/image-20210510120131192.png) ### 13.2.4 HTTPS_OTA_Update 该例程需要搭配一个可以提供访问并且带有bin文件的网址链接 #### 例程:HTTPS_OTA_Update ```c++ // This sketch provide the functionality of OTA Firmware Upgrade #include "WiFi.h" #include "HttpsOTAUpdate.h" // This sketch shows how to implement HTTPS firmware update Over The Air. // Please provide your WiFi credentials, https URL to the firmware image and the server certificate. static const char *ssid = "your-ssid"; // your network SSID (name of wifi network) static const char *password = "your-password"; // your network password static const char *url = "https://example.com/firmware.bin"; //state url of your firmware image static const char *server_certificate = "-----BEGIN CERTIFICATE-----\n" \ "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ "-----END CERTIFICATE-----"; static HttpsOTAStatus_t otastatus; void HttpEvent(HttpEvent_t *event) { switch(event->event_id) { case HTTP_EVENT_ERROR: Serial.println("Http Event Error"); break; case HTTP_EVENT_ON_CONNECTED: Serial.println("Http Event On Connected"); break; case HTTP_EVENT_HEADER_SENT: Serial.println("Http Event Header Sent"); break; case HTTP_EVENT_ON_HEADER: Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value); break; case HTTP_EVENT_ON_DATA: break; case HTTP_EVENT_ON_FINISH: Serial.println("Http Event On Finish"); break; case HTTP_EVENT_DISCONNECTED: Serial.println("Http Event Disconnected"); break; } } void setup(){ Serial.begin(115200); Serial.print("Attempting to connect to SSID: "); WiFi.begin(ssid, password); // attempt to connect to Wifi network: while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.print("Connected to "); Serial.println(ssid); HttpsOTA.onHttpEvent(HttpEvent); Serial.println("Starting OTA"); HttpsOTA.begin(url, server_certificate); Serial.println("Please Wait it takes some time ..."); } void loop(){ otastatus = HttpsOTA.status(); if(otastatus == HTTPS_OTA_SUCCESS) { Serial.println("Firmware written successfully. To reboot device, call API ESP.restart() or PUSH restart button on device"); } else if(otastatus == HTTPS_OTA_FAIL) { Serial.println("Firmware Upgrade Fail"); } delay(1000); } ``` ### 13.2.5 httpUpdateSPIFFS 该例程需要搭配一个可以提供访问并且带有bin文件的网址链接 #### 例程:httpUpdateSPIFFS ```c++ /** httpUpdateSPIFFS.ino Created on: 05.12.2015 */ #include #include #include #include #include WiFiMulti WiFiMulti; void setup() { Serial.begin(115200); // Serial.setDebugOutput(true); Serial.println(); Serial.println(); Serial.println(); for (uint8_t t = 4; t > 0; t--) { Serial.printf("[SETUP] WAIT %d...\n", t); Serial.flush(); delay(1000); } WiFi.mode(WIFI_STA); WiFiMulti.addAP("SSID", "PASSWORD"); } void loop() { // wait for WiFi connection if ((WiFiMulti.run() == WL_CONNECTED)) { Serial.println("Update SPIFFS..."); WiFiClient client; // The line below is optional. It can be used to blink the LED on the board during flashing // The LED will be on during download of one buffer of data from the network. The LED will // be off during writing that buffer to flash // On a good connection the LED should flash regularly. On a bad connection the LED will be // on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second // value is used to put the LED on. If the LED is on with HIGH, that value should be passed // httpUpdate.setLedPin(LED_BUILTIN, LOW); t_httpUpdate_return ret = httpUpdate.updateSpiffs(client, "http://server/spiffs.bin"); if (ret == HTTP_UPDATE_OK) { Serial.println("Update sketch..."); ret = httpUpdate.update(client, "http://server/file.bin"); switch (ret) { case HTTP_UPDATE_FAILED: Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); break; case HTTP_UPDATE_NO_UPDATES: Serial.println("HTTP_UPDATE_NO_UPDATES"); break; case HTTP_UPDATE_OK: Serial.println("HTTP_UPDATE_OK"); break; } } } } ``` ## 13.2 FreeRTOS 多任务调度 ### API参考 #### xTaskCreatePinnedToCore() - 创建任务到指定CPU内核 xTaskCreate() 函数无法指定CPU内核,如果要创建任务到指定内核可使用这个函数。 对于在运行看门狗的CPU内核上运行的任务,需要定期调用delay(1)等。 **语法** ```c++ BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, const char *const pcName, const uint32_t usStackDepth, void *const pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pvCreatedTask, const BaseType_t xCoreID) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :----------------------------------------------------------: | :----: | | pvTaskCode | 指向任务函数的指针。指定不以无限循环结尾的函数 | | | pcName | 任务的描述性名称。即使重叠,它也可以工作,但是它是用于调试的。最多16个字符 | | | usStackDepth | 堆栈大小(字节) | | | pvParameters | 指向创建任务的参数的指针 | | | uxPriority | 创建任务优先级0~25(0:低,25:高) | | | pvCreatedTask | 指向创建任务的handle的指针 | | | xCoreID | CPU内核(0-1) | | **返回** | 返回值 | 说明 | 值范围 | | :--------: | :------- | ------ | | BaseType_t | 执行结果 | | **用法示例** ```c++ #define userLED 15 // 定义用户LED为GPIO0 #define userButton 38 // 定义用户按钮为GPIO38 // define two tasks for Blink & AnalogRead void TaskBlink( void *pvParameters ); void TaskAnalogReadA3( void *pvParameters ); // the setup function runs once when you press reset or power the board void setup() { // initialize serial communication at 115200 bits per second: Serial.begin(115200); // 创建任务在CPU核心0上运行 // Now set up two tasks to run independently. xTaskCreatePinnedToCore( TaskBlink // 任务对应的函数 , "TaskBlink" // 任务名。 A name just for humans , 1024 // 栈大小。This stack size can be checked & adjusted by reading the Stack Highwater , NULL // 传给任务函数的参数 , 2 // 任务优先级。Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. , NULL // 用来返回任务handle , 0 // 指定CPU核心,tskNO_AFFINITY表示不指定,数字0,1代表核心ID ); // 创建任务在CPU核心1上运行 xTaskCreatePinnedToCore( TaskDigitalRead38 , "DigitalRead38" , 1024 // Stack size , NULL , 1 // Priority , NULL , 1); // Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started. } void loop() { // 添加其他代码,Empty. Things are done in Tasks. } /*--------------------------------------------------*/ /*---------------------- Tasks ---------------------*/ /*--------------------------------------------------*/ void TaskBlink(void *pvParameters) // This is a task. { (void) pvParameters; // 初始用户LED引脚为输出 pinMode(userLED, OUTPUT); // 初始化userButton为输入 pinMode(userButton, INPUT); for (;;) // A Task shall never return or exit. { digitalWrite(userLED, HIGH); // turn the LED on (HIGH is the voltage level) vTaskDelay(100); // one tick delay (100ms) in between reads for stability digitalWrite(userLED, LOW); // turn the LED off by making the voltage LOW vTaskDelay(100); // one tick delay (100ms) in between reads for stability } } void TaskDigitalRead38(void *pvParameters) // This is a task. { (void) pvParameters; for (;;) { // print out the value you read: Serial.println(digitalRead(userButton)); vTaskDelay(10); // one tick delay (15ms) in between reads for stability } } ``` #### xTaskCreate() - 任务创建 相当于 xTaskCreatePinnedToCore() 函数的xCoreID参数设置为0。为了和过去的代码兼容。 **语法** ```c++ static IRAM_ATTR BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char *const pcName, const uint32_t usStackDepth, void *const pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pvCreatedTask) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :----------------------------------------------------------: | :----: | | pvTaskCode | 指向任务函数的指针。指定不以无限循环结尾的函数 | | | pcName | 任务的描述性名称。即使重叠,它也可以工作,但是它是用于调试的。最多16个字符 | | | usStackDepth | 堆栈大小(字节) | | | pvParameters | 指向创建任务的参数的指针 | | | uxPriority | 创建任务优先级0~25(0:低 ,25:高) | | | pvCreatedTask | 指向创建任务的handle的指针 | | **返回** | 返回值 | 说明 | 值范围 | | :------------------: | :------- | ------ | | BaseType_t IRAM_ATTR | 执行结果 | | #### vTaskDelete() - 删除任务 删除任务。 **语法** ```c++ void vTaskDelete(TaskHandle_t xTaskToDelete) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :--------------------------------------: | :----: | | xTaskToDelete | 任务handle。如果指定NULL,则指定调用任务 | | **返回** 无 **用法示例** ```c++ TaskHandle_t taskHandle[2]; void testTask(void *pvParameters) { while (1) { Serial.print( xPortGetCoreID() ); // 输出CPU核心ID delay(1000); } } void testTask2(void *pvParameters) { for( int i = 0 ; i < 5 ; i++ ) { Serial.print( xPortGetCoreID() ); // 输出CPU核心ID delay(1000); } // 基本上是无限循环,完成时删除任务 vTaskDelete(NULL); } void setup() { Serial.begin(115200); // Core0任务启动 xTaskCreatePinnedToCore( testTask, "testTask1", 8192, NULL, 1, &taskHandle[0], 0 ); // Core1任务启动 xTaskCreatePinnedToCore( testTask2, "testTask2", 8192, NULL, 1, &taskHandle[1], 1 ); // 10秒后删除testTask delay(10000); vTaskDelete(taskHandle[0]); // //删除任务,如果参数为NULL时表示删除当前执行的任务 } void loop() { } ``` #### vTaskDelay() - 任务相对延迟 暂停任务执行以执行其他任务。 调用vTaskDelay()函数后,任务会进入阻塞状态,持续时间由vTaskDelay()函数的参数xTicksToDelay指定,单位是系统节拍时钟周期Tick。常量portTICK_RATE_MS 用来辅助计算真实时间,此值是系统节拍时钟中断的周期,单位是毫秒。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelay 必须设置成1,此函数才能有效。 vTaskDelay()指定的延时时间是从调用vTaskDelay()后开始计算的相对时间。比如vTaskDelay(100),那么从调用vTaskDelay()后,任务进入阻塞状态,经过100个系统时钟节拍周期,任务解除阻塞。因此,vTaskDelay()并不适用与周期性执行任务的场合。此外,其它任务和中断活动,会影响到vTaskDelay()的调用(比如调用前高优先级任务抢占了当前任务),因此会影响任务下一次执行的时间。API函数vTaskDelayUntil()可用于固定频率的延时,它用来延时一个绝对时间。 **语法** ```c++ void vTaskDelay(const TickType_t xTicksToDelay) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :------------------------------------: | :----: | | xTicksToDelay | 延时时间总数,单位是系统时钟节拍周期。 | | **返回** 无 **用法示例** ```c++ void vTaskA( void * pvParameters ) { /* 阻塞500ms. 注:宏pdMS_TO_TICKS用于将毫秒转成节拍数,FreeRTOS V8.1.0及 以上版本才有这个宏,如果使用低版本,可以使用 500 / portTICK_RATE_MS */ const portTickType xDelay = pdMS_TO_TICKS(500); for( ;; ) { // ... // 这里为任务主体代码 // ... /* 调用系统延时函数,阻塞500ms */ vTaskDelay( xDelay ); } } ``` #### vTaskDelayUntil() - 定时任务绝对延迟 暂停任务执行以执行其他任务。 任务延时一个指定的时间。周期性任务可以使用此函数,以确保一个恒定的频率执行。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelayUntil 必须设置成1,此函数才有效。 这个函数不同于vTaskDelay()函数的一个重要之处在于:vTaskDelay()指定的延时时间是从调用vTaskDelay()之后(执行完该函数)开始算起的,但是vTaskDelayUntil()指定的延时时间是一个绝对时间。 调用vTaskDelay()函数后,任务会进入阻塞状态,持续时间由vTaskDelay()函数的参数指定,单位是系统节拍时钟周期。因此vTaskDelay()并不适用于周期性执行任务的场合。因为调用vTaskDelay()到任务解除阻塞的时间不总是固定的并且该任务下一次调用vTaskDelay()函数的时间也不总是固定的(两次执行同一任务的时间间隔本身就不固定,中断或高优先级任务抢占也可能会改变每一次执行时间)。 vTaskDelay()指定一个从调用vTaskDelay()函数后开始计时,到任务解除阻塞为止的相对时间,而vTaskDelayUntil()指定一个绝对时间,每当时间到达,则解除任务阻塞。 应当指出的是,如果指定的唤醒时间已经达到,vTaskDelayUntil()立刻返回(不会有阻塞)。因此,使用vTaskDelayUntil()周期性执行的任务,无论任何原因(比如,任务临时进入挂起状态)停止了周期性执行,使得任务少运行了一个或多个执行周期,那么需要重新计算所需要的唤醒时间。这可以通过传递给函数的指针参数pxPreviousWake指向的值与当前系统时钟计数值比较来检测,在大多数情况下,这并不是必须的。 常量portTICK_RATE_MS 用来辅助计算真实时间,此值是系统节拍时钟中断的周期,单位是毫秒。 当调用vTaskSuspendAll()函数挂起RTOS调度器时,不可以使用此函数。 **语法** ```c++ void vTaskDelayUntil(TickType_t *const pxPreviousWakeTime, const TickType_t xTimeIncrement) ``` **参数** | 传入值 | 说明 | 值范围 | | :----------------: | :----------------------------------------------------------- | :----: | | pxPreviousWakeTime | 指针,指向一个变量,该变量保存任务最后一次解除阻塞的时间。第一次使用前,该变量必须初始化为当前时间。之后这个变量会在vTaskDelayUntil()函数内自动更新。 | | | xTimeIncrement | 周期循环时间。当时间等于(*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。如果不改变参数xTimeIncrement的值,调用该函数的任务会按照固定频率执行。 | | **返回** 无 **注意**: 在通过调用vTaskSuspendAll()挂起RTOS调度程序后,不得调用此函数。 **用法示例** ```c++ void vTaskFunction( void * pvParameters ) { TickType_t xLastWakeTime; const TickType_t xFrequency = 10; // Initialise the xLastWakeTime variable with the current time. xLastWakeTime = xTaskGetTickCount(); for( ;; ) { // Wait for the next cycle. vTaskDelayUntil( &xLastWakeTime, xFrequency ); // Perform action here. } } ``` #### uxTaskPriorityGet() - 获取优先级 获取任务优先级。 **语法** ```c++ UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask) ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :----------------------------------------: | :----: | | xTask | 任务handle。如果指定NULL,则指定调用任务。 | | **返回** | 返回值 | 说明 | 值范围 | | :---------: | :----- | ------ | | UBaseType_t | 优先级 | | #### uxTaskPriorityGetFromISR() - 获取中断优先级 从ISR(中断)获得可以使用的优先级。 **语法** ```c++ UBaseType_t uxTaskPriorityGetFromISR(TaskHandle_t xTask) ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :----------------------------------------: | :----: | | xTask | 任务handle。如果指定NULL,则指定调用任务。 | | **返回** | 返回值 | 说明 | 值范围 | | :---------: | :----- | ------ | | UBaseType_t | 优先级 | | #### eTaskGetState() - 获取任务状态 获取任务的状态。 **语法** ```c++ eTaskState eTaskGetState(TaskHandle_t xTask) ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :------------------: | :----: | | xTask | 任务句柄。不允许为空 | | **返回** | 返回值 | 说明 | 值范围 | | :--------: | :----------------------------------------------------------- | ------ | | eTaskState | 状态(eRunning:0,eReady:1,eBlocked:2,eSuspended:3,eDeleted:4) | | #### vTaskPrioritySet() - 任务优先级设置 如果设置的优先级高于当前执行的任务,则在函数返回之前将进行上下文切换。 **语法** ```c++ void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :----------------------------------------: | :----: | | xTask | 任务handle。如果指定NULL,则指定调用任务。 | | | uxNewPriority | 新的优先级 | | **返回** 无 #### vTaskSuspend() - 挂起任务 挂起任务。 void vTaskSuspend(TaskHandle_t xTaskToSuspend) PRIVILEGED_FUNCTION 争论 TaskHandle_txTaskToSuspend任务句柄。如果指定NULL,则指定调用任务。 **语法** ```c++ void vTaskSuspend(TaskHandle_t xTaskToSuspend) ``` **参数** | 传入值 | 说明 | 值范围 | | :------------: | :----------------------------------------: | :----: | | xTaskToSuspend | 任务handle。如果指定NULL,则指定调用任务。 | | **返回** 无 #### vTaskResume() - 恢复已停止的任务 恢复已停止的任务。 void vTaskResume(TaskHandle_t xTaskToResume) PRIVILEGED_FUNCTION 争论 TaskHandle_txTaskToResume任务句柄。不允许为空 **语法** ```c++ void vTaskResume(TaskHandle_t xTaskToResume) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :------------------: | :----: | | xTaskToResume | 任务句柄。不允许为空 | | **返回值** 无 #### xTaskResumeFromISR() - 中断任务恢复 恢复被中断停止的任务。 **语法** ```c++ BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :--------------------: | :----: | | xTaskToResume | 任务句柄。不允许为空。 | | **返回** | 返回值 | 说明 | 值范围 | | :--------: | :------- | ------ | | BaseType_t | 执行结果 | | #### vTaskSuspendAll() - 所有任务挂起 挂起所有任务。它用于暂时停止其他任务的执行。使用它后,请使用xTaskResumeAll()重新启动它。 **语法** ```c++ void vTaskSuspendAll(void) ``` **参数** 无 **返回** 无 #### xTaskResumeAll() - 恢复所有任务 恢复所有任务。 BaseType_t xTaskResumeAll(void) PRIVILEGED_FUNCTION 返回值 BaseType_t执行结果 **语法** ```c++ BaseType_t xTaskResumeAll(void) ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :--------: | :------- | ------ | | BaseType_t | 执行结果 | | #### xTaskGetTickCount() - 获取任务启动时间 获取任务已开始的Tick。 TickType_t xTaskGetTickCount(void) PRIVILEGED_FUNCTION 返回值 任务启动的滴答数 **语法** ```c++ TickType_t xTaskGetTickCount(void) ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :--------: | :--------------- | ------ | | TickType_t | 任务启动的Tick数 | | #### xTaskGetTickCountFromISR() - 中断任务启动时间 获取中断任务已触发的Tick。 TickType_t xTaskGetTickCountFromISR(void) PRIVILEGED_FUNCTION 返回值 TickType_t任务启动的滴答数 **语法** ```c++ TickType_t xTaskGetTickCountFromISR(void) ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :--------: | :--------------- | ------ | | TickType_t | 任务启动的Tick数 | | #### uxTaskGetNumberOfTasks() - 获取任务数 获取任务数。 UBaseType_t uxTaskGetNumberOfTasks(void) PRIVILEGED_FUNCTION 返回值 UBaseType_t任务数 **语法** ```c++ UBaseType_t uxTaskGetNumberOfTasks(void) ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :---------: | :----- | ------ | | UBaseType_t | 任务数 | | #### pcTaskGetTaskName() - 获取任务名称 获取任务的名称。 **语法** ```c++ char* pcTaskGetTaskName(TaskHandle_t xTaskToQuery) ``` **参数** | 传入值 | 说明 | 值范围 | | :----------: | :--------------------------------------: | :----: | | xTaskToQuery | 任务句柄。如果指定NULL,则指定调用任务。 | | **返回** | 返回值 | 说明 | 值范围 | | :----: | :------: | ------ | | char * | 任务名称 | | #### uxTaskGetStackHighWaterMark() - 获取剩余的堆栈 获取任务中最少的堆栈字节数。距离0越近,溢出的可能性就越大。 **语法** ```c++ UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask) ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :--------------------------------------: | :----: | | xTask | 任务句柄。如果指定NULL,则指定调用任务。 | | **返回** | 返回值 | 说明 | 值范围 | | :---------: | :--------: | ------ | | UBaseType_t | 堆栈字节数 | | #### pxTaskGetStackStart() - 获取堆栈起始地址 获取堆栈的起始地址。 **语法** ```c++ uint8_t* pxTaskGetStackStart(TaskHandle_t xTask) ``` **参数** | 传入值 | 说明 | 值范围 | | :----: | :--------------------------------------: | :----: | | xTask | 任务句柄。如果指定NULL,则指定调用任务。 | | **返回** | 返回值 | 说明 | 值范围 | | :------: | :----------: | ------ | | uint8_t* | 堆栈起始地址 | | #### xTaskGetIdleTaskHandle() - 获取空闲任务 获取当前正在运行的CPU内核的空闲任务句柄。 **语法** ```c++ TaskHandle_t xTaskGetIdleTaskHandle(void) ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :----------: | :------: | ------ | | TaskHandle_t | 任务句柄 | | #### xTaskGetIdleTaskHandleForCPU() - CPU核心规范空闲任务获取 指定CPU内核以获取空闲任务句柄。 仅在INCLUDE_xTaskGetIdleTaskHandle设置为1 时可用。 **语法** ```c++ TaskHandle_t xTaskGetIdleTaskHandleForCPU(UBaseType_t cpuid) ``` **参数** | 传入值 | 说明 | 值范围 | | :---------: | :------------------: | :----: | | UBaseType_t | cpuidCPU核心(0,1) | | **返回** | 返回值 | 说明 | 值范围 | | :----------: | :------: | ------ | | TaskHandle_t | 任务句柄 | | #### xTaskNotify() - 任务通知 向任务发送通知。 不得从中断服务程序(ISR)调用此函数。请改用 xTaskNotifyFromISR()。 **语法** ```c++ BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :----------------------------------------------------------- | :----: | | xTaskToNotify | 通知任务的句柄。这是目标任务 | | | ulValue | 要通知的数据。通知方法因eAction而异 | | | eAction | 指定通知如何更新任务的通知值(如果有的话)。eAction的有效值如下:
eNoAction = 0 不更新通知值,在这种情况下,不使用ulValue。
eSetBits = 1 目标任务的通知值将与ulValue按位或。例如,如果ulValue设置为0x01,则将在目标任务的通知值内设置位0。同 样,如果ulValue为0x04,则将在目标任务的通知值中设置位2
eIncrement = 2 目标任务的通知值将增加一,从而使对xTaskNotify()的调用等效于对 xTaskNotifyGive()的调用。在这种情况下,不使用ulValue。
eSetValueWithOverwrite = 3 目标任务的通知值无条件设置为ulValue
eSetValueWithoutOverwrite = 4 如果目标任务尚未有待处理的通知,则其通知值将设置为ulValue。如果目标任务已经有待处理的通知,则其通知值不会更新, | | **返回** | 返回值 | 说明 | 值范围 | | :--------: | :------: | ------ | | BaseType_t | 执行结果 | | **用法示例** ```c++ TaskHandle_t taskHandle; void testTask(void *pvParameters) { uint32_t ulNotifiedValue; while (1) { // 等待通知。不清除值 xTaskNotifyWait( 0, 0, &ulNotifiedValue, portMAX_DELAY ); Serial.println( pcTaskGetTaskName(NULL) ); Serial.println( ulNotifiedValue ); } } void setup() { Serial.begin(115200); // Core0任务启动 xTaskCreatePinnedToCore( testTask, "loopTask1", 8192, NULL, 1, &taskHandle, 0 ); } void loop() { delay(500); // 发送通知(值从0增加,在这种情况下,不使用这个ulValue值100) xTaskNotify(taskHandle, 100, eIncrement ); } ``` #### xTaskNotifyFromISR() - 中断任务通知 在中断时向任务发送通知。 **语法** ```c++ aseType_t xTaskNotifyFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken) ``` **参数** | 传入值 | 说明 | 值范围 | | :------------------------: | :----------------------------------------------------------: | :----: | | xTaskToNotify | 通知任务的句柄。这是目标任务 | | | ulValue | 要通知的数据。通知方法因eAction而异 | | | eAction | 指定通知如何更新任务的通知值(如果有的话)。eAction的有效值如下:
eNoAction = 0 不更新通知值,在这种情况下,不使用ulValue。
eSetBits = 1 目标任务的通知值将与ulValue按位或。例如,如果ulValue设置为0x01,则将在目标任务的通知值内设置位0。同 样,如果ulValue为0x04,则将在目标任务的通知值中设置位2
eIncrement = 2 目标任务的通知值将增加一,从而使对xTaskNotify()的调用等效于对 xTaskNotifyGive()的调用。在这种情况下,不使用ulValue。
eSetValueWithOverwrite = 3 目标任务的通知值无条件设置为ulValue
eSetValueWithoutOverwrite = 4 如果目标任务尚未有待处理的通知,则其通知值将设置为ulValue。如果目标任务已经有待处理的通知,则其通知值不会更新, | | | *pxHigherPriorityTaskWoken | 如果向其发送通知的任务退出阻塞状态,并且具有比当前正在运行的任务更高的优先级,则设置为pdTRUE。
返回值 | | **返回** | 返回值 | 说明 | 值范围 | | :--------: | :------: | ------ | | BaseType_t | 执行结果 | | #### xTaskNotifyWait() - 获取任务通知 等待任务通知。 **语法** ```c++ BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait) ``` **参数** | 传入值 | 说明 | 值范围 | | :-------------------: | :----------------------------------------------------------: | :----: | | ulBitsToClearOnEntry | 在调用uint32_t之前清除的值的位。0不重置,使用ULONG_MAX重置为0 | | | ulBitsToClearOnExit | 在uint32_t函数完成之前清除的值的位。0不重置,使用ULONG_MAX重置为0 | | | *pulNotificationValue | 通知中使用的变量的指针 | | | xTicksToWait | 最大等待时间。使用pdMS_TO_TICSK(value_in_ms)宏指定,或无限期等待portMAX_DELAY | | **返回** | 返回值 | 说明 | 值范围 | | :--------: | :------: | ------ | | BaseType_t | 执行结果 | | #### xTaskNotifyGive() - 简单任务通知 向任务发送通知。一个内部调用xTaskNotify((xTaskToNotify),0,eIncrement)的内联函数。 **语法** ```c++ BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify) ``` **参数** | 传入值 | 说明 | 值范围 | | :-----------: | :----------: | :----: | | xTaskToNotify | 任务处理程序 | | **返回值** | 返回值 | 说明 | 值范围 | | :--------: | :------: | ------ | | BaseType_t | 执行结果 | | #### vTaskNotifyGiveFromISR() - 中断简单任务通知 中断将通知发送给任务。 **语法** ```c++ void vTaskNotifyGiveFromISR(TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken) ``` **参数** | 传入值 | 说明 | 值范围 | | :------------------------: | :----------------------------------------------------------: | :----: | | xTaskToNotify | 任务处理程序 | | | *pxHigherPriorityTaskWoken | 如果向其发送通知的任务退出阻塞状态,并且具有比当前正在运行的任务更高的优先级,则设置为pdTRUE。 | | **返回** 无 #### ulTaskNotifyTake() - 获取简单的任务通知 uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait) 争论 BaseType_t对于xClearCountOnExitpdFALSE,通知值减小,对于pdTRUE,通知值被清除 TickType_txTicksToWait最大等待时间。使用pdMS_TO_TICSK(value_in_ms)宏指定,或无限期等待portMAX_DELAY 返回值 uint32_t通知值 **语法** ```c++ uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait) ``` **参数** | 传入值 | 说明 | 值范围 | | :---------------: | :----------------------------------------------------------: | :----: | | xClearCountOnExit | pdFALSE,通知值减小。pdTRUE,通知值被清除。 | | | xTicksToWait | 最大等待时间。使用pdMS_TO_TICSK(value_in_ms)宏指定,或无限期等待portMAX_DELAY | | **返回** | 返回值 | 说明 | 值范围 | | :------: | :----: | ------ | | uint32_t | 通知值 | | **用法示例** ```c++ TaskHandle_t taskHandle; void testTask(void *pvParameters) { while (1) { // 等待通知。因为不清除Value,所以只要收到通知就保持1 int ulNotifiedValue = ulTaskNotifyTake( pdFALSE, portMAX_DELAY ); Serial.println( pcTaskGetTaskName(NULL) ); Serial.println( ulNotifiedValue ); } } void setup() { Serial.begin(115200); // Core0任务启动 xTaskCreatePinnedToCore( testTask, "loopTask1", 8192, NULL, 1, &taskHandle, 0 ); } void loop() { delay(500); // 发送通知(值从0增加,在这种情况下,不使用这个ulValue值100) xTaskNotify(taskHandle, 100, eIncrement ); } ``` ## 13.3 复位原因 ResetReason 用于复位后的判断,便于选择采取的措施。 ### 例程:ResetReason ```c++ /* * Print last reset reason of ESP32 * ================================= * * Use either of the methods print_reset_reason * or verbose_print_reset_reason to display the * cause for the last reset of this device. * * Public Domain License. * * Author: * Evandro Luis Copercini - 2017 */ #ifdef ESP_IDF_VERSION_MAJOR // IDF 4+ #if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 #include "esp32/rom/rtc.h" #elif CONFIG_IDF_TARGET_ESP32S2 #include "esp32s2/rom/rtc.h" #elif CONFIG_IDF_TARGET_ESP32C3 #include "esp32c3/rom/rtc.h" #else #error Target CONFIG_IDF_TARGET is not supported #endif #else // ESP32 Before IDF 4.0 #include "rom/rtc.h" #endif #define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */ void print_reset_reason(RESET_REASON reason) { switch ( reason) { case 1 : Serial.println ("POWERON_RESET");break; /**<1, Vbat power on reset*/ case 3 : Serial.println ("SW_RESET");break; /**<3, Software reset digital core*/ case 4 : Serial.println ("OWDT_RESET");break; /**<4, Legacy watch dog reset digital core*/ case 5 : Serial.println ("DEEPSLEEP_RESET");break; /**<5, Deep Sleep reset digital core*/ case 6 : Serial.println ("SDIO_RESET");break; /**<6, Reset by SLC module, reset digital core*/ case 7 : Serial.println ("TG0WDT_SYS_RESET");break; /**<7, Timer Group0 Watch dog reset digital core*/ case 8 : Serial.println ("TG1WDT_SYS_RESET");break; /**<8, Timer Group1 Watch dog reset digital core*/ case 9 : Serial.println ("RTCWDT_SYS_RESET");break; /**<9, RTC Watch dog Reset digital core*/ case 10 : Serial.println ("INTRUSION_RESET");break; /**<10, Instrusion tested to reset CPU*/ case 11 : Serial.println ("TGWDT_CPU_RESET");break; /**<11, Time Group reset CPU*/ case 12 : Serial.println ("SW_CPU_RESET");break; /**<12, Software reset CPU*/ case 13 : Serial.println ("RTCWDT_CPU_RESET");break; /**<13, RTC Watch dog Reset CPU*/ case 14 : Serial.println ("EXT_CPU_RESET");break; /**<14, for APP CPU, reseted by PRO CPU*/ case 15 : Serial.println ("RTCWDT_BROWN_OUT_RESET");break;/**<15, Reset when the vdd voltage is not stable*/ case 16 : Serial.println ("RTCWDT_RTC_RESET");break; /**<16, RTC Watch dog reset digital core and rtc module*/ default : Serial.println ("NO_MEAN"); } } void verbose_print_reset_reason(RESET_REASON reason) { switch ( reason) { case 1 : Serial.println ("Vbat power on reset");break; case 3 : Serial.println ("Software reset digital core");break; case 4 : Serial.println ("Legacy watch dog reset digital core");break; case 5 : Serial.println ("Deep Sleep reset digital core");break; case 6 : Serial.println ("Reset by SLC module, reset digital core");break; case 7 : Serial.println ("Timer Group0 Watch dog reset digital core");break; case 8 : Serial.println ("Timer Group1 Watch dog reset digital core");break; case 9 : Serial.println ("RTC Watch dog Reset digital core");break; case 10 : Serial.println ("Instrusion tested to reset CPU");break; case 11 : Serial.println ("Time Group reset CPU");break; case 12 : Serial.println ("Software reset CPU");break; case 13 : Serial.println ("RTC Watch dog Reset CPU");break; case 14 : Serial.println ("for APP CPU, reseted by PRO CPU");break; case 15 : Serial.println ("Reset when the vdd voltage is not stable");break; case 16 : Serial.println ("RTC Watch dog reset digital core and rtc module");break; default : Serial.println ("NO_MEAN"); } } void setup() { // put your setup code here, to run once: Serial.begin(115200); delay(2000); Serial.println("CPU0 reset reason:"); print_reset_reason(rtc_get_reset_reason(0)); verbose_print_reset_reason(rtc_get_reset_reason(0)); Serial.println("CPU1 reset reason:"); print_reset_reason(rtc_get_reset_reason(1)); verbose_print_reset_reason(rtc_get_reset_reason(1)); // Set ESP32 to go to deep sleep to see a variation // in the reset reason. Device will sleep for 5 seconds. esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); Serial.println("Going to sleep"); esp_deep_sleep(5 * uS_TO_S_FACTOR); } void loop() { // put your main code here, to run repeatedly: } /* Example Serial Log: ==================== rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 CPU0 reset reason: RTCWDT_RTC_RESET RTC Watch dog reset digital core and rtc module CPU1 reset reason: EXT_CPU_RESET for APP CPU, reseted by PRO CPU Going to sleep ets Jun 8 2016 00:22:57 rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 CPU0 reset reason: DEEPSLEEP_RESET Deep Sleep reset digital core CPU1 reset reason: EXT_CPU_RESET for APP CPU, reseted by PRO CPU Going to sleep ets Jun 8 2016 00:22:57 rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 CPU0 reset reason: DEEPSLEEP_RESET Deep Sleep reset digital core CPU1 reset reason: EXT_CPU_RESET for APP CPU, reseted by PRO CPU Going to sleep */ ``` 将程序下载到主板,打开串口监视器,按下板载复位按钮后可以看到如下的打印结果 ``` CPU0 reset reason: DEEPSLEEP_RESET Deep Sleep reset digital core CPU1 reset reason: EXT_CPU_RESET for APP CPU, reseted by PRO CPU Going to sleep ets Jul 29 2019 12:21:46 ``` ## 13.4 软件重置 通过调用 ESP 对象上的 restart 方法重新启动 Edge101WE 主板。此方法不接收任何参数并返回 void。 ### 例程:软件重启 ```c++ void setup() { Serial.begin(115200); Serial.println("Restarting in 10 seconds"); delay(10000); ESP.restart(); } void loop() {} ``` ## 13.5 芯片信息 ### API参考 #### getEfuseMac() 获取 ESP32 芯片的 MAC 地址。 **语法** ```c++ uint64_t EspClass::getEfuseMac(void); ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :------: | :-----: | ------ | | uint64_t | MAC地址 | | #### getChipModel() 获取ESP32芯片模型。 **语法** ```c++ const char * EspClass::getChipModel(void) ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :----------: | :------: | ------ | | const char * | 芯片模型 | | #### getChipRevision() 获取ESP32芯片版本信息。 **语法** ```c++ uint64_t EspClass::getEfuseMac(void); ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :------: | :--------: | ------ | | uint64_t | 芯片修订版 | | #### getChipCores() 获取ESP32芯片上的内核数。 **语法** ```c++ uint8_t EspClass::getChipCores(void) ``` **参数** 无 **返回** | 返回值 | 说明 | 值范围 | | :-----: | :--------------: | ------ | | uint8_t | 芯片中的内核数。 | | ### 例程:GetChipID ```c++ /* The true ESP32 chip ID is essentially its MAC address. This sketch provides an alternate chip ID that matches the output of the ESP.getChipId() function on ESP8266 (i.e. a 32-bit integer matching the last 3 bytes of the MAC address. This is less unique than the MAC address chip ID, but is helpful when you need an identifier that can be no more than a 32-bit integer (like for switch...case). created 2020-06-07 by cweinhofer with help from Cicicok */ uint32_t chipId = 0; void setup() { Serial.begin(115200); } void loop() { for(int i=0; i<17; i=i+8) { chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; } Serial.printf("ESP32 Chip model = %s Rev %d\n", ESP.getChipModel(), ESP.getChipRevision()); Serial.printf("This chip has %d cores\n", ESP.getChipCores()); Serial.print("Chip ID: "); Serial.println(chipId); delay(3000); } ``` 打印的芯片信息 ``` ESP32 Chip model = ESP32-D0WDQ5 Rev 1 This chip has 2 cores Chip ID: 4139185 ``` ## 13.6 量产程序 **介绍** 这一程序主要用于量产时为每一设备创建工厂 NVS(非易失性存储器)分区映像。NVS 分区映像由 CSV(逗号分隔值)文件生成,文件中包含了用户提供的配置项及配置值。 注意,该程序仅创建用于量产的二进制映像,您需要使用以下工具将映像烧录到设备上: esptool.py Flash 下载工具(仅适用于 Windows) 直接烧录程序 教程根据**espressif**官方提供的Flash 下载工具实现量产程序的烧录。 第一步 我们先打开**Arduino IDE**中编译和烧录信息 ![liangchan1](./pictures/liangchan1.png) 第二步 打开Arduino IDE中编译和烧录信息 ![liangchan2](./pictures/liangchan2.png) ​ 然后,我们来了解一下**Arduino IDE**里面烧录esp32程序的烧录信息。 ![liangchan3](./pictures/liangchan3.png) ​ 上图中1号标记,我们可以看到这里有对flash 以**0xe000**地址为首地址进行一次写入操作这里烧写的是**boot_app0.bin**,本二进制文件可在我们的SDK中找到,相对目录是**SDK-->FireBeetleMESH\hardware\esp32\0.0.6\tools\partitions。** 上图中2号标记,这里对**0x1000**地址烧写了一块内容,烧写的是**bootloader.bin**,SDK中提供了8种**bootloader.bin**,根据Arduino IDE里面的设置我们可以看到他选择的是**bootloader_qio_80m.bin**,如图4所示。相对目录**SDK-->FireBeetleMESH\hardware\esp32\0.0.6\tools\sdk\esp32\bin**。 ![liangchan4](./pictures/liangchan4.png) ​ 上图3号标记,这里是烧写的程序主体,烧写首地址是**0x10000**。我们怎么得到一个一个可执行的二进制文件呢?我们可以通过**Arduino IDE** 导出。如下图所示。 导出二进制文件 ![liangchan5](./pictures/liangchan5.png) 导出二进制文件位置 ![liangchan6](./pictures/liangchan6.png) 上图中4号标记,这里烧录的是flash的分区信息,烧写的首地址是0x8000,我们可以选择上图中根据程序编译生成的**xxx.ino.partitions.bin**.也可以选择SDK中默认的**default.bin**.相对目录是**SDK-->FireBeetleMESH\hardware\esp32\0.0.6\tools\partitions**.但是需要注意的是在更改了**Arduino IDE**中的**Partition Scheme**后(如下图所示),这里不能再选择使用**default.bin**。 ![liangchan7](./pictures/liangchan7.png) **了解到上面这些信息后,我们就可以着手进行一次烧录了** 我们先从**espressif**官网上下载Flash 下载工具 https://www.espressif.com/zh-hans/support/download/other-tools 。 ![liangchan8](./pictures/liangchan8.png) 打开flash_download_tool.exe,并如下图所示进行选择 ![liangchan9](./pictures/liangchan9.png) 接下来我们填写烧写的文件以及信息,如下图所示。 ![liangchan10](./pictures/liangchan10.png) ​ 进行一次正确的烧写 ![liangchan11](./pictures/liangchan11.png) 到这里,一次量产程序的烧写就完成了。