#简介
项目上需要支持OTA固件升级功能,小结一下调试过程中需要的问题和疑问和解决方法。
注意:由于是初步调试,代码中存在很多不合理和需要优化的地方,请勿直接使用。
#硬件
MCU:STM32F405
通讯模块:EC600N-CN
#思路
首先要理清整个OTA的大体思路,OTA的过程就是下载,保存,跳转这三个步骤
- 下载我这里通过CAT1模块的HTTP功能。
- 保存这里直接通过STM32库中的FLASH读写接口来实现。
- 跳转这里参考网上一些资料写了一个Bootloader用来把下载好的固件拷贝到APP区,然后跳转到APP区执行,这里我没有使用网上的直接跳转到APP2执行的思路。
#问题和解决方法
1、本来通过EC600N的http和file命令文档发现可以将固件一次性下载然后存储到EC600N的本地FILE中,然后通过FILE命令慢慢读取数据再保存,但是发现生成的bin文件有160多KB,通过命令查询到EC600N的FILE的自带存储只有80多KB(可能模块型号不同,也有更高的存储空间),那么一次性下载的方案就无法实现了,然而EC600N的HTTP功能中可以实现文件的分片下载,从文件的指定位置下载指定大小的功能
那么就可以通过将160KB分为多片文件然后下载到FILE系统中,然后再通过FILE命令分块读取保存。
首先GET整个文件,从相应中提取整个bin文件的大小,然后每次下载40KB,然后保存到EC600N的FILE中,在每次下载和保存前,清理一次FILE文件中的空间。
2、其中使用__HAL_TIM_SET_AUTORELOAD这个接口,是因为我接收数据使用的是串口超时中断接收,在调试中发现从FILE中读取时,AT命令返回的命令和后接数据中存在400ms的间隔,那么我在接收数据前将串口超时设置为500,保证本次完整接收,然后再将超时重新设置为20ms,20ms可能比较长,为方便调试。
3、FLASH编程文档中说写入之前需要先擦除再写入,其实可以直接将固件存储区直接擦除,就会全变成0XFF,然后写入就不用先擦除在写入了,直接写入就可以了。
4、BootLoader很多博主都已经写好了,基本拿来就用,我就不再介绍了,基本就是读取OTA状态,根据状态执行是否擦除APP1和拷贝APP2到APP1。
5、Bootloader的KEIL设置中需要设置ROM起始地址为0x08000000,大小我设置为32KB,
在Bootloader和APP1之间我设置了OTA状态的存储区域,大小32KB,APP1和APP2我设置了192KB,足够用了
6、调试时出现了,将APP2拷贝到APP1后,无法执行程序的问题,困扰了我一段时间,然后我发现是因为Bootloader我烧录在0x08000000地址,APP1我编译设置的地址是0x08010000,APP2编译我设置的地址是0x08040000,然后我将Bootloader和APP1和APP2分别用KEIL烧录后,这样就出现了一个问题,Bootloader固件中的中断向量地址是对的bin文件开头都是0x08000000基准的,APP1也是对的,bin文件开头都是0x08010000基准的,但是APP2中却是0x08040000基准的,那当我把APP2拷贝到APP1后,跳转执行后,文件还是从0x08040000开始执行,肯定是执行不起来的,那么APP2的固件就需要设置ROM地址为0x08010000,然后生成的bin文件需要用ST-LINK-UNILITY烧录到指定0x08040000地址,这样在APP2拷贝到APP1后,执行时才能正常执行中断向量,这里有可能有人会有疑问,不是直接用接口修改中断向量地址就好了么?这个我试过,还是不行,只有通过ST-LINK-UNILITY烧录到指定0x08040000地址才行,不过这个问题不影响,因为我们OTA是通过云端拷贝到本地的,不存在KEIL直接烧录的情况。
#附录代码和说明
首先GET整个文件,从相应中提取整个bin文件的大小
ATCmd_SendWithoutACK("AT+QHTTPGET=20\r\n", 300, &httpInfo.http_get_rsp_state);
if ((httpInfo.http_get_rsp_code / 100) != 2) //判断http rspcode,如果不是200系列,则GET错误
{
printf("ota http get err:%d\r\n", httpInfo.http_get_rsp_code);
return -1;
}
httpInfo.http_get_total_state = 0;
part_end_size = otaInfo.total_size % HTTP_PART_SIZE;
part_total_count = part_end_size ? (otaInfo.total_size / HTTP_PART_SIZE + 1) : (otaInfo.total_size / HTTP_PART_SIZE);
printf("http get total size:%d part_end_size:%d part_total_count:%d\r\n", otaInfo.total_size, part_end_size, part_total_count);
然后每次下载40KB,然后保存到EC600N的FILE中,在每次下载和保存前,清理一次FILE文件中的空间。
read_offset = 0;
for (j = 0; j < part_total_count; j++)
{
__HAL_TIM_SET_AUTORELOAD(&htim2, 20);
ATCmd_SendWithoutACK("AT+QFDEL=\"*\"", 100, NULL);
ATCmd_SendWithoutACK("AT+QFLDS=\"UFS\"", 50, NULL);
ATCmd_SendWithoutACK("AT+QFLST", 50, NULL);
httpInfo.http_get_rsp_state = 0;
if (j == (part_total_count - 1))
{
part_size = part_end_size ? part_end_size : HTTP_PART_SIZE;
}
else
{
part_size = HTTP_PART_SIZE;
}
httpInfo.http_get_rsp_state = 0;
httpInfo.http_get_rsp_code = 0;
httpInfo.http_content_len = 0;
httpInfo.http_err_code = 0;
httpInfo.http_get_part_state = 1;
otaInfo.part_size = 0;
sprintf(send_data, "AT+QHTTPGETEX=20,%d,%d", read_offset, part_size);
ATCmd_SendWithoutACK(send_data, 500, &httpInfo.http_get_rsp_state);
if ((httpInfo.http_get_rsp_code / 100) != 2) //判断http rspcode,如果不是200系列,则GET错误
{
printf("ota http get err:%d\r\n", httpInfo.http_get_rsp_code);
return -1;
}
printf("http get part[%d] part_size:%d, otaInfo.part_size:%d\r\n", j, part_size, otaInfo.part_size);
httpInfo.http_get_part_state = 0;
ATCmd_SendWithoutACK("AT+QHTTPREADFILE=\"UFS:ota.bin\",80", 200, NULL);
ATCmd_SendWithoutACK("AT+QFOPEN=\"ota.bin\",2", 200, NULL);
file_handle_index = httpInfo.file_handle_index;
package_end_size = part_size % HTTP_PACKAGE_SIZE;
package_total_count = package_end_size ? (part_size / HTTP_PACKAGE_SIZE + 1) : (part_size / HTTP_PACKAGE_SIZE);
printf("> file handle:%d, part_size:%d, pack_end_size:%d, package_total_count:%d\r\n", file_handle_index, part_size, package_end_size, package_total_count);
每次下载保存后,再从FILE中每次1024字节读取出来,然后保存在固件存储区中
__HAL_TIM_SET_AUTORELOAD(&htim2, 500);
package_read_offset = 0;
read_size = 0;
for (i = 0; i < package_total_count; i++)
{
if (i == (package_total_count - 1))
{
read_size = package_end_size ? package_end_size : 1024;
}
else
{
read_size = 1024;
}
httpInfo.file_readed_size = read_size;
httpInfo.file_read_offset = read_offset;
sprintf(send_data, "AT+QFSEEK=%d,%d,0", file_handle_index, package_read_offset);
ATCmd_SendWithoutACK(send_data, 100, NULL);
sprintf(send_data, "AT+QFREAD=%d,%d", file_handle_index, read_size);
ATCmd_SendWithoutACK(send_data, 1, NULL);
read_offset += read_size;
package_read_offset += read_size;
if (pdTRUE == xQueueReceive(otaRecv_QueueHandle, &cat1DataIndex, 10000))
{
if (cat1DataIndex < UART_BUFFER_MAX_NUM)
{
sprintf(data_tmp, "\r\nCONNECT %d\r\n", read_size);
data_tmp_len = strlen(data_tmp);
if (NULL != (p = strstr(pUart3RecvData->Data[cat1DataIndex].data, data_tmp)))
{
otaInfo.already_size = read_offset;
printf("> [%d / %d]---[%d / %d]\r\n", package_read_offset, otaInfo.part_size, otaInfo.already_size, otaInfo.total_size);
flash_write_word_size = (read_size % 4) ? (read_size / 4 + 1) : (read_size / 4);
HTTP_Flash_Write_Words(FLASH_APP2_START_ADDR + httpInfo.file_read_offset, (uint32_t *)(p + data_tmp_len), flash_write_word_size);
// Flash_Write_Bytes(FLASH_APP2_START_ADDR + httpInfo.file_read_offset, (uint8_t*)(p + data_tmp_len), httpInfo.read_size);
osDelay(10);
}
}
}
else
{
sprintf(send_data, "AT+QFCLOSE=%d", file_handle_index);
ATCmd_SendWithoutACK(send_data, 200, NULL);
printf("> HTTP OTA ERROR!!!\r\n");
return -1;
}
}
sprintf(send_data, "AT+QFCLOSE=%d", file_handle_index);
ATCmd_SendWithoutACK(send_data, 200, NULL);
这里用到的命令有:
FILE相关:
- AT+QFLST:获取UFS中文件信息,方便调试
- AT+QFDEL:删除UFS中文件,我将文件全部删除
- AT+QFOPEN:打开指定文件,这里文件的handle需要通过AT+QFREAD读取时,通过AT返回来记录下来。
- AT+QFREAD:读取指定名字的文件,有文件则会返回handle文件句柄
- AT+QFSEEK:读取数据时,可设置读取开始的指针位置,设置从指针位置读取
- AT+QFCLOSE:关闭文件
HTTP相关:
- AT+QHTTPGET=80 //发送 HTTP GET 请求,最大响应时间为 80 秒。+QHTTPGET: 0,200,601710 //若 HTTP 响应头信息中包括 CONTENT-LENGTH,则将上报content_length信息
- AT+QHTTPREADFILE=“ota.bin”,80 //读取 HTTP 响应头信息和响应体并将其储存到 ota.bin。
HTTP 会话关闭的最长等待时间为 80 秒。