优秀的网页设计网站,网站安全建设,在百度做网站怎么做,装修房子的app软件哪个好STM32 OTA应用开发——通过串口/RS485实现OTA升级#xff08;方式1#xff09; 目录STM32 OTA应用开发——通过串口/RS485实现OTA升级#xff08;方式1#xff09;前言1 环境搭建2 功能描述3 程序编写3.1 BootLoader部分3.2 APP的制作4 修改工程中的内存配置4.1 Bootloader…STM32 OTA应用开发——通过串口/RS485实现OTA升级方式1 目录STM32 OTA应用开发——通过串口/RS485实现OTA升级方式1前言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。例如通过4G5GWiFI蓝牙等无线通讯进行下载升级的可以称为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的内存分成4个区域来实现一个OTA的功能。 分区表如下
nameoffsetsizefunctionboot0x080000000x00004000存放boot程序setting0x080040000x00004000存放升级相关的配置参数app0x080080000x00018000存放应用程序download0x080200000x00020000存放需要升级的新固件方案介绍 1bootloader部分 运行时从setting分区里面读取升级相关的数据确定是否需要升级如果需要则把download分区的固件搬运到app分区如果不需要升级则直接跳转到app分区。另外使用串口1来打印运行的一些信息。 2APP部分 通过串口2或者RS485连接到PC端然后等待上位机发送特定的升级命令如果MCU收到命令则进入下载模式然后通过串口2或者RS485传输新固件到download分区并且在下载完成后把升级标志写入到setting分区里面。 我这里图方便串口传输固件的方式我采用的是Ymodem协议因为这个协议很多tool都可以用就不用专门做一个上位机了。如果你想用其他的协议或者自定义协议其实都是可以的稍做修改就行。 3 程序编写
3.1 BootLoader部分
不管用的是什么MCU要使用OTA都离不开BootLoaderBootLoader是一个统称它其实只是一段引导程序在MCU启动的时候会先运行这段代码判断是否需要升级如果不需要升级就跳转到APP分区运行用户代码如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件然后再跳转到APP分区运行新固件从而实现OTA升级。 BootLoader的制作需要根据实际的需求来做不同的运行方式或者升级方式在做法上都是有区别的包括BootLoader所需要的内存空间也不尽相同。 不过不管是用什么方式Bootloader都应该尽可能做的更小更简洁这样的话内存的开销就更小对于内存较小的MCU来说压力就没那么大了。
示例代码如下 分区定义
#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
#define BOOT_SECTOR_SIZE 0x4000
#define SETTING_SECTOR_ADDR 0x08004000
#define SETTING_SECTOR_SIZE 0x4000
#define APP_SECTOR_ADDR 0x08008000 // APP sector start address
#define APP_SECTOR_SIZE 0x18000 // APP sector size
#define DOWNLOAD_SECTOR_ADDR 0x08020000 // Download sector start address
#define DOWNLOAD_SECTOR_SIZE 0x20000 // Download sector size 程序跳转
uint8_t jump_app(uint32_t app_addr)
{uint32_t jump_addr;jump_callback cb;if (((*(__IO uint32_t*)app_addr) 0x2FFE0000 ) 0x20000000) { jump_addr *(__IO uint32_t*) (app_addr 4); cb (jump_callback)jump_addr; __set_MSP(*(__IO uint32_t*)app_addr); cb();return 1;} return 0;
}主函数
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 | 0x08000000 | 0x00004000 |\r\n);uart_log(| setting | 0x08004000 | 0x00004000 |\r\n);uart_log(| app | 0x08008000 | 0x00018000 |\r\n);uart_log(| download | 0x08020000 | 0x00020000 |\r\n);uart_log(\r\n);
}int main()
{process_status process;uint16_t i;uint8_t boot_state;uint8_t down_buf[128];uint32_t down_addr;uint32_t app_addr;delay_init(168);uart_init(115200);print_boot_message();boot_parameter.process read_setting_boot_state();boot_parameter.addr APP_SECTOR_ADDR;while (1) {process get_boot_state();switch (process) {case START_PROGRAM:uart_log(start app...\r\n);delay_ms(50);if (!jump_app(boot_parameter.addr)) {uart_log(no program\r\n);delay_ms(1000);}uart_log(start app failed\r\n);break;case UPDATE_PROGRAM:uart_log(update app program...\r\n);app_addr APP_SECTOR_ADDR;down_addr DOWNLOAD_SECTOR_ADDR;uart_log(app addr: 0x%08X \r\n, app_addr);uart_log(down addr: 0x%08X \r\n, down_addr);uart_log(erase mcu flash...\r\n);mcu_flash_erase(app_addr, APP_ERASE_SECTORS_NUM); uart_log(mcu flash erase success\r\n);uart_log(write mcu flash...\r\n);// memset(down_buf, 0, sizeof(down_buf));for (i 0; i (APP_SECTOR_SIZE / 1024) * 8; i){mcu_flash_read(down_addr, down_buf[0], 128);delay_ms(5);mcu_flash_write(app_addr, down_buf[0], 128);delay_ms(5);down_addr 128;app_addr 128;// uart_log(mcu_flash_write: %d\r\n, i);}uart_log(mcu flash write success\r\n);set_boot_state(UPDATE_SUCCESS);break;case UPDATE_SUCCESS:uart_log(update success\r\n);boot_state UPDATE_SUCCESS_STATE;write_setting_boot_state(boot_state);set_boot_state(START_PROGRAM);break;default:break;}}
}关于bootloader详细的讲解可以看下我之前发的博客 STM32 OTA应用开发——自制BootLoader 完整代码下载地址https://download.csdn.net/download/ShenZhen_zixian/87546126
3.2 APP的制作
APP部分根据自己实际的功能来做我这里用的是串口或者RS485连接PC端然后传输固件的协议用的是Ymodem。 当然了协议也是可以自定义只要能正确的把固件从PC端搬运到MCU的flash就行了。
示例代码如下 Ymodem协议部分 注详细的协议解析这里就不讲解了不懂的同学自行查阅资料。
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 DOWNLOAD_SECTOR_ADDR;mcu_flash_erase(ymodem.addr, ERASE_SECTORS);ymodem_ack();ymodem_c();ymodem.status;}else if (type 1) {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_handle(void)
{uint8_t boot_state;process_status process;process get_ymodem_status();switch (process) {case START_PROGRAM:break;case UPDATE_PROGRAM:ymodem_c();delay_ms(1000);break;case UPDATE_SUCCESS:boot_state UPDATE_PROGRAM_STATE;mcu_flash_erase(SETTING_BOOT_STATE, 1);mcu_flash_write(SETTING_BOOT_STATE, boot_state, 1);// mcu_flash_read(SETTING_BOOT_STATE, boot_state, 1);// uart_log(boot_state:%d\r\n, boot_state);uart_log(firmware download success\r\n);uart_log(system reboot...\r\n);delay_ms(2000);system_reboot();break;default:break;}
}void ymodem_init(void)
{RS485_Init(115200);timer_init();queue_initiate(rx_queue);
}主函数
#define APP_VERSION V100void 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)
{delay_init(168);uart_init(115200);ymodem_init();print_boot_message();uart_log (app init success\r\n);while (1){ymodem_handle();}
}修改中断向量 bootloader的运行地址是在起始地址上的所以中断向量是0不用改。 但是app的运行地址是在起始地址上做了偏移的所以中断向量也要改不然会运行会出问题。
#define VECT_TAB_OFFSET 0x8000注这个变量定义在system_stm32f4xx.c中可以找到。
完整代码下载地址https://download.csdn.net/download/ShenZhen_zixian/87546126
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开始运行代码 不需要升级时直接跳转到App区如下图
2进入烧录模式 进入APP之后往串口2/RS485发送一个字符1进入升级模式然后通过调试工具发送新固件的bin文件。 注为了方便调试才用了一个字符1实际使用的话最好改一下太简单的话容易出现误操作。 串口调试窗口log如下图
3通过Ymodem传输新固件 调试工具我用的是XShell实际上用其他工具也行只要支持Ymodem方式传输文件即可。 4升级固件 固件搬运完成后自动重启重新运行Bootloader然后进行固件的升级。 至此整个升级流程就走完了。
结束语
好了关于自制BootLoader并实现串口以及RS485 OTA升级的介绍就讲到这里本文列举的例子其实只是升级的其中一种方式只是提供一个思路不是唯一的方法实际上最好还是根据自己实际的需求来做。 需要源码的同学可以在下面的链接下载我把BootLoader和APP都上传了。 如果你有什么问题或者有更好的方法欢迎在评论区留言。
完整代码下载地址https://download.csdn.net/download/ShenZhen_zixian/87546126 更多相关文章 OTA应用开发系列合集https://blog.csdn.net/ShenZhen_zixian/article/details/129074047