STM32 IAP应用开发——通过USB实现固件升级

目录

STM32 IAP应用开发——通过USB实现固件升级前言1 环境搭建2 功能描述3 BootLoader的制作4 APP的制作5 烧录下载配置6 运行测试结束语

前言

什么是IAP?

IAP(In-Application Programming) 指MCU可以在系统中获取新代码并对自己重新编程,即可用程序来改变程序。在应用编程(IAP)是用户的应用代码对片内Flash存储器进行擦除/编程的方法。这种方式的典型应用就是用一小段代码来实现程序的下载,实际上单片机的ISP功能就是通过IAP技术来实现的,即片子在出厂前就已经有一段小的boot程序在里面,片子上电后,开始运行这段程序,当检测到上位机有下载要求时,便和上位机通信,然后下载数据到数据存储区,从而实现固件升级。

什么是BootLoader?

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

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

我之前也有发过一些关于STM32远程升级的文章,但用的是第三方BootLoader,而且是基于操作系统实现的,BootLoader占用的内存也比较大,而且不开源。 那么这一期我就来介绍一下如何自己制作一个BootLoader程序,并且通过USB实现固件升级。

1 环境搭建

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

2 功能描述

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

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

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

分区表如下:

nameoffsetsizeboot0x080000000x00003000setting0x080030000x00001000app0x080040000x0000E000download0x080120000x0000E000

3 BootLoader的制作

不管用的是什么MCU,要实现固件升级都离不开BootLoader,BootLoader是一个统称,它其实只是一段引导程序,在MCU启动的时候会先运行这段代码,判断是否需要升级,如果不需要升级就跳转到APP分区运行用户代码,如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件,然后再跳转到APP分区运行新固件,从而实现固件升级。 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 IAP应用开发——自制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", YMODEM_ACK);

}

void ymodem_nack(void)

{

usb_printf("%c", YMODEM_NAK);

}

void ymodem_c(void)

{

usb_printf("%c", 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 IAP升级的介绍就讲到这里,本文列举的例子其实只是升级的其中一种方式,只是提供一个思路,不是唯一的方法,实际上最好还是根据自己实际的需求来做。 需要源码的同学可以在下面的链接下载,我把BootLoader和APP都上传了。

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

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

更多相关文章: 固件在线升级系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

文章链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: