STM32 OTA应用开发

您所在的位置:网站首页 串口助手app STM32 OTA应用开发

STM32 OTA应用开发

2023-03-20 08:49| 来源: 网络整理| 查看: 265

STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式2)

目录 STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式2)前言1 环境搭建2 功能描述3 程序编写3.1 BootLoader部分3.2 APP的制作 4 修改工程中的内存配置4.1 Bootloader工程内存配置4.2 APP工程内存配置 5 烧录相关配置5.1 BootLoader部分5.2 APP部分 6 运行测试结束语

前言

什么是OTA?

百度百科:空中下载技术(Over-the-Air Technology; OTA),是通过移动通信的空中接口实现对移动终端设备及SIM卡数据进行远程管理的技术。经过公网多年的应用与发展,已十分成熟,网络运营商通过OTA技术实现SIM卡远程管理,还能提供移动化的新业务下载功能。

实际上,现在我们所说的OTA比百度百科的定义还要更广泛,OTA的形式已经不再局限于手机和SIM卡,只要涉及到远程下载升级程序的方式我们都可以称之为OTA。例如通过4G,5G,WiFI,蓝牙等无线通讯进行下载升级的可以称为OTA,通过U盘,RS485等串行接口进行升级的也可以称之为OTA。

OTA的作用? OTA的意义在于它在一定程度上突破了距离的限制,在不借助烧录器的情况下完成固件的下载升级,极大的方便了产品的升级和维护,降低售后成本。

什么是BootLoader?

百度百科:在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。

实际上,BootLoader不仅仅在操作系统上使用,在一些内存小,功能应用较为简单的单片机设备上面也可以通过BootLoader来完成OTA升级。

我之前也有发过一些关于STM32远程OTA的文章,实现的方式有很多种,感兴趣的同学可以去看一下。 OTA应用开发系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

那么这一期我来介绍一下如何自己制作一个BootLoader程序,并且通过串口或者RS485实现OTA升级。

1 环境搭建

关于STM32以及Keil的环境这里就不具体介绍了,网上教程也很多,不懂的同学自行查阅资料。

2 功能描述

在做bootloader之前一定要先想好升级的途径和方式,这样才好规划分区以及制作bootloader。 关于bootloader详细的讲解,可以看下我之前发的博客: STM32 OTA应用开发——自制BootLoader

分区介绍: 我用的是STM32F407,内存是512K的(想用内存更小的MCU也是可以的,改下各个分区的内存分配就行了)。 注:F4系列的MCU不像F1那样,内存扇区都很大(最少也是16K),而且同一块扇区只能一起擦除,所以就没办法分的那么细了。详细的内存分布可以参考下面的两个图。 STM32F4x扇区分布图如下: 请添加图片描述 STM32F1x扇区分布图如下: 请添加图片描述 那么我这里呢,就用一个512k的内存,分成3个区域,来实现一个OTA的功能。 分区表如下:

nameoffsetsizefunctionboot0x080000000x00004000存放boot程序setting0x080040000x00004000存放设备需要保存的一些参数app0x080080000x00078000存放应用程序

请添加图片描述

方案介绍: 1)bootloader部分: 开始运行后先等待5s,在这个时间内如果收到串口2或者RS485的升级命令就进入升级模式,如果超时则跳转到用户程序(APP)。 在升级模式,可以通过串口2或者RS485传输要升级的固件,传输的数据协议我这里图方便就直接用Ymodem了,不知道Ymodem协议的可以先自行查阅一下资料。 请添加图片描述

2)APP部分: APP部分修改一下中断向量表地址即可,其他的随便你做什么应用。 另外,我在分区的时候留了一块settimg区,在实际的应该中如果有需要记录一些掉电后还能保存的数据,那么这块区域就可以用得上了。

3 程序编写 3.1 BootLoader部分

