12.系统API

12.1 OTA升级

OTA —— Over the air update of the firmware,也就是无线固件更新,通过此方法我们可以不连接 USB,而通过无线方式下载程序。

Edge101WE 主板具备多种的 OTA 方式。

12.1.1 ArduinoOTA

ArduinoOTA 方式需要结合Arduino IDE,比较适合开发工程师使用。

API参考

onStart()

注册要在OTA更新开始时调用的回调函数。

语法

#include <ArduinoOTA.h>

ArduinoOTAClass& ArduinoOTAClass::onStart(THandlerFunction fn);

参数

传入值 说明 值范围
fn 回调函数。
THandlerFunction类型的定义如下。
typedef std::function THandlerFunction;

返回

返回值 说明 值范围
ArduinoOTAClass *this

需要预先定义Arduino OTA类的对象Arduino OTA,并使用该对象对其进行操作。

getCommand()

获取用于OTA更新的命令。

语法

#include <ArduinoOTA.h>

int ArduinoOTAClass::getCommand();

参数

返回

返回值 说明 值范围
U_FLASH或U_SPIFFS
handle()

处理OTA更新执行。

语法

#include <ArduinoOTA.h>

void ArduinoOTAClass::handle();

参数

返回

onEnd()

在OTA更新结束时注册要调用的回调函数。

语法

#include <ArduinoOTA.h>

ArduinoOTAClass& ArduinoOTAClass::onEnd(THandlerFunction fn);

参数

传入值 说明 值范围
fn 回调函数。
THandlerFunction类型的定义如下。
typedef std::function THandlerFunction;

返回

返回值 说明 值范围
ArduinoOTAClass *this
onError()

注册发生OTA更新错误时要执行的回调函数。

语法

#include <ArduinoOTA.h>

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更新中注册要在更新期间执行的回调函。

语法

#include <ArduinoOTA.h>

ArduinoOTAClass& ArduinoOTAClass::onProgress(THandlerFunction_Progress fn);

参数

传入值 说明 值范围
fn 回调函。
THandlerFunction_Progress类型的定义如下。
typedef std::function THandlerFunction_Progress;
第一个参数是固件当前数据大小,第二个参数是固件总大小。单位byte。

返回

返回值 说明 值范围
ArduinoOTAClass *this
setHostname()

设置用于OTA更新的主机名。如果未设置,默认值为esp32- [MAC address]。主要用于mDNS的域名映射。

语法

#include <ArduinoOTA.h>

ArduinoOTAClass& ArduinoOTAClass::setHostname(const char * hostname);

参数

传入值 说明 值范围
hostname 主机名

返回

返回值 说明 值范围
ArduinoOTAClass *this

注意:在begin方法之前调用。

setPassword()

设置用于OTA更新的访问密码。如果未设置,将不使用密码。

语法

#include <ArduinoOTA.h>

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更新的密码。如果未设置,将不使用密码。

语法

#include <ArduinoOTA.h>

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。

语法

#include <ArduinoOTA.h>

ArduinoOTAClass& ArduinoOTAClass::setPort(uint16_t port);

参数

传入值 说明 值范围
port 服务端口

返回

返回值 说明 值范围
ArduinoOTAClass *this

注意:在begin方法之前调用。

begin()

启动OTA更新服务。

语法

#include <ArduinoOTA.h>

void ArduinoOTAClass::begin();

参数

返回

handle()

处理固件更新,这个方法需要在loop方法中不断检测调用。

语法

void ArduinoOTAClass::handle() {
  if (_state == OTA_RUNUPDATE) {
     //处理固件传输更新
    _runUpdate();
    _state = OTA_IDLE;
  }
}

参数

返回

setRebootOnSuccess()

设置固件更新完毕是否自动重启。

语法

void setRebootOnSuccess(bool reboot);

参数

传入值 说明 值范围
reboot 设置成true,自动重启

返回

updateCredentials()

校验用户信息,用于OTAWebUpdater。

语法

void updateCredentials(const char * username, const char * password)

参数

传入值 说明 值范围
username 用户名称
password 用户密码

