文章目录
- 一、前言
- [二、了解 JTAG 和 Ymodem 的工作原理](#二、了解 JTAG 和 Ymodem 的工作原理)
-
- [2.1 环境准备](#2.1 环境准备)
- [2.2 Ymodem 协议工作原理](#2.2 Ymodem 协议工作原理)
- [2.3 固件分区准备](#2.3 固件分区准备)
- 三、关键升级函数
- [五、使用shell 测试](#五、使用shell 测试)
一、前言
如果使用 JTAG 串口 结合 Ymodem 协议 实现 ESP32 的固件升级,整体逻辑将围绕通过串口传输固件文件并将其烧录到指定的 Flash 分区。以下是完整的实现步骤和代码说明。为什么要使用ymodem ,因为要对文件进行校验,使得数据丢失可以重传,为什么保证固件的完整性,所以使用ymodem协议。
二、了解 JTAG 和 Ymodem 的工作原理
2.1 环境准备
JTAG 接口工具:如 FT2232、J-Link、ESP32 自带的 UART-to-USB。
终端工具支持 Ymodem 协议:如 TeraTerm 或 Minicom。
ESP-IDF :开发 ESP32 所需的软件框架
参考上一篇文章 ymodem协议
https://blog.csdn.net/mayuxin1314/article/details/143990976
2.2 Ymodem 协议工作原理
- 发送端:PC 端通过 Ymodem 协议将固件文件逐块传输到设备。
- 接收端:ESP32 通过 UART 接口接收 Ymodem 数据并逐块校验。
- 目标:将接收到的数据写入 OTA 分区。
2.3 固件分区准备
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 200K,
otadata, data, ota, 0x110000, 0x2000,
phy_init, data, phy, 0x112000, 0x1000,
factory, app, factory, 0x120000, 3M,
ota_0, app, ota_0, 0x420000, 3M,
ota_1, app, ota_1, 0x720000, 3M,
font_data, 0x50, 0x22, 0xa20000, 2M,
storage, data, littlefs, 0xc20000, 2M,
三、关键升级函数
c
// 定义文件名和文件大小变量
static esp_ota_handle_t ota_handle;
OtaUpdate_t xOtaUpdate_t;
// xLocaUpdate_Begin 函数:初始化 OTA 更新
static enum rym_code xLocaUpdate_Begin(RYM_t *pxRYM_Ag, uint8_t *buf, uint32_t len)
{
printf("xLocaUpdate_Begin\r\n");
const esp_partition_t *running_partition = esp_ota_get_running_partition();
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
if (ota_partition == NULL)
{
ESP_LOGE(TAG, "未找到 OTA 分区");
return RYM_ERR_CAN;
}
ESP_LOGI(TAG, "当前分区地址: 0x%08" PRIx32 ", OTA 分区地址: 0x%08" PRIx32,
running_partition->address, ota_partition->address);
parseYModemData(buf,xOtaUpdate_t.ucFilename, &xOtaUpdate_t.ulFilesize);
ESP_LOGI(TAG, "固件名字 %s 固件内容长度: %" PRIu32 " 字节\r\n",xOtaUpdate_t.ucFilename, xOtaUpdate_t.ulFilesize);
// 初始化 OTA 会话
esp_err_t err = esp_ota_begin(ota_partition, xOtaUpdate_t.ulFilesize, &ota_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "OTA 更新初始化失败: %s", esp_err_to_name(err));
return RYM_ERR_CAN;
}
xOtaUpdate_t.bytes_written = 0; // 初始化已写入字节数
return RYM_CODE_ACK;
}
// xLocaUpdate_Data 函数:写入固件数据
static enum rym_code xLocaUpdate_Data(RYM_t *pxRYM_Ag, uint8_t *buf, uint32_t len)
{
// 检查剩余要写入的字节数
uint32_t remaining_bytes = xOtaUpdate_t.ulFilesize - xOtaUpdate_t.bytes_written;
// 如果接收的数据超过剩余的字节数,则只写入剩余部分
if (len > remaining_bytes) {
len = remaining_bytes;
}
// 写入数据到 OTA 分区
esp_err_t err = esp_ota_write(ota_handle, buf, len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "OTA 写入失败: %s", esp_err_to_name(err));
return RYM_ERR_CAN;
}
// 更新已写入的字节数
xOtaUpdate_t.bytes_written += len;
ESP_LOGI(TAG, "写入了 %u 字节,总计写入 %u/%u 字节", (unsigned int)len, (unsigned int)xOtaUpdate_t.bytes_written, (unsigned int)xOtaUpdate_t.ulFilesize);
return RYM_CODE_ACK;
}
// xLocaUpdate_End 函数:完成 OTA 更新
static enum rym_code xLocaUpdate_End(RYM_t *pxRYM_Ag, uint8_t *buf, uint32_t len)
{
esp_err_t err = esp_ota_end(ota_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "OTA 更新结束失败: %s", esp_err_to_name(err));
return RYM_ERR_CAN;
}
// 获取当前 OTA 分区的 SHA-256 校验值
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
uint8_t sha256_hash[32];
err = esp_partition_get_sha256(ota_partition, sha256_hash);
if (err != ESP_OK) {
ESP_LOGE(TAG, "获取 SHA-256 校验失败: %s", esp_err_to_name(err));
return RYM_ERR_CAN;
}
// 输出 SHA-256 校验结果供验证
ESP_LOGI(TAG, "OTA 分区 SHA-256 校验值:");
for (int i = 0; i < 32; i++) {
printf("%02x", sha256_hash[i]);
}
printf("\n");
// // 将新固件设置为引导分区
// const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL);
err = esp_ota_set_boot_partition(ota_partition);
if (err != ESP_OK) {
ESP_LOGE(TAG, "设置引导分区失败: %s", esp_err_to_name(err));
return RYM_ERR_CAN;
}
ESP_LOGI(TAG, "OTA 更新完成,设备将使用新固件引导");
// 触发设备重启以加载新固件
esp_restart();
return RYM_CODE_ACK;
}
int ulLocalUpdate()
{
int err = 0;
err = RYM_ReadFile(&xRYM_Ag, xLocaUpdate_Begin, xLocaUpdate_Data, xLocaUpdate_End);
return err;
}
五、使用shell 测试
-
设置
-
测试