官方指南:空中升级 (OTA) - ESP32 - — ESP-IDF 编程指南 v4.3.6 文档,虽然是正对ESP32的,但是原理是一样的。
官方参考例程:esp-idf\ESP8266_RTOS_SDK\examples\system\ota\,其中包含两个例程,一个是simple_ota_example,另一个是native_ota。simple_ota_example例程调用的是封装之后函数,只需要将固件的下载地址传入函数即可自动完成升级,而native_ota调用的是原生的函数,有更多的过程,详细展示了OTA的下载、存储过程以及校验。这里以simple_ota_example为例进行讲解。
使用带ota分区的Partition Table,在flash区域中划出两块app存储空间,一块区域是当前运行的app区,另一块区域是新下载的app存放的区域。进入到工程目录中,执行make menuconfig,然后选择
Partition Table --->
Partition Table (Single factory app, no OTA) --->
(X) Factory app, two OTA definitions
此方式不需要证书,更简单。
Component config --->
ESP HTTPS OTA --->
[*] Allow HTTP for OTA (WARNING: ONLY FOR TESTING PURPOSE, READ HELP)
上电->进入app_main()->创建升级任务(simple_ota_example_task)->执行任务->等待wifi连接并获取IP地址->http请求服务器上的固件版本号->与当前运行版本号进行比较->如果服务器上的固件版本号更高->下载新的固件->下载完成之后重启设备。
版本比较函数
/**
* @brief None.
* @param None.
* @retval None.
*/
int edition_compare(const char* pszStr1, const char* pszStr2)
{
if (pszStr1 == NULL || pszStr2 == NULL) {
return 0;
}
int nCurPos = 0, nCapPos=-1;
const char* pszTmp1 = pszStr1;
const char* pszTmp2 = pszStr2;
while ((*pszTmp1 != '\0') && (*pszTmp2 != '\0') && (*pszTmp1 == *pszTmp2)) {
nCurPos++; //找到第一个处不相同出现的位置
pszTmp1++;
pszTmp2++;
if (*pszTmp1 == '.') {
nCapPos = nCurPos; //记录最近的‘.’的位置
}
}
if (*pszTmp1 == '\0' && *pszTmp2 == '\0') { // 两个字符串相等
return 0;
} else if(*pszTmp1 == '\0'){
return -1;
} else if(*pszTmp2 == '\0'){
return 1;
}else{ // 两个字符串不相等,比较大小
pszTmp1 = pszStr1 + nCapPos + 1;
pszTmp2 = pszStr2 + nCapPos + 1;
int pszNub1=strtol(pszTmp1,NULL,10);
int pszNub2=strtol(pszTmp2,NULL,10);
return (pszNub1 - pszNub2);
}
}
创建设备事件,用于等待网络连接
const int CONNECTED_BIT = BIT0;
s_device_event_group = xEventGroupCreate();
event_handler()函数中加入s_device_event_group的设置
/**
* @brief event_handler.
* @param None.
* @retval None.
*/
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
xEventGroupClearBits(s_device_event_group, WIFI_CONNECTED_BIT);
esp_wifi_connect();
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
xEventGroupSetBits(s_device_event_group, WIFI_CONNECTED_BIT);
}
else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE)
{
ESP_LOGI(TAG, "Scan done");
}
else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL)
{
ESP_LOGI(TAG, "Found channel");
}
else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD)
{
ESP_LOGI(TAG, "Got SSID and password");
smartconfig_event_got_ssid_pswd_t* evt = (smartconfig_event_got_ssid_pswd_t*)event_data;
wifi_config_t wifi_config;
uint8_t ssid[33] = { 0 };
uint8_t password[65] = { 0 };
uint8_t rvd_data[33] = { 0 };
bzero(&wifi_config, sizeof(wifi_config_t));
memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
wifi_config.sta.bssid_set = evt->bssid_set;
if (wifi_config.sta.bssid_set == true) {
memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
}
memcpy(ssid, evt->ssid, sizeof(evt->ssid));
memcpy(password, evt->password, sizeof(evt->password));
ESP_LOGI(TAG, "SSID:%s", ssid);
ESP_LOGI(TAG, "PASSWORD:%s", password);
if (evt->type == SC_TYPE_ESPTOUCH_V2) {
ESP_ERROR_CHECK( esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data)) );
ESP_LOGI(TAG, "RVD_DATA:%s", rvd_data);
}
ESP_ERROR_CHECK(esp_wifi_disconnect());
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_connect());
} else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {
xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
}
}
任务函数
/**
* @brief None.
* @param None.
* @retval None.
*/
static void simple_ota_example_task(void * pvParameter)
{
EventBits_t uxBits = xEventGroupWaitBits(s_device_event_group, WIFI_CONNECTED_BIT, pdFALSE, false, portMAX_DELAY );
if( (uxBits & WIFI_CONNECTED_BIT) == WIFI_CONNECTED_BIT )
{
ESP_LOGI(TAG, "Starting OTA example...");
char local_response_buffer[101] = {0};
esp_http_client_config_t config0 = {
.url = "http://www.huochaigun.top/hello-world/version.html",
.event_handler = _http_event_handler,
// .user_data = local_response_buffer, // Pass address of local buffer to get response
.buffer_size = 100
};
esp_http_client_handle_t client = esp_http_client_init(&config0);
// GET
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
}
esp_http_client_read_response( client, local_response_buffer, 100 );
local_response_buffer[ esp_http_client_get_content_length(client) ] = '\0';
ESP_LOGI(TAG,"http response:%s\n", local_response_buffer );
esp_http_client_cleanup(client);
if( edition_compare(local_response_buffer, AppVer) > 0 )
{
esp_http_client_config_t config = {
.url = "http://www.huochaigun.top/hello-world/hello-world.bin",
.event_handler = _http_event_handler,
};
esp_err_t ret = esp_https_ota(&config);
if (ret == ESP_OK) {
esp_restart();
} else {
ESP_LOGE(TAG, "Firmware Upgrades Failed");
}
}
else
{
ESP_LOGI(TAG, "The current software is the latest version");
}
}
vTaskDelete( NULL );
}
app_main()中加入以下代码创建任务
xTaskCreate(simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
执行make之后,会新生成4个bin文件,将4个bin文件下载到设备即可。
0xd000 ota_data_initial.bin
0x0 bootloader.bin
0x10000 hello-world.bin
0x8000 partitions_two_ota.bin
可提供有偿技术支持。