OTA工作流程
ESP32 FLASH空间分区
通过menuconfig --> partition table 有三种分区选择:
- single factory app
- factory app , two ota
- custom partition table
ESP32 SDK对应的flash分区配置的源码路径是:esp-idf-v4.4.2\components\partition_table
- single factory:partition_singleapp.csv
- single factory large: partitons_singleapp_large.csv
- two ota: partitions_two_ota.csv
OTA 分区
- OTA data区:决定运行哪个区的APP
- factory app:出厂时的默认APP
- OTA_0区:OTA_0 APP
- OTA_1区:OTA_1 APP
系统重启时获取OTA data分区数据进行计算,决定此后加载哪个ota分区的app执行,从而实现升级
在不同阶段,esp32使用分区的情况
使用简化API升级
菜单配置
-
flash大小配置
serial flasher config --> flash size
-
配置分区表
partition table --> factory app,two OTA definitions
-
配置服务器类型
component config --> esp https ota --> allow http for ota
(配置为http ota ,在代码中注释掉https使用的CA证书)
-
配置wifi信息
example connection configuation --> wifi ssid -->wifi password
-
配置APP版本号
application manger --> get the project version form kconfi
简化例程代码
代码源路径:\esp-idf-v4.4.2\components\esp_https_ota\src
main
创建ota升级任务
void app_main(void)
{
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// 1.OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
// If this happens, we erase NVS partition and initialize NVS again.
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
#if CONFIG_EXAMPLE_CONNECT_WIFI
/* Ensure to disable any WiFi power save mode, this allows best throughput
* and hence timings for overall OTA operation.
*确保禁用任何WiFi省电模式,这可以实现最佳吞吐量,从而实现整体OTA操作的计时
*/
esp_wifi_set_ps(WIFI_PS_NONE);//设置当前WiFi省电模式
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
xTaskCreate(&simple_ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
}
simple_ota_example_task
void simple_ota_example_task(void *pvParameter)
{
ESP_LOGI(TAG, "Starting OTA example");
esp_http_client_config_t config = {
.url = "http://xxx.xxx.xx.xx:8070/control.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 upgrade failed");
}
while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
esp_https_ota
esp_err_t esp_https_ota(const esp_http_client_config_t *config)
{
if (!config) {
ESP_LOGE(TAG, "esp_http_client config not found");
return ESP_ERR_INVALID_ARG;
}
esp_https_ota_config_t ota_config = {
.http_config = config,
};
esp_https_ota_handle_t https_ota_handle = NULL;
esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);//启动OTA固件升级
if (https_ota_handle == NULL) {
return ESP_FAIL;
}
while (1) {
err = esp_https_ota_perform(https_ota_handle);//从http流中读取并写入OTA分区
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
break;
}
}
if (err != ESP_OK) {
esp_https_ota_abort(https_ota_handle);//清理https ota固件升级并关闭https连接
return err;
}
esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle);
if (ota_finish_err != ESP_OK) {
return ota_finish_err;
}
return ESP_OK;
}
typedef struct {
//ESP HTTP客户端配置
const esp_http_client_config_t *http_config;
//初始化ESP HTTP客户端后的回调
http_client_init_cb_t http_client_init_cb;
//在初始化过程中删除整个FLASH分区。默认情况下,flash分区在写操作时被擦除
bool bulk_flash_erase;
//允许通过多个http请求下载固件映像
bool partial_http_download;
//部分http下载的最大请求大小
int max_http_request_size;
} esp_https_ota_config_t;
例程中API说明
esp_https_ota_begin
初始化ESP HTTPS OTA上下文并建立HTTPS连接。
这个函数必须先被调用。如果这个函数成功返回,那么’ esp_https_ota_perform ‘应该被调用来继续OTA进程,并且在OTA操作完成或后续操作失败时应该调用’ esp_https_ota_finish '。
@param[in] ota_config
指向esp_https_ota_config_t结构的指针
@param[out] handle
指向’ esp_https_ota_handle_t '类型的已分配数据的指针,该数据将在此函数中初始化
esp_https_ota_perform
从HTTP流读取图像数据并写入OTA分区。只有esp_https_ota_begin()成功返回时,才必须调用此函数。
此函数必须在循环中调用,因为它在每个HTTP读操作后返回,从而使您可以灵活地中途停止OTA操作。
param[in] 指向esp_https_ota_handle_t结构体的指针
return:ESP_ERR_HTTPS_OTA_IN_PROGRESS: OTA更新正在进行中,再次调用此API继续。
ESP_OK: OTA更新成功
ESP_FAIL: OTA更新失败
ESP_ERR_INVALID_ARG:无效参数
ESP_ERR_OTA_VALIDATE_FAILED:无效的应用程序映像
ESP_ERR_NO_MEM:无法为OTA操作分配内存。
ESP_ERR_FLASH_OP_TIMEOUT或ESP_ERR_FLASH_OP_FAIL: Flash写入失败。
esp_https_ota_abort
清理 HTTPS OTA 固件升级并关闭 HTTPS 连接。此函数关闭 HTTP 连接并释放 ESP HTTPS OTA 上下文。
备注:esp_https_ota_abort不应在调用esp_https_ota_finish后调用
param[in] https_ota_handle– 指向esp_https_ota_handle_t结构的指针
return ESP_OK:清理成功
ESP_ERR_INVALID_STATE:无效的
ESP HTTPS OTA 状态ESP_FAIL:OTA 未启动
ESP_ERR_NOT_FOUND:找不到 OTA 句柄
ESP_ERR_INVALID_ARG:参数无效
esp_https_ota_finish
清理 HTTPS OTA 固件升级并关闭 HTTPS 连接。
此函数关闭 HTTP 连接并释放 ESP HTTPS OTA 上下文。此函数将引导分区切换到包含新固件映像的 OTA 分区。
备注:如果此 API 成功返回,则必须调用 esp_restart() 才能从新固件映像启动,esp_https_ota_finish调用后不应调用esp_https_ota_abort
param[in] https_ota_handle – 指向esp_https_ota_handle_t结构的指针
return : ESP_OK:清理成功
ESP_ERR_INVALID_STATEESP_ERR_INVALID_ARG:参数无效
ESP_ERR_OTA_VALIDATE_FAILED:应用图像无效
OTA升级具体流程
Ⅰ. 启动固件升级
OTA结构体
struct esp_https_ota_handle {
esp_ota_handle_t update_handle;
const esp_partition_t *update_partition;
esp_http_client_handle_t http_client;
char *ota_upgrade_buf;
size_t ota_upgrade_buf_size;
int binary_file_len;
int image_length;
int max_http_request_size;
esp_https_ota_state state;
bool bulk_flash_erase;
bool partial_http_download;
};
typedef struct esp_https_ota_handle esp_https_ota_t;
初始化HTTP连接
https_ota_handle->http_client = esp_http_client_init(ota_config->http_config);//http初始化
if (https_ota_handle->http_client == NULL) {
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
err = ESP_FAIL;
goto failure;
}
err = _http_connect(https_ota_handle->http_client);//http连接
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to establish HTTP connection");
goto http_cleanup;
}
//获取http响应头内的content_length
if (!https_ota_handle->partial_http_download) {
https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client);
}
获取新固件写入的下一个OTA应用程序分区
https_ota_handle->update_partition = esp_ota_get_next_update_partition(NULL);
if (https_ota_handle->update_partition == NULL) {
ESP_LOGE(TAG, "Passive OTA partition not found");
err = ESP_FAIL;
goto http_cleanup;
}
Ⅱ . 从http流中读取并写入OTA分区
ESP_HTTPS_OTA_BEGIN
err = esp_ota_begin(handle->update_partition, erase_size, &handle->update_handle);//启动ota
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
return err;
}
handle->state = ESP_HTTPS_OTA_IN_PROGRESS;
ESP_HTTPS_OTA_IN_PROGRESS
data_read = esp_http_client_read(handle->http_client,
handle->ota_upgrade_buf,
handle->ota_upgrade_buf_size);//读取http数据
if (data_read == 0) //接收数据为0
{
//esp_http_client_is_complete_data_received用于检查是否接收到完整的镜像
bool is_recv_complete = esp_http_client_is_complete_data_received(handle->http_client);
/*如果没有收到完整的数据,但服务器发送了一个ENOTCONN或ECONNRESET,则返回失败。如果收到完整的数据,我们就成功结束。*/
if ((errno == ENOTCONN || errno == ECONNRESET || errno == ECONNABORTED) && !is_recv_complete)
{
ESP_LOGE(TAG, "Connection closed, errno = %d", errno);
return ESP_FAIL;
}
else if (!is_recv_complete)
{//未接收完成
return ESP_ERR_HTTPS_OTA_IN_PROGRESS;
}
ESP_LOGD(TAG, "Connection closed");
}
else if (data_read > 0) //接收数据大于0
{
return _ota_write(handle, (const void *)handle->ota_upgrade_buf, data_read);//ota写入
}
else
{
ESP_LOGE(TAG, "data read %d, errno %d", data_read, errno);
return ESP_FAIL;
}
Ⅲ . 清理https ota固件升级并关闭https连接
//ota写入失败
if (err != ESP_OK) {
esp_https_ota_abort(https_ota_handle);//清理https ota固件升级并关闭https连接
return err;
}
//ota写入成功
esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle);//清理https ota固件升级并关闭https连接
if (ota_finish_err != ESP_OK) {
return ota_finish_err;
}
HTTP_OTA 开发中遇到的问题
- 问题一:下载速度太慢
解决:OTA下载速度取决于HTTP流下载速度,所以更改 HTTP_Client配置中接收缓存的大小即可。
esp_https_ota :
esp_https_ota_config_t ota_config = {
.http_config = config,
};
esp_https_ota_begin :
const int alloc_size = MAX(ota_config->http_config->buffer_size, DEFAULT_OTA_BUF_SIZE);
https_ota_handle->ota_upgrade_buf = (char *)malloc(alloc_size);
https_ota_handle->ota_upgrade_buf_size = alloc_size;
esp_https_ota_perform :
data_read = esp_http_client_read(handle->http_client,
handle->ota_upgrade_buf,
handle->ota_upgrade_buf_size);