返回

setup()

配置WebOTA。

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功能。也可以通过按钮、软件控制等方式让主板在开机时选择运行正常工作模式或更新模式。

void loop() {
  if (flag ==0 ) {
    // 正常工作状态的代码
  } else {
    ArduinoOTA.handle();
 }
}

例程:BasicOTA

(参考Arduino IDE例程 Examples -> Examples for Edge101WE ->ArduinoOTA\examples\BasicOTA)

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

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中增加串口打印

void loop() {
  ArduinoOTA.handle();
  Serial.println("The code has changed");
  delay(100);
}

Port 选择 新出现的 IP地址为前面串口打印出地址。点击上传程序。

image-20210514123530986

上传成功后,切换为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)

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "your_ssid";
const char* password = "your_password";

WebServer server(80);

/*
 * Login page
 */

const char* loginIndex =
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
             "<td>Username:</td>"
             "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";

/*
 * Server Index Page
 */

const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

/*
 * 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

点击登录,此时进入固件上传界面

image-20210518143324975

修改loop函数

void loop(void) {
  server.handleClient();
  Serial.println("The code has changed");
  delay(100);
}

点击Sketch -> Export compiled Binary,编译并导出.bin文件

image-20210518142954113

编译完成后显示Done compilng,在信息窗可看到.bin文件所在的文件夹

image-20210518143959210

点击选择OTAWebUpdater.ino.bin文件,点击Update上传。

image-20210518143324975

image-20210518144220481

12.1.2 Web Update

参考:http://arduino.esp8266.com/Arduino/versions/2.0.0/doc/ota_updates/ota_updates.html#web-browser

https://lastminuteengineers.com/esp32-ota-web-updater-arduino-ide/

Web Update 用于在局域网内,远程更新主板固件。

例程:WebUpdate

(参考Arduino IDE例程 Examples -> Examples for Edge101WE ->WebServer\examples\WebUpdate)

/*
  To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
*/

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32-webupdate";
const char *ssid = "your_ssid";  
const char *password = "your_password";

WebServer server(80);
const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";

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

使用网页浏览器访问这个网址。

image-20210423140519937

此时我们将代码 WebUpdate 另存为 WebUpdateTest 项目,在 loop 循环增加 Serial.println(”The code has been modified”); 语句。

image-20210423141027554

在 Arduino IDE 点击 Sketch — Export compiled Binary,导出bin二进制文件。

image-20210423155817809

此时 Arduino IDE 将执行编译。编译完成后可以看到 bin 文件的位置。

image-20210423160620011

浏览器打开 http://esp32-webupdate.local 点击 选择文件 按钮

image-20210423141818568

在刚才串口打印出的文件夹位置选择 WebUpdateTest.ino.bin ,然后点击 Update

image-20210423141648360

等待更新完成将 会显示 OK

image-20210423155101697

在串口打印出的 debug 信息也可以看到 Update 的过程。最终会在串口持续打印 The code has been modified。说明新代码运行起来了。

这样我们就可以通过无线的形式来更新代码,这对于已经安装到现场的产品的程序更新非常有用。

image-20210423155342505

注意:如果更新了不带 Web Update功能的代码,将不能进行下一次的 Web Update。

12.1.3 HTTP Update

例程:HTTP Update 通过本地更新程序

#include <Arduino.h>

#include <WiFi.h>
#include <WiFiMulti.h>

#include <HTTPClient.h>
#include <HTTPUpdate.h>

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

修改打印内容,代码版本 v1.2

image-20210510121818732

点击 Export compiled Binary 编译出bin文件

image-20210510122130598

编译出的 bin文件地址可在编译对话框中找到

image-20210510120304814

注册阿里云账户,进入对象存储OSS页面 https://oss.console.aliyun.com/

创建一个Bucket,选项如下

image-20210518154421328

在阿里OSS上传文件

image-20210518155811922

点击详情

image-20210518160030802

关闭HTTPS,拷贝URL,修改程序中的连接

image-20210518160417972

将 httpUpdate.ino.bin 拖动到 HFS软件处

