Generic STM32 serial bootloader, with a simple command-line interface
某次@xjtuecho提到他做的一个bootloader, 支持N多型号, 只要改下配置文件就能适应不同型号;此外还提供了简单的命令解释器, 可以在串口命令行实现查看/擦除任意位置flash, 模拟eeprom读写(与app共享)等功能. 是不是挺实用? 于是打算自己也实现一个.
arm-none-eabi-gcc 4.9.3. Keil或IAR用户可能需要手动修改链接脚本和Makefile之类.
- STM32F072CB, 实测正常使用.
- STM32F303CC, 实测正常使用.
- GD32F350CB, 实测正常使用.
- STM32F103RC, 实测正常使用.
- STM32F401RC, 初步实测正常使用.
- STM32F030C8, 可正常编译, 未测试.
- STM32F042F6, 可正常编译, 未测试.
- GD32F130F8, 可正常编译, 未测试.
- 在FLASH每页1K的MCU上共占用8K FLASH, 其中EEPROM占1页, 提供256字节EEPROM空间.
- (在STM32F042上占用9K FLASH.)
- 在FLASH每页2K的MCU上共占用10K FLASH, 提供512字节EEPROM空间.
- 在STM32F401上共占用32K FLASH, 提供4096字节EEPROM空间. (尚未充分测试!)
为了时钟配置简单起见, bootloader在8M HSI时钟运行. 串口波特率定为500kbps.
选择对应型号的Makefile, 并在usart.c里修改用到的usart外设和管脚即可.
之后可以执行make.bat, 选择mcu型号, 即可编译出所需要的bootloader.
上位机见根目录的iap.py, python 3.6.4和python 3.7.1实测正常工作.
需要zlib和bincopy两个第三方库. (前者用于计算CRC32校验, 后者用于把hex格式转为binary.)
app这边需要做的:
- 写入bootloader后先在串口命令行执行## sysinfo, 取得app区的入口地址, 应该是0x08002000或0x08002800. STM32F401则是0x08008000.
- 修改链接文件(.ld或.lds, 在不同开发环境下可能不同), 把FLASH区的起始地址改为上面的入口地址, LENGTH要根据页大小减去8K或10K. STM32F401要减去32K.
- Makefile或者其他类似指定了FLASH大小的场合, 要减去8K或10K. STM32F401要减去32K.
- main.c在最前面加上两行, 其中VECT_TAB_OFFSET的值是0x2000或0x2800. STM32F401为0x8000.
NVIC_SetVectorTable(NVIC_VectTab_FLASH, VECT_TAB_OFFSET);
__enable_irq();
- 如果是stm32f0系列呢? 它们不提供重定向中断向量表的功能, 所以NVIC_SetVectorTable就没用了. 不过bootloader还是能用的, 稍微麻烦一点, 需要在main.c最前面加上这几行, 把中断向量表复制到SRAM的起始位置, 然后把复位地址改为指向SRAM起始位置.
memcpy((void*)(0x20000000), (void*)APP_BASE, 256);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
__enable_irq();
APP_BASE的值是前面提到过的0x08002000/0x08002800. __enable_irq()则还是需要的. 此外, .ld/.lds文件里SRAM起始位置需要改为0x20000100, LENGTH要减去256.
-
上电启动同时执行py iap.py xxx.hex即可. (启动延迟约1s后自动跳转到app.)
-
如果需要一键iap, 应该怎么操作呢? 需要在app里响应命令"## REBOOT"后, 先向串口输出任意字符, 然后执行NVIC_SystemReset()即可. 如果是stm32f0xx, 则还要先执行SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_Flash), 把系统起始位置改回FLASH.
-
可以在app的Makefile里增加一条目标iap, 这样更新程序时只要执行make iap即可, 省太多事了.
PYTHON := "py.exe"
IAP := $(PYTHON) "iap.py
...
iap:
$(IAP) xxx.hex
- 如果要在app里使用eeprom呢? 只要在app里加上flash_eeprom.h和flash_eeprom.c两个文件, 并在使用前先初始化即可:
FLASH_EEPROM_Config(APP_BASE - PAGE_SIZE, PAGE_SIZE);
所有命令前面需要加上##.
- help: 显示帮助.
- empty: 检查app区是否为空.
- erase_all: 擦除app区全部内容.
- eeprom:
- eeprom read [addr] [size]: 读取EEPROM内容.
- eeprom write addr data: 写入EEPROM内容, data为unsigned short型(2字节).
- eeprom readall: 读取EEPROM全部内容.
- eeprom eraseall: 擦除EEPROM全部内容.
- reboot: 复位.
- read [addr] [size]: 读取指定地址, 包括FLASH/SRAM/外设等均可. 如果读到非法地址会复位.
- sysinfo: 显示系统信息, 包括FLASH大小, SRAM大小, FLASH单页大小, Bootloader和APP占用空间, EEPROM和APP的起始地址.
- 硬件CRC32校验: STM32大部分都有硬件CRC单元(我不确定是否全系都有), 但是操作方式不完全相同. 因此使用硬件CRC32校验的话还需要针对不同型号仔细适配. 此外, 如果需要校验的内容较多, 还需要用DMA来加速.
- flash_eeprom优化: 目前用的是伪地址方案, 每次写入时按"地址:数据"成对写入, 读取时从后往前查找, 找到第一个匹配的地址即返回. 写入时则是从前往后查找, 找到第一个空地址后写入并返回. 将来这里需要改成二分查找, 能稍微快一点. 以及, ST官方文档的做法, 写满时是用另一页FLASH作为后备页来轮转, 这样使用SRAM空间较少, 并且不怕掉电. 我这里简单起见, 直接在SRAM中轮转, 如果轮转时正好掉电有可能丢失EEPROM全部数据. 在bootloader里问题不大, app里如果追求可靠性的话应该采用ST官方方案, 或者用大电容+掉电中断来预防.
xjtuecho (@xjtuecho) elm-chan (http://elm-chan.org)