不管用的是什么MCU,要使用OTA都离不开BootLoader,BootLoader是一个统称,它其实只是一段引导程序,在MCU启动的时候会先运行这段代码,判断是否需要升级,如果不需要升级就跳转到APP分区运行用户代码,如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件,然后再跳转到APP分区运行新固件,从而实现OTA升级。 BootLoader的制作需要根据实际的需求来做,不同的运行方式或者升级方式在做法上都是有区别的,包括BootLoader所需要的内存空间也不尽相同。 不过不管是用什么方式,Bootloader都应该尽可能做的更小更简洁,这样的话内存的开销就更小,对于内存较小的MCU来说压力就没那么大了。

注:我这里是基于正点原子的工程模板改的,增加了自己的功能。

示例代码如下: Bootloader分区定义:

#define FLASH_SECTOR_SIZE 1024 #define FLASH_SECTOR_NUM 512 // 512K #define FLASH_START_ADDR ((uint32_t)0x8000000) #define FLASH_END_ADDR ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE)) //flash sector addr #define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) //sector0 addr, 16 Kbytes #define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) //sector1 addr, 16 Kbytes #define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) //sector2 addr, 16 Kbytes #define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) //sector3 addr, 16 Kbytes #define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) //sector4 addr, 64 Kbytes #define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) //sector5 addr, 128 Kbytes #define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) //sector6 addr, 128 Kbytes #define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) //sector7 addr, 128 Kbytes #define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) //sector8 addr, 128 Kbytes #define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) //sector9 addr, 128 Kbytes #define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) //sector10 addr,128 Kbytes #define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) //sector11 addr,128 Kbytes #define BOOT_SECTOR_ADDR 0x08000000 // BOOT sector start addres #define BOOT_SECTOR_SIZE 0x4000 // BOOT sector size #define SETTING_SECTOR_ADDR 0x08004000 // SETTING sector start addres #define SETTING_SECTOR_SIZE 0x4000 // SETTING sector size #define APP_SECTOR_ADDR 0x08008000 // APP sector start address #define APP_SECTOR_SIZE 0x78000 // APP sector size #define BOOT_ERASE_SECTORS_NUM 1 // 16k #define SETTING_ERASE_SECTORS_NUM 1 // 16k #define APP_ERASE_SECTORS_NUM 6 // 16k + 16k + 64k + 128k + 128k + 128k

main函数:

#include "bootloader.h" #include "usart.h" #include "rs485.h" #include "delay.h" #include "ymodem.h" #define WAIT_TIMEOUT 5 void print_boot_message(void) { uart_log("---------- Enter BootLoader ----------\r\n"); uart_log("\r\n"); uart_log("======== flash pration table =========\r\n"); uart_log("| name | offset | size |\r\n"); uart_log("--------------------------------------\r\n"); uart_log("| boot | 0x%08X | 0x%08X |\r\n", BOOT_SECTOR_ADDR, BOOT_SECTOR_SIZE); uart_log("| setting | 0x%08X | 0x%08X |\r\n", SETTING_SECTOR_ADDR, SETTING_SECTOR_SIZE); uart_log("| app | 0x%08X | 0x%08X |\r\n", APP_SECTOR_ADDR, APP_SECTOR_SIZE); uart_log("======================================\r\n"); } void print_wait_message(void) { uart_log("------- Please enter parameter -------\r\n"); uart_log("[1].Start program\r\n"); uart_log("[2].Update program\r\n"); uart_log("--------------------------------------\r\n"); } int main() { process_status process; uint16_t timerout = 0; delay_init(168); uart_init(115200); ymodem_init(); print_boot_message(); print_wait_message(); while (1) { process = get_ymodem_status(); switch (process) { case WAIT_START_PROGRAM: uart_log("wait start app...(%ds)\r\n", WAIT_TIMEOUT - timerout); delay_ms(1000); timerout ++; if(timerout >= WAIT_TIMEOUT) { set_ymodem_status(START_PROGRAM); } break; case START_PROGRAM: uart_log("start app...\r\n"); delay_ms(50); if (!jump_app(APP_SECTOR_ADDR)) { uart_log("start app failed: app no program\r\n"); delay_ms(1000); } break; case UPDATE_PROGRAM: ymodem_c(); uart_log("update app program...\r\n"); delay_ms(1000); break; case UPDATE_SUCCESS: uart_log("update success\r\n"); uart_log("system reboot...\r\n"); delay_ms(1000); system_reboot(); break; default: break; } } }