image-20210510120131192

12.2.4 HTTPS_OTA_Update

该例程需要搭配一个可以提供访问并且带有bin文件的网址链接

例程:HTTPS_OTA_Update

// 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);
}

12.2.5 httpUpdateSPIFFS

该例程需要搭配一个可以提供访问并且带有bin文件的网址链接

例程:httpUpdateSPIFFS

/**
   httpUpdateSPIFFS.ino

    Created on: 05.12.2015

*/

#include <Arduino.h>

#include <WiFi.h>
#include <WiFiMulti.h>

#include <HTTPClient.h>
#include <HTTPUpdate.h>

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;
      }
    }
  }
}

12.2 FreeRTOS 多任务调度

API参考

xTaskCreatePinnedToCore() - 创建任务到指定CPU内核

xTaskCreate() 函数无法指定CPU内核,如果要创建任务到指定内核可使用这个函数。

对于在运行看门狗的CPU内核上运行的任务,需要定期调用delay(1)等。

语法

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 执行结果

用法示例

#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。为了和过去的代码兼容。

语法

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() - 删除任务

删除任务。

语法

void vTaskDelete(TaskHandle_t xTaskToDelete)

参数

传入值 说明 值范围
xTaskToDelete 任务handle。如果指定NULL,则指定调用任务

返回

用法示例

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()可用于固定频率的延时,它用来延时一个绝对时间。

语法

void vTaskDelay(const TickType_t xTicksToDelay)

参数

传入值 说明 值范围
xTicksToDelay 延时时间总数,单位是系统时钟节拍周期。

返回

用法示例

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调度器时,不可以使用此函数。

语法

void vTaskDelayUntil(TickType_t *const pxPreviousWakeTime, const TickType_t xTimeIncrement)

参数

传入值 说明 值范围
pxPreviousWakeTime 指针,指向一个变量,该变量保存任务最后一次解除阻塞的时间。第一次使用前,该变量必须初始化为当前时间。之后这个变量会在vTaskDelayUntil()函数内自动更新。
xTimeIncrement 周期循环时间。当时间等于(*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。如果不改变参数xTimeIncrement的值,调用该函数的任务会按照固定频率执行。

返回

注意

在通过调用vTaskSuspendAll()挂起RTOS调度程序后,不得调用此函数。

用法示例

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() - 获取优先级

获取任务优先级。

语法

UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask)

参数

传入值 说明 值范围
xTask 任务handle。如果指定NULL,则指定调用任务。

返回

返回值 说明 值范围
UBaseType_t 优先级

uxTaskPriorityGetFromISR() - 获取中断优先级

从ISR(中断)获得可以使用的优先级。

语法

UBaseType_t uxTaskPriorityGetFromISR(TaskHandle_t xTask) 

参数

传入值 说明 值范围
xTask 任务handle。如果指定NULL,则指定调用任务。

返回

返回值 说明 值范围
UBaseType_t 优先级

eTaskGetState() - 获取任务状态

获取任务的状态。

语法

eTaskState eTaskGetState(TaskHandle_t xTask) 

参数

传入值 说明 值范围
xTask 任务句柄。不允许为空

返回

返回值 说明 值范围
eTaskState 状态(eRunning:0,eReady:1,eBlocked:2,eSuspended:3,eDeleted:4)

vTaskPrioritySet() - 任务优先级设置

如果设置的优先级高于当前执行的任务,则在函数返回之前将进行上下文切换。

语法

void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority)

参数

传入值 说明 值范围
xTask 任务handle。如果指定NULL,则指定调用任务。
uxNewPriority 新的优先级

返回

vTaskSuspend() - 挂起任务

挂起任务。

void vTaskSuspend(TaskHandle_t xTaskToSuspend) PRIVILEGED_FUNCTION 争论

TaskHandle_txTaskToSuspend任务句柄。如果指定NULL,则指定调用任务。

语法

void vTaskSuspend(TaskHandle_t xTaskToSuspend)

参数

传入值 说明 值范围
xTaskToSuspend 任务handle。如果指定NULL,则指定调用任务。

