STM32 OTA应用开发——通过USB实现OTA升级

news/2024/3/28 18:07:00/文章来源:https://blog.csdn.net/ShenZhen_zixian/article/details/129107916

STM32 OTA应用开发——通过USB实现OTA升级

目录

  • STM32 OTA应用开发——通过USB实现OTA升级
    • 前言
    • 1 环境搭建
    • 2 功能描述
    • 3 BootLoader的制作
    • 4 APP的制作
    • 5 烧录下载配置
    • 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的文章,但用的是第三方BootLoader,而且是基于操作系统实现的,BootLoader占用的内存也比较大,而且不开源。
那么这一期我就来介绍一下如何自己制作一个BootLoader程序,并且通过USB实现OTA升级。

1 环境搭建

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

2 功能描述

在做bootloader之前一定要先想好升级的途径和方式,这样才好规划分区以及制作bootloader。

方案介绍:
1)bootloader部分:
运行时从setting里面读一些参数,确定是否需要升级,如果需要,则把download分区的固件搬运到app分区,如果不需要升级则直接跳转到app分区。
2)APP部分:
运行时先连接USB(以USB CDC的方式),然后等待上位机发送升级命令,如果收到命令,则进入下载模式。
我这里图方便,USB传输固件的方式我采用的是Ymodem协议,因为这个协议很多tool都可以用,就不用专门做一个上位机了。如果你想用其他的协议或者自定义协议其实都是可以的,稍做修改就行。

在这里插入图片描述

分区介绍:
我用的是STM32F103,内存是128K的(想用内存更小的MCU也是可以的,改下各个分区的内存分配就行了)。

分区表如下:

nameoffsetsize
boot0x080000000x00003000
setting0x080030000x00001000
app0x080040000x0000E000
download0x080120000x0000E000

请添加图片描述

3 BootLoader的制作

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

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

#define FLASH_SECTOR_SIZE       1024
#define FLASH_SECTOR_NUM        128    // 128K
#define FLASH_START_ADDR        ((uint32_t)0x8000000)
#define FLASH_END_ADDR          ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))#define BOOT_SECTOR_ADDR        0x08000000     // BOOT sector start address 
#define BOOT_SECTOR_SIZE        0x3000         // BOOT sector size
#define SETTING_SECTOR_ADDR     0x08003000     // SETTING sector start address 
#define SETTING_SECTOR_SIZE     0x1000         // SETTING sector size
#define APP_SECTOR_ADDR         0x08004000     // APP sector start address  
#define APP_SECTOR_SIZE         0xE000         // APP sector size
#define DOWNLOAD_SECTOR_ADDR    0x08012000     // Download sector start address
#define DOWNLOAD_SECTOR_SIZE    0xE000         // 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)
{printf("---------- Enter BootLoader ----------\r\n");printf("\r\n");printf("======== flash pration table =========\r\n");printf("| name     | offset     | size       |\r\n");printf("--------------------------------------\r\n");printf("| boot     | 0x08000000 | 0x00003000 |\r\n");printf("| setting  | 0x08003000 | 0x00001000 |\r\n");printf("| app      | 0x08004000 | 0x0000E000 |\r\n");printf("| download | 0x08012000 | 0x0000E000 |\r\n");printf("======================================\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;uart1_init();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:printf("start app...\r\n");delay_ms(50);if (!jump_app(boot_parameter.addr)) {printf("no program\r\n");delay_ms(1000);}printf("start app failed\r\n");break;case UPDATE_PROGRAM:printf("update app program...\r\n");app_addr = APP_SECTOR_ADDR;down_addr = DOWNLOAD_SECTOR_ADDR;printf("app addr: 0x%08X \r\n", app_addr);printf("down addr: 0x%08X \r\n", down_addr);printf("erase mcu flash...\r\n");mcu_flash_erase(app_addr, APP_ERASE_SECTORS);  printf("mcu flash erase success\r\n");printf("write mcu flash...\r\n");// memset(down_buf, 0, sizeof(down_buf));for (i = 0; i < APP_ERASE_SECTORS * 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;// printf("mcu_flash_write: %d\r\n", i);}printf("mcu flash write success\r\n");set_boot_state(UPDATE_SUCCESS);break;case UPDATE_SUCCESS:printf("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/87462312

4 APP的制作

APP部分根据自己实际的功能来做,我这里用的是USB CDC连接PC端,然后传输固件的协议用的是Ymodem。
实际上USB也可以用HID或者其他的,协议也是可以自定义,只要能正确的把固件从PC端搬运到MCU的flash就行了。

示例代码如下:
Ymodem协议部分:
注:详细的协议解析这里就不讲解了,不懂的同学自行查阅资料。

void ymodem_ack(void) 
{usb_printf("%c\r", YMODEM_ACK);
}void ymodem_nack(void) 
{usb_printf("%c\r", YMODEM_NAK);}void ymodem_c(void) 
{usb_printf("%c\r", YMODEM_C);
}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;mcu_flash_erase(ymodem.addr, ERASE_SECTORS);ymodem_ack();ymodem_c();ymodem.status++;}else if (type == '1') {// 为了方便调试,简单发一个字符"1"就可以进入升级模式了printf("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:usb_printf("C\r\n");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);printf("firmware download success\r\n");printf("system reboot...\r\n");delay_ms(2000);system_reboot();break;default:break;}
}

主函数:

#define APP_VERSION   "V100"
void print_boot_message(void)
{printf("======================================\r\n");printf("-------------- Enter APP -------------\r\n");printf ("app version is: %s\r\n", APP_VERSION);printf("======================================\r\n");
}void user_usb_init(void)
{USB_Port_Set(0); 	delay_ms(700);USB_Port_Set(1);	Set_USBClock();   USB_Interrupts_Config();    USB_Init();	
}int main(void)
{SysTick_Init_Config();USART1_Init_Config(115200);print_boot_message();ymodem_init();user_usb_init();printf ("app init success\r\n");while (1){ymodem_handle();}
}

修改中断向量:
bootloader的运行地址是在起始地址上的,所以中断向量是0,不用改。
但是app的运行地址是在起始地址上做了偏移的,所以中断向量也要改,不然会运行会出问题。

#define VECT_TAB_OFFSET  0x4000

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

5 烧录下载配置

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

1)BootLoader部分:
我这里做出来的bootloader bin只有8K,不过为了方便后续在这部分增加新功能,我实际分配了12K的空间,地址区间是0x08000000-0x08003000。

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