Ymodem协议处理:

#define YMODEM_SOH 0x01 #define YMODEM_STX 0x02 #define YMODEM_EOT 0x04 #define YMODEM_ACK 0x06 #define YMODEM_NAK 0x15 #define YMODEM_CA 0x18 #define YMODEM_C 0x43 #define MAX_QUEUE_SIZE 1200 typedef void (*ymodem_callback)(process_status); typedef struct { process_status process; uint8_t status; uint8_t id; uint32_t addr; uint8_t sectors_size; ymodem_callback cb; } ymodem_t; //顺序循环队列的结构体定义如下: typedef struct { uint8_t queue[MAX_QUEUE_SIZE]; int rear; //队尾指针 int front; //队头指针 int count; //计数器 } seq_queue_t; typedef struct { uint8_t data[1200]; uint16_t len; } download_buf_t; void ymodem_ack(void) { uint8_t buf[3]; buf[0] = YMODEM_ACK; buf[1] = 0x0D; buf[2] = 0x0A; RS485_Send_Data(buf, 3); } void ymodem_nack(void) { uint8_t buf[3]; buf[0] = YMODEM_NAK; buf[1] = 0x0D; buf[2] = 0x0A; RS485_Send_Data(buf, 3); } void ymodem_c(void) { uint8_t buf[3]; buf[0] = YMODEM_C; buf[1] = 0x0D; buf[2] = 0x0A; RS485_Send_Data(buf, 3); } void set_ymodem_status(process_status process) { ymodem.process = process; } process_status get_ymodem_status(void) { process_status process = ymodem.process; return process; } void ymodem_start(ymodem_callback cb) { if (ymodem.status == 0) { ymodem.cb = cb; } } void ymodem_recv(download_buf_t *p) { uint8_t type = p->data[0]; switch (ymodem.status) { case 0: if (type == YMODEM_SOH) { ymodem.process = BUSY; ymodem.addr = APP_SECTOR_ADDR; uart_log("erase flash: 0x%08X\r\n", APP_SECTOR_ADDR); mcu_flash_erase(ymodem.addr, APP_ERASE_SECTORS_NUM); uart_log("erase flash success\r\n"); ymodem_ack(); ymodem_c(); ymodem.status++; } else if (type == '1') { uart_log("start program now\r\n"); ymodem.process = START_PROGRAM; } else if (type == '2') { uart_log("enter update mode\r\n"); ymodem.process = UPDATE_PROGRAM; } break; case 1: if (type == YMODEM_SOH || type == YMODEM_STX) { if (type == YMODEM_SOH) { mcu_flash_write(ymodem.addr, &p->data[3], 128); ymodem.addr += 128; } else { mcu_flash_write(ymodem.addr, &p->data[3], 1024); ymodem.addr += 1024; } ymodem_ack(); } else if (type == YMODEM_EOT) { ymodem_nack(); ymodem.status++; } else { ymodem.status = 0; } break; case 2: if (type == YMODEM_EOT) { ymodem_ack(); ymodem_c(); ymodem.status++; } break; case 3: if (type == YMODEM_SOH) { ymodem_ack(); ymodem.status = 0; ymodem.process = UPDATE_SUCCESS; } } p->len = 0; } void ymodem_init(void) { RS485_Init(115200); timer_init(); queue_initiate(&rx_queue); }

关于bootloader详细的讲解,可以看下我之前发的博客: STM32 OTA应用开发——自制BootLoader 完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

3.2 APP的制作

APP部分根据自己实际的功能来做,只要记得修改中断向量表地址即可。地址的值等于你APP区的起始地址。

