灵动微MM32 芯片串口升级OTA功能开发1. 项目框架与内存划分
·
灵动微MM32 芯片串口升级OTA功能开发1. 项目框架与内存划分

一、串口升级原理与内存划分
1.1 串口升级的概念
串口升级(UART Firmware Update)是通过串口接收新固件数据,并将其写入 Flash,实现固件更新的方式。其有以下特点和作用:
- 无需专用烧录器
- 可以远程升级
- 便于现场维护
- 成本低(仅需串口连接)
1.2 Flash 分区(128KB)
典型的Flash分区布局:
Flash 内存布局(128KB):
┌─────────────────────────────────────────────────────────┐
│ 0x08000000 - 0x08003FFF (16KB) │
│ 引导程序区 (Bootloader) │
│ - 串口升级程序 │
│ - 应用程序跳转逻辑 │
│ - 不能被覆盖 │
├─────────────────────────────────────────────────────────┤
│ 0x08004000 - 0x0801FFFF (112KB) │
│ 应用程序区 (Application) │
│ - 用户应用程序 │
│ - 可以被升级覆盖 │
│ - 起始地址:0x08004000 │
└─────────────────────────────────────────────────────────┘
| 分区 | 起始地址 | 大小 | 用途 |
|---|---|---|---|
| 引导区(Bootloader) | 0x08000000 |
16KB (0x4000) | whr-ees-bootloader-arm |
| 应用区(Application) | 0x08004000 |
112KB (0x1C000) | whr-ees-bootloader-app |
通过将内存分区,可以实现:
- 引导程序区:永久驻留,负责升级和跳转
- 应用程序区:可更新的用户程序
1.3 RAM 分区(8KB)
| 分区 | 起始地址 | 大小 | 用途 |
|---|---|---|---|
| RAM | 0x20000000 |
8KB (0x2000) | 两个项目共享 |
1.4 典型的串口升级流程
二、创建一个小的应用程序
本示例使用 凌动微 MM32SPIN0280,参数信息如下:
MM32SPIN0280 Series
Subfamily: MM32SPIN0280
Frequency: 96MHz
Memory: 128 kB Flash, 8 kB RAM
Communication: up to 3 x UART, 2 x SPI, 1 x I2C,2 x I2S,
Analog : ADC, COMP,OPAMP
power saving mode:stop, sleep, and standby mode
作为原始程序和要升级的程序,只实现简单的点LED。v1版本开机闪一下,v2版本开机闪两下LED。
1. main.c
#include "hal_gpio.h"
#include "hal_rcc.h"
#include "hal_uart.h"
#include "mm32_device.h"
/**
* @brief 简单的延时函数(毫秒级)
* @param ms 延时的毫秒数
*/
static void Delay_Ms(u32 ms)
{
/* 简单的循环延时,根据实际系统时钟调整 */
volatile u32 i, j;
for (i = 0; i < ms; i++) {
for (j = 0; j < 8000; j++) { // 根据系统时钟调整此值
__ASM("nop");
}
}
}
/**
* @brief 发送单个字符
* @param ch 要发送的字符
*/
static void UART_SendChar(char ch)
{
UART_SendData(UART1, (u16)ch);
/* 等待发送完成 */
while (RESET == UART_GetFlagStatus(UART1, UART_FLAG_TXEPT)) {
}
}
/**
* @brief 发送字符串
* @param text 要发送的字符串指针
*/
static void UART_SendString(const char* text)
{
if (text == NULL) {
return;
}
/* 逐个发送字符 */
while (*text != '\0') {
UART_SendChar(*text++);
}
}
/**
* @brief 初始化串口
* @note 使用 UART1,PB8(RX), PB9(TX),波特率 115200
*/
static void UART_Init_UART1(void)
{
GPIO_InitTypeDef gpio;
UART_InitTypeDef uart;
/* 使能 GPIOB 和 UART1 时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2ENR_UART1, ENABLE);
/* 配置 PB8 和 PB9 为 UART1 复用功能 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_0);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_0);
/* 配置 PB8 (RX) 为上拉输入 */
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_8;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &gpio);
/* 配置 PB9 (TX) 为复用推挽输出 */
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_9;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &gpio);
/* 配置 UART1 参数:115200, 8N1 */
UART_StructInit(&uart);
uart.BaudRate = 115200;
uart.WordLength = UART_WordLength_8b;
uart.StopBits = UART_StopBits_1;
uart.Parity = UART_Parity_No;
uart.HWFlowControl = UART_HWFlowControl_None;
uart.Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(UART1, &uart);
/* 使能 UART1 */
UART_Cmd(UART1, ENABLE);
}
int main(void)
{
GPIO_InitTypeDef gpio;
/* 初始化串口 */
UART_Init_UART1();
/* 启动时输出版本信息 */
UART_SendString("v1\r\n");
/* 使能 GPIO 时钟(LED连接在GPIOD Pin 8) */
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOD, ENABLE);
/* 配置 GPIO 为推挽输出 */
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_8; // LED连接在PD8
gpio.GPIO_Speed = GPIO_Speed_50MHz;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOD, &gpio);
/* 打开LED(启动时闪烁一次) */
GPIO_SetBits(GPIOD, GPIO_Pin_8);
Delay_Ms(500);
GPIO_ResetBits(GPIOD, GPIO_Pin_8);
/* 主循环 - LED持续闪烁 */
while (1) {
/* 打开LED */
GPIO_SetBits(GPIOD, GPIO_Pin_8);
Delay_Ms(500);
/* 关闭LED */
GPIO_ResetBits(GPIOD, GPIO_Pin_8);
Delay_Ms(500);
}
}
2. APPDEMO.sct
sct文件主要作用是告诉链接器把哪些内容(代码、常量、变量、栈、堆……)放到芯片的哪一段内存里。
; ***********************************************************************
; *** Scatter-Loading Description File for Application ***
; ***********************************************************************
; 应用程序起始地址:0x08004000 (引导程序占用 0x08000000 ~ 0x08003FFF,共16KB)
; 应用程序可用空间:0x08004000 ~ 0x0801FFFF (约112KB)
LR_IROM1 0x08004000 0x0001C000 { ; Flash区域 (从0x08004000开始,112KB可用)
ER_IROM1 0x08004000 0x0001C000 { ; 代码和数据起始地址:0x08004000
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
*.o (CHECKSUM, +Last)
}
RW_IRAM1 0x20000000 0x00002000 { ; RAM区域 (8KB)
.ANY (+RW +ZI)
}
}
Load Region
链接器会把所有将来要烧进 Flash 的内容安排到这 112 KB 空间里。
Execution Region 1 —— 代码/常量(在 Flash 里原地执行)
在ER_IROM1 大括号里是输入段选择模板,说明如下:
| 语法 | 含义 |
|---|---|
*.o (RESET, +First) |
把所有目标文件里的 RESET 输入段放到最开头。RESET 通常就是中断向量表(__Vectors)。 |
*(InRoot$$Sections) |
让链接器把 C/C++ 运行时必须的“根模块”加进来,比如 _main.o、__scatter*.o 等,省得自己手动列。 |
.ANY (+RO) |
剩下的所有“只读”段(代码、常量、字符串字面量…)按任意顺序填进来。 |
.ANY (+XO) |
带 __attribute__((execute_only)) 的代码段,防止被当成数据读取,提高安全性。 |
*.o (CHECKSUM, +Last) |
把名为 CHECKSUM 的段放到末尾,常用于 IAP/Bootloader 做完整性校验。 |
Execution Region 2 —— RAM 区(变量/栈/堆)
RW_IRAM1 0x20000000 0x00002000 {
0x2000_0000 是 STM32 片内 SRAM 的起始地址。
0x00002000 = 8 KB,长度与注释一致。
.ANY (+RW +ZI)
+RW:已初始化的全局/静态变量(.data)。
+ZI:零初始化段(.bss、栈、堆)。
链接器会把这些变量在 Flash 里留一份初值镜像,启动时由 __scatterload 代码自动复制/清零到 RAM。
LR 决定烧录布局,ER 决定运行布局;地址相同就原地执行,地址不同链接器负责搬。
keil里与sct配置保持一致:

3. 编译bin设置
在keil里设置:
$K\ARM\ARMCLANG\bin\fromelf.exe --bin --output .\Bin\@L.bin !L

下一篇文章将先实现引导程序项目框架。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)