返回

vTaskResume() - 恢复已停止的任务

恢复已停止的任务。

void vTaskResume(TaskHandle_t xTaskToResume) PRIVILEGED_FUNCTION 争论

TaskHandle_txTaskToResume任务句柄。不允许为空

语法

void vTaskResume(TaskHandle_t xTaskToResume) 

参数

传入值 说明 值范围
xTaskToResume 任务句柄。不允许为空

返回值

xTaskResumeFromISR() - 中断任务恢复

恢复被中断停止的任务。

语法

BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume) 

参数

传入值 说明 值范围
xTaskToResume 任务句柄。不允许为空。

返回

返回值 说明 值范围
BaseType_t 执行结果

vTaskSuspendAll() - 所有任务挂起

挂起所有任务。它用于暂时停止其他任务的执行。使用它后,请使用xTaskResumeAll()重新启动它。

语法

void vTaskSuspendAll(void) 

参数

返回

xTaskResumeAll() - 恢复所有任务

恢复所有任务。

BaseType_t xTaskResumeAll(void) PRIVILEGED_FUNCTION 返回值

BaseType_t执行结果

语法

BaseType_t xTaskResumeAll(void) 

参数

返回

返回值 说明 值范围
BaseType_t 执行结果

xTaskGetTickCount() - 获取任务启动时间

获取任务已开始的Tick。

TickType_t xTaskGetTickCount(void) PRIVILEGED_FUNCTION 返回值

任务启动的滴答数

语法

TickType_t xTaskGetTickCount(void)

参数

返回

返回值 说明 值范围
TickType_t 任务启动的Tick数

xTaskGetTickCountFromISR() - 中断任务启动时间

获取中断任务已触发的Tick。

TickType_t xTaskGetTickCountFromISR(void) PRIVILEGED_FUNCTION 返回值

TickType_t任务启动的滴答数

语法

TickType_t xTaskGetTickCountFromISR(void)

参数

返回

返回值 说明 值范围
TickType_t 任务启动的Tick数

uxTaskGetNumberOfTasks() - 获取任务数

获取任务数。

UBaseType_t uxTaskGetNumberOfTasks(void) PRIVILEGED_FUNCTION 返回值

UBaseType_t任务数

语法

UBaseType_t uxTaskGetNumberOfTasks(void)

参数

返回

返回值 说明 值范围
UBaseType_t 任务数

pcTaskGetTaskName() - 获取任务名称

获取任务的名称。

语法

char* pcTaskGetTaskName(TaskHandle_t xTaskToQuery) 

参数

传入值 说明 值范围
xTaskToQuery 任务句柄。如果指定NULL,则指定调用任务。

返回

返回值 说明 值范围
char * 任务名称

uxTaskGetStackHighWaterMark() - 获取剩余的堆栈

获取任务中最少的堆栈字节数。距离0越近,溢出的可能性就越大。

语法

UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask) 

参数

传入值 说明 值范围
xTask 任务句柄。如果指定NULL,则指定调用任务。

返回

返回值 说明 值范围
UBaseType_t 堆栈字节数

pxTaskGetStackStart() - 获取堆栈起始地址

获取堆栈的起始地址。

语法

uint8_t* pxTaskGetStackStart(TaskHandle_t xTask)

参数

传入值 说明 值范围
xTask 任务句柄。如果指定NULL,则指定调用任务。

返回

返回值 说明 值范围
uint8_t* 堆栈起始地址

xTaskGetIdleTaskHandle() - 获取空闲任务

获取当前正在运行的CPU内核的空闲任务句柄。

语法

TaskHandle_t xTaskGetIdleTaskHandle(void)

参数

返回

返回值 说明 值范围
TaskHandle_t 任务句柄

xTaskGetIdleTaskHandleForCPU() - CPU核心规范空闲任务获取

指定CPU内核以获取空闲任务句柄。

仅在INCLUDE_xTaskGetIdleTaskHandle设置为1 时可用。

语法

TaskHandle_t xTaskGetIdleTaskHandleForCPU(UBaseType_t cpuid)

