13.系统API
13.1 OTA升级
OTA —— Over the air update of the firmware,也就是无线固件更新,通过此方法我们可以不连接 USB,而通过无线方式下载程序。
Edge101WE 主板具备多种的 OTA 方式。
13.1.1 ArduinoOTA
ArduinoOTA 方式需要结合Arduino IDE,比较适合开发工程师使用。
API参考
onStart()
注册要在OTA更新开始时调用的回调函数。
语法
#include <ArduinoOTA.h>
ArduinoOTAClass& ArduinoOTAClass::onStart(THandlerFunction fn);
参数
传入值 | 说明 | 值范围 |
---|---|---|
fn | 回调函数。 THandlerFunction类型的定义如下。 typedef std::function |
返回
返回值 | 说明 | 值范围 |
---|---|---|
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 |
返回
返回值 | 说明 | 值范围 |
---|---|---|
ArduinoOTAClass *this |
onError()
注册发生OTA更新错误时要执行的回调函数。
语法
#include <ArduinoOTA.h>
ArduinoOTAClass& ArduinoOTAClass::onError(THandlerFunction_Error fn);
参数
传入值 | 说明 | 值范围 |
---|---|---|
fn | 回调函。 THandlerFunction_Error类型的定义如下。 typedef std::function 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 第一个参数是固件当前数据大小,第二个参数是固件总大小。单位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地址为前面串口打印出地址。点击上传程序。
上传成功后,切换为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。
点击登录,此时进入固件上传界面
修改loop函数
void loop(void) {
server.handleClient();
Serial.println("The code has changed");
delay(100);
}
点击Sketch -> Export compiled Binary,编译并导出.bin文件
编译完成后显示Done compilng,在信息窗可看到.bin文件所在的文件夹
点击选择OTAWebUpdater.ino.bin文件,点击Update上传。
13.1.2 Web Update
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
使用网页浏览器访问这个网址。
此时我们将代码 WebUpdate 另存为 WebUpdateTest 项目,在 loop 循环增加 Serial.println(”The code has been modified”); 语句。
在 Arduino IDE 点击 Sketch — Export compiled Binary,导出bin二进制文件。
此时 Arduino IDE 将执行编译。编译完成后可以看到 bin 文件的位置。
浏览器打开 http://esp32-webupdate.local 点击 选择文件 按钮
在刚才串口打印出的文件夹位置选择 WebUpdateTest.ino.bin ,然后点击 Update 。
等待更新完成将 会显示 OK
在串口打印出的 debug 信息也可以看到 Update 的过程。最终会在串口持续打印 The code has been modified。说明新代码运行起来了。
这样我们就可以通过无线的形式来更新代码,这对于已经安装到现场的产品的程序更新非常有用。
注意:如果更新了不带 Web Update功能的代码,将不能进行下一次的 Web Update。
13.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方式更新
}
修改打印内容,代码版本 v1.2
点击 Export compiled Binary 编译出bin文件
编译出的 bin文件地址可在编译对话框中找到
注册阿里云账户,进入对象存储OSS页面 https://oss.console.aliyun.com/
创建一个Bucket,选项如下
在阿里OSS上传文件
点击详情
关闭HTTPS,拷贝URL,修改程序中的连接
将 httpUpdate.ino.bin 拖动到 HFS软件处
13.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);
}
13.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;
}
}
}
}
13.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 );
}
13.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
13.4 软件重置
通过调用 ESP 对象上的 restart 方法重新启动 Edge101WE 主板。此方法不接收任何参数并返回 void。
例程:软件重启
void setup() {
Serial.begin(115200);
Serial.println("Restarting in 10 seconds");
delay(10000);
ESP.restart();
}
void loop() {}
13.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
13.6 量产程序
介绍 这一程序主要用于量产时为每一设备创建工厂 NVS(非易失性存储器)分区映像。NVS 分区映像由 CSV(逗号分隔值)文件生成,文件中包含了用户提供的配置项及配置值。
注意,该程序仅创建用于量产的二进制映像,您需要使用以下工具将映像烧录到设备上:
esptool.py
Flash 下载工具(仅适用于 Windows)
直接烧录程序
教程根据espressif官方提供的Flash 下载工具实现量产程序的烧录。
第一步 我们先打开Arduino IDE中编译和烧录信息
第二步 打开Arduino IDE中编译和烧录信息
然后,我们来了解一下Arduino IDE里面烧录esp32程序的烧录信息。
上图中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。
上图3号标记,这里是烧写的程序主体,烧写首地址是0x10000。我们怎么得到一个一个可执行的二进制文件呢?我们可以通过Arduino IDE 导出。如下图所示。
导出二进制文件
导出二进制文件位置
上图中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。
了解到上面这些信息后,我们就可以着手进行一次烧录了
我们先从espressif官网上下载Flash 下载工具 https://www.espressif.com/zh-hans/support/download/other-tools 。
打开flash_download_tool.exe,并如下图所示进行选择
接下来我们填写烧写的文件以及信息,如下图所示。
进行一次正确的烧写
到这里,一次量产程序的烧写就完成了。