示例代码如下: main函数:

#include "main.h" #include "usart.h" #include "delay.h" #define APP_VERSION "V100" #define NVIC_VTOR_MASK 0x3FFFFF80 #define APP_PART_ADDR 0x08008000 void ota_app_vtor_reconfig(void) { /* Set the Vector Table base location by user application firmware definition */ SCB->VTOR = APP_PART_ADDR & NVIC_VTOR_MASK; } void led_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOF, &GPIO_InitStructure); GPIO_SetBits(GPIOF, GPIO_Pin_9); } void print_boot_message(void) { uart_log("======================================\r\n"); uart_log("-------------- Enter APP -------------\r\n"); uart_log ("app version is: %s\r\n", APP_VERSION); uart_log("======================================\r\n"); } int main(void) { ota_app_vtor_reconfig(); delay_init(168); uart_init(115200); print_boot_message(); led_init(); uart_log ("app init success\r\n"); while (1) { GPIO_SetBits(GPIOF, GPIO_Pin_9); delay_ms(1000); GPIO_ResetBits(GPIOF, GPIO_Pin_9); delay_ms(1000); } }

完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

4 修改工程中的内存配置

因为我们对stm32的内存进行了分区,不同的代码要存放在不同的区域,因此,我们在编译工程之前需要先定义好各自的区域,以免出现内存越界。

4.1 Bootloader工程内存配置

Bootloader的起始地址不需要改,按flash默认地址即可,size需要改成实际分区大小。

请添加图片描述

4.2 APP工程内存配置

APP的起始地址和size都需要根据实际的分区来改。 请添加图片描述

5 烧录相关配置

我们的Bootloader做好以后需要烧录到MCU里面,可以直接用Keil uVison来下载,也可以用J-Flash或者其他,这个都没关系,但是要注意内存的分配,要把固件烧到对应的内存地址上。

5.1 BootLoader部分

1)使用Keil uVision下载 如果是用keil下载的话,需要注意flash的配置,具体如下: 请添加图片描述 2)使用其他下载工具 如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08000000就好了。

5.2 APP部分

1)使用Keil uVision下载 跟BootLoader一样,我们按照前面分配好的空间配置APP的参数即可。 请添加图片描述

2)使用其他下载工具 如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08008000就好了。

6 运行测试

用串口助手查看运行log(我这里用的是XShell,用其他的也是可以的)。

1)开始运行代码 等待5s,如果不需要升级就跳转到App区,如下图: 请添加图片描述

2)发送命令1 在等待的5s内通过串口2或者RS485发送一个’1’,直接跳转到APP。 注:我这里为了方便调试才用的这种方式,实际上可以根据自己的需求来做。 请添加图片描述

3)发送命令2,进入升级模式 在等待的5s内通过串口2或者RS485发送一个’2’,进入升级模式。 注:我这里为了方便调试才用的这种方式,实际上可以根据自己的需求来做。比如用按键进入,或者用其他串口,USB之类的,也可以在APP部分做这个功能。 串口调试窗口log如下图: 请添加图片描述

4)通过Ymodem传输新固件 调试工具我用的是XShell,实际上用其他工具也行,只要支持Ymodem方式传输文件即可。 请添加图片描述 请添加图片描述

5)升级固件 固件升级完成后自动重启,重新运行Bootloader和APP。 请添加图片描述

至此,整个升级流程就走完了。

结束语

好了,关于自制BootLoader并实现串口以及RS485 OTA升级的介绍就讲到这里,本文列举的例子其实只是升级的其中一种方式,只是提供一个思路,不是唯一的方法,实际上最好还是根据自己实际的需求来做。我之前也发给几篇OTA相关的文章,用的都是不同的方式,各有各的优点和缺点,感兴趣的同学可以去看一下。 需要源码的同学可以在下面的链接下载,我把BootLoader和APP都上传了。 如果你有什么问题或者有更好的方法,欢迎在评论区留言。

完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87553496 更多相关文章: OTA应用开发系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3