2)APP部分:
跟BootLoader一样,我们按照前面分配好的空间配置APP的参数。
在这里插入图片描述
在这里插入图片描述
如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08004000就好了。

6 运行测试

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

不需要升级时直接跳转到App区,如下图:
在这里插入图片描述

进入APP之后,往USB发送一个字符"1",进入升级模式,然后通过调试工具发送新固件的bin文件。
注:为了方便调试才用了一个字符"1",实际使用的话最好改一下,太简单的话容易出现误操作。调试工具我用的是XShell,实际上用其他工具也行,只要支持Ymodem方式传输文件即可。
串口调试窗口log如下图:
在这里插入图片描述

USB调试窗口log如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结束语

好了,关于自制BootLoader并实现USB OTA升级的介绍就讲到这里,本文列举的例子其实只是升级的其中一种方式,只是提供一个思路,不是唯一的方法,实际上最好还是根据自己实际的需求来做。
需要源码的同学可以在下面的链接下载,我把BootLoader和APP都上传了。

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

本文如果你有什么问题或者有更好的方法,欢迎在评论区留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73516.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud系列】SpringCloudConfig配置中心

前言 我们在开发过程中总是会有各种各样的配置&#xff0c;比较如数据库连接配置&#xff0c;Mybatis配置等等各种组件的配置&#xff0c;这些配置都放在yml中&#xff0c;如果想要变更这些配置&#xff0c;需要修改yml文件&#xff0c;然后重新部署项目才能生效&#xff0c;同…

后端接收格式为x-www-form-urlencoded的数据

1.x-www-form-urlencoded是什么&#xff1f; x-www-form-urlencoded纸面翻译即所谓url格式的编码&#xff0c;是post的默认Content-Type&#xff0c;其实就是一种编码格式&#xff0c;类似json也是一种编码传输格式。form表单中使用 form的enctype属性为编码方式&#xff0…

GoFrame工程目录设计介绍

GoFrame框架针对业务项目的目录设计&#xff0c;主体的思想来源于三层架构&#xff0c;但在具体实现中&#xff0c;对其进行了一定的改进和细化使其更符合工程实践和时代进步。 一.工程目录结构 GoFrame业务项目基本目录结构如下&#xff1a; 二.目录结构解释 对外接口 对…

AWS攻略——使用中转网关(Transit Gateway)连接不同区域(Region)VPC

