STM32F405+4G模块OTA固件升级调试记录

#简介
项目上需要支持OTA固件升级功能,小结一下调试过程中需要的问题和疑问和解决方法。
注意:由于是初步调试,代码中存在很多不合理和需要优化的地方,请勿直接使用。

#硬件
MCU:STM32F405
通讯模块:EC600N-CN

#思路
首先要理清整个OTA的大体思路,OTA的过程就是下载,保存,跳转这三个步骤

  1. 下载我这里通过CAT1模块的HTTP功能。
  2. 保存这里直接通过STM32库中的FLASH读写接口来实现。
  3. 跳转这里参考网上一些资料写了一个Bootloader用来把下载好的固件拷贝到APP区,然后跳转到APP区执行,这里我没有使用网上的直接跳转到APP2执行的思路。

#问题和解决方法
1、本来通过EC600N的http和file命令文档发现可以将固件一次性下载然后存储到EC600N的本地FILE中,然后通过FILE命令慢慢读取数据再保存,但是发现生成的bin文件有160多KB,通过命令查询到EC600N的FILE的自带存储只有80多KB(可能模块型号不同,也有更高的存储空间),那么一次性下载的方案就无法实现了,然而EC600N的HTTP功能中可以实现文件的分片下载,从文件的指定位置下载指定大小的功能
HTTP获取指定位置指定长度1
HTTP获取指定位置指定长度2
那么就可以通过将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相关:

  1. AT+QFLST:获取UFS中文件信息,方便调试
  2. AT+QFDEL:删除UFS中文件,我将文件全部删除
  3. AT+QFOPEN:打开指定文件,这里文件的handle需要通过AT+QFREAD读取时,通过AT返回来记录下来。
  4. AT+QFREAD:读取指定名字的文件,有文件则会返回handle文件句柄
  5. AT+QFSEEK:读取数据时,可设置读取开始的指针位置,设置从指针位置读取
  6. AT+QFCLOSE:关闭文件

HTTP相关:

  1. AT+QHTTPGET=80 //发送 HTTP GET 请求,最大响应时间为 80 秒。+QHTTPGET: 0,200,601710 //若 HTTP 响应头信息中包括 CONTENT-LENGTH,则将上报content_length信息
  2. AT+QHTTPREADFILE=“ota.bin”,80 //读取 HTTP 响应头信息和响应体并将其储存到 ota.bin。
    HTTP 会话关闭的最长等待时间为 80 秒。
  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值