参数

传入值 说明 值范围
UBaseType_t cpuidCPU核心(0,1)

返回

返回值 说明 值范围
TaskHandle_t 任务句柄

xTaskNotify() - 任务通知

向任务发送通知。

不得从中断服务程序(ISR)调用此函数。请改用 xTaskNotifyFromISR()。

语法

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 执行结果

用法示例

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() - 中断任务通知

在中断时向任务发送通知。

语法

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() - 获取任务通知

等待任务通知。

语法

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)的内联函数。

语法

BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify)

参数

传入值 说明 值范围
xTaskToNotify 任务处理程序

返回值

返回值 说明 值范围
BaseType_t 执行结果

vTaskNotifyGiveFromISR() - 中断简单任务通知

中断将通知发送给任务。

语法

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通知值

语法

uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait)

参数

传入值 说明 值范围
xClearCountOnExit pdFALSE,通知值减小。pdTRUE,通知值被清除。
xTicksToWait 最大等待时间。使用pdMS_TO_TICSK(value_in_ms)宏指定,或无限期等待portMAX_DELAY

返回

返回值 说明 值范围
uint32_t 通知值

用法示例

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 ); 
}

12.3 复位原因 ResetReason

用于复位后的判断,便于选择采取的措施。

例程:ResetReason

/*
*  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

12.4 软件重置

通过调用 ESP 对象上的 restart 方法重新启动 Edge101WE 主板。此方法不接收任何参数并返回 void。

例程:软件重启

void setup() {
  Serial.begin(115200);
  Serial.println("Restarting in 10 seconds");
  delay(10000);
  ESP.restart();
}
void loop() {}

12.5 芯片信息

API参考

getEfuseMac()

获取 ESP32 芯片的 MAC 地址。

语法

uint64_t EspClass::getEfuseMac(void);

参数

返回

返回值 说明 值范围
uint64_t MAC地址

getChipModel()

获取ESP32芯片模型。

语法

const char * EspClass::getChipModel(void)

参数

返回

返回值 说明 值范围
const char * 芯片模型

getChipRevision()

获取ESP32芯片版本信息。

语法

uint64_t EspClass::getEfuseMac(void);

参数

返回

返回值 说明 值范围
uint64_t 芯片修订版

getChipCores()

获取ESP32芯片上的内核数。

语法

uint8_t EspClass::getChipCores(void)

参数

返回

返回值 说明 值范围
uint8_t 芯片中的内核数。

例程:GetChipID

/* 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

12.6 量产程序

介绍 这一程序主要用于量产时为每一设备创建工厂 NVS(非易失性存储器)分区映像。NVS 分区映像由 CSV(逗号分隔值)文件生成,文件中包含了用户提供的配置项及配置值。

注意,该程序仅创建用于量产的二进制映像,您需要使用以下工具将映像烧录到设备上:

esptool.py

Flash 下载工具(仅适用于 Windows)

直接烧录程序

教程根据espressif官方提供的Flash 下载工具实现量产程序的烧录。

第一步 我们先打开Arduino IDE中编译和烧录信息

liangchan1

第二步 打开Arduino IDE中编译和烧录信息

liangchan2

然后,我们来了解一下Arduino IDE里面烧录esp32程序的烧录信息。

liangchan3

上图中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

上图3号标记,这里是烧写的程序主体,烧写首地址是0x10000。我们怎么得到一个一个可执行的二进制文件呢?我们可以通过Arduino IDE 导出。如下图所示。

导出二进制文件

liangchan5

导出二进制文件位置

liangchan6

上图中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

了解到上面这些信息后,我们就可以着手进行一次烧录了

我们先从espressif官网上下载Flash 下载工具 https://www.espressif.com/zh-hans/support/download/other-tools 。

liangchan8

打开flash_download_tool.exe,并如下图所示进行选择

liangchan9

接下来我们填写烧写的文件以及信息,如下图所示。

liangchan10

​ 进行一次正确的烧写

liangchan11

到这里,一次量产程序的烧写就完成了。