文章目录Peering方案Transit Gateway方案环境准备创建Transit Gateway Peering Connection接受邀请修改中转网关路由修改被邀请方中转网关路由修改邀请方中转网关路由测试修改Public子网路由知识点参考资料区别于 《AWS攻略——使用中转网关(Transit Gateway)连接同区域(Region…

记录--前端项目中运行 npm run xxx 的时候发生了什么?

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 npm 是 node 捆绑的依赖管理器&#xff0c;常用程度可想而知。那么你每天都在 npm/yarn run 的命令到底是如何运行项目的呢&#xff1f; 前端项目中运行 npm run xxx 的时候发生了什么&#xff1f;大家…

LinkSLA智能运维技术派-Redis的监控

Redis是一个开源&#xff0c;内存存储的数据服务器&#xff0c;可用作数据库、高速缓存和消息队列代理等场景。 首先我们对内存进行监控&#xff0c;主要指标如下&#xff1a; - used_memory:使用内存 - used_memory_rss:从操作系统分配的内存 - mem_fragmentation_ratio:内…

Mac mini 外接移动硬盘无法写入或者无法显示的解决方法

文章目录1. 背景2. 让NTFS格式的移动硬盘正常读写方法3. 打开“启动安全性实用工具”4. 更改“安全启动”设置1. 背景 刚买mac min&#xff08;2023年2月3日&#xff09;不久&#xff0c;发现macOS的玩起来并不容易&#xff0c;勇习惯了windows系统的习惯&#xff0c;感觉 mac…

免去打包烦恼,自动构建你的GitHub Pages|玩转GitHub Pages三部曲(二)

本文讲述了如何利用 GitHub Actions 来自动构建 GitHub Pages 项目&#xff0c;免去繁琐的手动构建再提交过程&#xff0c;让你专注于写作。大家的点赞和互动是我更文的动力 /(ㄒoㄒ)/ 所以我决定发起一项活动&#xff0c;到三月三十一日统计&#xff0c;留言次数和赞赏次数最多…

selenium基本操作

爬虫与反爬虫之间的斗争爬虫&#xff1a;对某个网站数据或图片感兴趣&#xff0c;开始抓取网站信息&#xff1b;网站&#xff1a;请求次数频繁&#xff0c;并且访问ip固定&#xff0c;user_agent也是python&#xff0c;开始限制访问&#xff1b;爬虫&#xff1a;通过设置user_a…

ifconfig不显示ipv4地址,ifconfig eth0 192.168.5.9失败

ifconfig eth0 192.168.5.9设置ip地址后&#xff0c;通过ifconfig仍然没有ipv4地址&#xff1a; 一、 执行ifup eth0启动eth0: ifconfig、ifup、ifdown &#xff1a;这三个命令的用途都是启动网络接口&#xff0c;不过&#xff0c;ifup 与 ifdown 仅就 /etc/sysconfig/network-…

【数据存储】浮点型在内存中的存储

目录 一、存储现象 二、IEEE标准规范 1.存储 2.读取 三、举例验证 1.存储 2.读取 浮点型存储的标准是IEEE&#xff08;电气电子工程师学会&#xff09;754制定的。 一、存储现象 浮点数由于其有小数点的特殊性&#xff0c;有很多浮点数是不能精确存储的&#xff0c;如&#…

阅读HAL源码之重点总结

HAL封装中有如下特点&#xff08;自己总结的&#xff09;&#xff1a; 特定外设要设置的参数组成一个结构体&#xff1b; 特定外设所有寄存器组成一个结构体&#xff1b; 地址基本都是通过宏来定义的&#xff0c;定义了各外设的起始地址&#xff0c;也就是对应寄存器结构体的地…

优秀外贸业务员必备的业务技能

2023年的春天&#xff0c;可谓是外贸企业三年寒冬后的第一个春天。外贸行业离不开的就是优秀的外贸业务员&#xff0c;那么一个优秀的外贸业务员需要有哪些必备的技能呢&#xff1f;跟着我一起来看看吧&#xff01;一、电话开发客户能力首先&#xff0c;要知道&#xff0c;声音…

【unittest学习】unittest框架主要功能

1.认识unittest在 Python 中有诸多单元测试框架&#xff0c;如 doctest、unittest、pytest、nose 等&#xff0c;Python 2.1 及其以后的版本已经将 unittest 作为一个标准模块放入 Python 开发包中。2.认识单元测试不用单元测试框架能写单元测试吗&#xff1f;答案是肯定的。单…

华为OD机试题,用 Java 解【最小施肥机能效】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

无线通信时代的新技术----信标( Beacon)

随着IT技术的发展&#xff0c;无线通信技术也在不断发展。 现已根据预期用途开发了各种无线通信技术&#xff0c;例如 NFC、WIFI、Bluetooth和 RFID。 车辆内部结构的复杂化和数字化&#xff0c;车载通信网络技术的重要性也越来越高。 一个典型的例子是远程信息处理。 远程信息…

ESP32 Arduino EspNow点对点双向通讯

ESP32 Arduino EspNow点对点双向通讯✨本案例分别采用esp32和esp32C3之间点对点单播无线通讯方式。 &#x1f33f;esp32开发板 &#x1f33e;esp32c3开发板 &#x1f527;所需库(需要自行导入到Arduino IDE library文件夹中&#xff0c;无法在IDE 管理库界面搜索下载到该库)&am…

git cherry-pick could not apply fb2cde669...问题解决

最近多个分支修复bug&#xff0c;在使用git cherry-pick进行小功能合并时经常会出现类似could not apply fb2cde669...的错误。具体如下图&#xff1a;具体原因是cherry-pick指定的commit内容中和当前分支有冲突导致的。具体解决分以下步骤&#xff1a;1&#xff1a;首先使用gi…

京东前端二面必会vue面试题(持续更新中)

说一下Vue的生命周期 Vue 实例有⼀个完整的⽣命周期&#xff0c;也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程&#xff0c;称这是Vue的⽣命周期。 beforeCreate&#xff08;创建前&#xff09;&#xff1a;数据观测和初…

spring(六)--------spring的扫描机制

spring的扫描一般可以通过两种方式&#xff1a; 测试类&#xff1a; Component public class Scan_A {PostConstructpublic void init(){System.out.println("-----------Scan_A");}} 1&#xff09;、ComponentSscan注解 public class ComponentScanTest {public…