STM32—OTA-YModem
协议优点缺点适用场景Xmodem极其简单,资源占用最小无法传输文件名和大小,检错能力弱 (Checksum)非常古老或资源极度受限的场景,现已很少用于IAPYmodem功能/资源/实现复杂度平衡,传输元数据,CRC校验速度不如Zmodem(停等协议),无内置安全机制最经典的串口IAP场景,资源受限的MCUZmodem速度快(流式传输,出错才重传),支持断点续传实现比Ymodem复杂,资源占用稍多对
目录
第1章 程序的分散装载技术

第2章 内存映射

2.1 Flash内存规划

第3章 寄存器映射
从第3章我们可以知道外设的基地址是从0x40000000开始,然后依次是APB1,APB2,AHB1,AHB2和AHB3总线。每条总线上挂载着不同的外设。
例如:要想获取GPIOB_IDR寄存器的地址,就需要使用如下方式(寄存器偏移)来实现。


第4章 程序跳转
假设,假设,假设。项目部有三名员工,一个是项目经理,另外两个是大头兵张三和李四。项目经理分配两个特别简单的功能。功能要求过年之前完成。
功能一:4盏LED灯,每隔1秒钟点亮一次,持续30s。
功能二:蜂鸣器以50%的占空比鸣叫。
马上过年了,张三将功能一完成了,并且交付给项目经理一个文件【leds.bin】,文件大小为20K;李四将功能二完成了,并且交付给项目经理一个文件叫:【buzzer.bin】文件大小为32K。
项目经理拿着两个文件犯困,叫你们完成两个功能,你们却给我两个单独封装好的文件。项目经理为啥人,这能难倒他。一是他就想到将两个文件都烧录到flash中,但是是烧录到flash的不同位置。先将【leds.bin】烧录到【0x0800 0000】,在将【buzzer.bin】烧录到【0x0802 0000】。功能一运行30s之后,就执行功能二。
好吧,又让项目经理装到了。最后两个大头兵又日复一日,年复一年的继续当着大头兵。
1. 什么是程序跳转?

2. 为啥要程序跳转?
不执行程序跳转,项目经理怎么装x,怎么将两个下属出的题迎刃而解呢。各位看官说是不是?
4.1 主flash的存放规则
在 STM32 单片机中,Flash 存储器的起始地址 0x08000000 必须存放中断向量表,这是 Cortex-M 内核规定的。这个表是程序启动和中断响应的核心。
下面我将从 0x08000000 开始,列出你提供的向量表内容在 Flash 中的地址分布和存放的具体内容:
| 地址 (Hex) | 偏移 (Bytes) | 存放的内容 (DCD) | 说明 |
|---|---|---|---|
0x08000000 |
0x00 | sfe(CSTACK) |
初始堆栈指针 (MSP) 的值。这是程序启动时堆栈的最高地址(栈顶)。 |
0x08000004 |
0x04 | Reset_Handler |
复位中断服务程序的入口地址。CPU 上电或复位后,执行完 MSP 设置,立即跳转到此地址执行。 |
0x08000008 |
0x08 | NMI_Handler |
不可屏蔽中断 (NMI) 服务程序的入口地址。 |
0x0800000C |
0x0C | HardFault_Handler |
硬件故障中断服务程序的入口地址。 |
0x08000010 |
0x10 | MemManage_Handler |
存储器管理故障中断服务程序的入口地址。 |
0x08000014 |
0x14 | BusFault_Handler |
总线故障中断服务程序的入口地址。 |
0x08000018 |
0x18 | UsageFault_Handler |
用法故障中断服务程序的入口地址。 |
0x0800001C |
0x1C | 0 |
保留 (Reserved) |
0x08000020 |
0x20 | 0 |
保留 (Reserved) |
0x08000024 |
0x24 | 0 |
保留 (Reserved) |
0x08000028 |
0x28 | 0 |
保留 (Reserved) |
0x0800002C |
0x2C | SVC_Handler |
系统服务调用 (SVCall) 中断服务程序的入口地址。 |
0x08000030 |
0x30 | DebugMon_Handler |
调试监控 (Debug Monitor) 中断服务程序的入口地址。 |
0x08000034 |
0x34 | 0 |
保留 (Reserved) |
0x08000038 |
0x38 | PendSV_Handler |
可挂起的系统调用 (PendSV) 中断服务程序的入口地址。 |
0x0800003C |
0x3C | SysTick_Handler |
SysTick 定时器中断服务程序的入口地址。 |
0x08000040 |
0x40 | WWDG_IRQHandler |
窗口看门狗 (WWDG) 中断服务程序的入口地址。 |
0x08000044 |
0x44 | PVD_IRQHandler |
可编程电压检测 (PVD) 中断服务程序的入口地址。 |
0x08000048 |
0x48 | TAMP_STAMP_IRQHandler |
撕裂和时间戳 (TAMPER/STAMP) 中断服务程序的入口地址。 |
0x0800004C |
0x4C | RTC_WKUP_IRQHandler |
RTC 唤醒中断服务程序的入口地址。 |
0x08000050 |
0x50 | FLASH_IRQHandler |
FLASH 存储器中断服务程序的入口地址。 |
0x08000054 |
0x54 | RCC_IRQHandler |
复位和时钟控制 (RCC) 中断服务程序的入口地址。 |
0x08000058 |
0x58 | EXTI0_IRQHandler |
外部中断线 0 服务程序的入口地址。 |
0x0800005C |
0x5C | EXTI1_IRQHandler |
外部中断线 1 服务程序的入口地址。 |
0x08000060 |
0x60 | EXTI2_IRQHandler |
外部中断线 2 服务程序的入口地址。 |
0x08000064 |
0x64 | EXTI3_IRQHandler |
外部中断线 3 服务程序的入口地址。 |
0x08000068 |
0x68 | EXTI4_IRQHandler |
外部中断线 4 服务程序的入口地址。 |
0x0800006C |
0x6C | DMA1_Stream0_IRQHandler |
DMA1 通道 0 传输完成等中断服务程序的入口地址。 |
0x08000070 |
0x70 | DMA1_Stream1_IRQHandler |
DMA1 通道 1 传输完成等中断服务程序的入口地址。 |
0x08000074 |
0x74 | DMA1_Stream2_IRQHandler |
DMA1 通道 2 传输完成等中断服务程序的入口地址。 |
0x08000078 |
0x78 | DMA1_Stream3_IRQHandler |
DMA1 通道 3 传输完成等中断服务程序的入口地址。 |
0x0800007C |
0x7C | DMA1_Stream4_IRQHandler |
DMA1 通道 4 传输完成等中断服务程序的入口地址。 |
0x08000080 |
0x80 | DMA1_Stream5_IRQHandler |
DMA1 通道 5 传输完成等中断服务程序的入口地址。 |
0x08000084 |
0x84 | DMA1_Stream6_IRQHandler |
DMA1 通道 6 传输完成等中断服务程序的入口地址。 |
0x08000088 |
0x88 | ADC_IRQHandler |
ADC1/2/3 转换完成等中断服务程序的入口地址。 |
0x0800008C |
0x8C | CAN1_TX_IRQHandler |
CAN1 发送中断服务程序的入口地址。 |
0x08000090 |
0x90 | CAN1_RX0_IRQHandler |
CAN1 接收 FIFO 0 中断服务程序的入口地址。 |
0x08000094 |
0x94 | CAN1_RX1_IRQHandler |
CAN1 接收 FIFO 1 中断服务程序的入口地址。 |
0x08000098 |
0x98 | CAN1_SCE_IRQHandler |
CAN1 状态变化中断服务程序的入口地址。 |
0x0800009C |
0x9C | EXTI9_5_IRQHandler |
外部中断线 9-5 服务程序的入口地址。 |
0x080000A0 |
0xA0 | TIM1_BRK_TIM9_IRQHandler |
TIM1 Break 和 TIM9 中断服务程序的入口地址。 |
0x080000A4 |
0xA4 | TIM1_UP_TIM10_IRQHandler |
TIM1 更新和 TIM10 中断服务程序的入口地址。 |
0x080000A8 |
0xA8 | TIM1_TRG_COM_TIM11_IRQHandler |
TIM1 触发和通信中断以及 TIM11 服务程序的入口地址。 |
0x080000AC |
0xAC | TIM1_CC_IRQHandler |
TIM1 捕获/比较中断服务程序的入口地址。 |
0x080000B0 |
0xB0 | TIM2_IRQHandler |
TIM2 中断服务程序的入口地址。 |
0x080000B4 |
0xB4 | TIM3_IRQHandler |
TIM3 中断服务程序的入口地址。 |
0x080000B8 |
0xB8 | TIM4_IRQHandler |
TIM4 中断服务程序的入口地址。 |
0x080000BC |
0xBC | I2C1_EV_IRQHandler |
I2C1 事件中断服务程序的入口地址。 |
0x080000C0 |
0xC0 | I2C1_ER_IRQHandler |
I2C1 错误中断服务程序的入口地址。 |
0x080000C4 |
0xC4 | I2C2_EV_IRQHandler |
I2C2 事件中断服务程序的入口地址。 |
0x080000C8 |
0xC8 | I2C2_ER_IRQHandler |
I2C2 错误中断服务程序的入口地址。 |
0x080000CC |
0xCC | SPI1_IRQHandler |
SPI1 中断服务程序的入口地址。 |
0x080000D0 |
0xD0 | SPI2_IRQHandler |
SPI2 中断服务程序的入口地址。 |
0x080000D4 |
0xD4 | USART1_IRQHandler |
USART1 中断服务程序的入口地址。 |
0x080000D8 |
0xD8 | USART2_IRQHandler |
USART2 中断服务程序的入口地址。 |
0x080000DC |
0xDC | USART3_IRQHandler |
USART3 中断服务程序的入口地址。 |
0x080000E0 |
0xE0 | EXTI15_10_IRQHandler |
外部中断线 15-10 服务程序的入口地址。 |
0x080000E4 |
0xE4 | RTC_Alarm_IRQHandler |
RTC 报警中断服务程序的入口地址。 |
0x080000E8 |
0xE8 | OTG_FS_WKUP_IRQHandler |
USB OTG FS 唤醒中断服务程序的入口地址。 |
0x080000EC |
0xEC | TIM8_BRK_TIM12_IRQHandler |
TIM8 Break 和 TIM12 中断服务程序的入口地址。 |
0x080000F0 |
0xF0 | TIM8_UP_TIM13_IRQHandler |
TIM8 更新和 TIM13 中断服务程序的入口地址。 |
0x080000F4 |
0xF4 | TIM8_TRG_COM_TIM14_IRQHandler |
TIM8 触发和通信中断以及 TIM14 服务程序的入口地址。 |
0x080000F8 |
0xF8 | TIM8_CC_IRQHandler |
TIM8 捕获/比较中断服务程序的入口地址。 |
0x080000FC |
0xFC | DMA1_Stream7_IRQHandler |
DMA1 通道 7 传输完成等中断服务程序的入口地址。 |
0x08000100 |
0x100 | FSMC_IRQHandler |
FSMC 中断服务程序的入口地址。 |
0x08000104 |
0x104 | SDIO_IRQHandler |
SDIO 中断服务程序的入口地址。 |
0x08000108 |
0x108 | TIM5_IRQHandler |
TIM5 中断服务程序的入口地址。 |
0x0800010C |
0x10C | SPI3_IRQHandler |
SPI3 中断服务程序的入口地址。 |
0x08000110 |
0x110 | UART4_IRQHandler |
UART4 中断服务程序的入口地址。 |
0x08000114 |
0x114 | UART5_IRQHandler |
UART5 中断服务程序的入口地址。 |
0x08000118 |
0x118 | TIM6_DAC_IRQHandler |
TIM6 和 DAC1/2 欠载中断服务程序的入口地址。 |
0x0800011C |
0x11C | TIM7_IRQHandler |
TIM7 中断服务程序的入口地址。 |
0x08000120 |
0x120 | DMA2_Stream0_IRQHandler |
DMA2 通道 0 传输完成等中断服务程序的入口地址。 |
0x08000124 |
0x124 | DMA2_Stream1_IRQHandler |
DMA2 通道 1 传输完成等中断服务程序的入口地址。 |
0x08000128 |
0x128 | DMA2_Stream2_IRQHandler |
DMA2 通道 2 传输完成等中断服务程序的入口地址。 |
0x0800012C |
0x12C | DMA2_Stream3_IRQHandler |
DMA2 通道 3 传输完成等中断服务程序的入口地址。 |
0x08000130 |
0x130 | DMA2_Stream4_IRQHandler |
DMA2 通道 4 传输完成等中断服务程序的入口地址。 |
0x08000134 |
0x134 | ETH_IRQHandler |
以太网 (ETH) 中断服务程序的入口地址。 |
0x08000138 |
0x138 | ETH_WKUP_IRQHandler |
以太网唤醒中断服务程序的入口地址。 |
0x0800013C |
0x13C | CAN2_TX_IRQHandler |
CAN2 发送中断服务程序的入口地址。 |
0x08000140 |
0x140 | CAN2_RX0_IRQHandler |
CAN2 接收 FIFO 0 中断服务程序的入口地址。 |
0x08000144 |
0x144 | CAN2_RX1_IRQHandler |
CAN2 接收 FIFO 1 中断服务程序的入口地址。 |
0x08000148 |
0x148 | CAN2_SCE_IRQHandler |
CAN2 状态变化中断服务程序的入口地址。 |
0x0800014C |
0x14C | OTG_FS_IRQHandler |
USB OTG FS 中断服务程序的入口地址。 |
0x08000150 |
0x150 | DMA2_Stream5_IRQHandler |
DMA2 通道 5 传输完成等中断服务程序的入口地址。 |
0x08000154 |
0x154 | DMA2_Stream6_IRQHandler |
DMA2 通道 6 传输完成等中断服务程序的入口地址。 |
0x08000158 |
0x158 | DMA2_Stream7_IRQHandler |
DMA2 通道 7 传输完成等中断服务程序的入口地址。 |
0x0800015C |
0x15C | USART6_IRQHandler |
USART6 中断服务程序的入口地址。 |
0x08000160 |
0x160 | I2C3_EV_IRQHandler |
I2C3 事件中断服务程序的入口地址。 |
0x08000164 |
0x164 | I2C3_ER_IRQHandler |
I2C3 错误中断服务程序的入口地址。 |
0x08000168 |
0x168 | OTG_HS_EP1_OUT_IRQHandler |
USB OTG HS 端点 1 OUT 中断服务程序的入口地址。 |
0x0800016C |
0x16C | OTG_HS_EP1_IN_IRQHandler |
USB OTG HS 端点 1 IN 中断服务程序的入口地址。 |
0x08000170 |
0x170 | OTG_HS_WKUP_IRQHandler |
USB OTG HS 唤醒中断服务程序的入口地址。 |
0x08000174 |
0x174 | OTG_HS_IRQHandler |
USB OTG HS 中断服务程序的入口地址。 |
0x08000178 |
0x178 | DCMI_IRQHandler |
DCMI (数字摄像头接口) 中断服务程序的入口地址。 |
0x0800017C |
0x17C | CRYP_IRQHandler |
加密 (CRYP) 模块中断服务程序的入口地址。 |
0x08000180 |
0x180 | HASH_RNG_IRQHandler |
HASH 和随机数发生器 (RNG) 中断服务程序的入口地址。 |
0x08000184 |
0x184 | FPU_IRQHandler |
浮点单元 (FPU) 中断服务程序的入口地址。 |
总结:
0x08000000到0x08000184:这部分地址(共 388 字节)严格存放了你列出的 中断向量表。0x08000188及之后:紧接着向量表,存放的就是你的程序代码(Reset_Handler、main函数以及其他所有函数的机器码)和常量数据。
4.2 SRAM的存储规则
地址 ↑
+-----------------------+ ← 0x20007458 (CSTACK 初始 SP,堆栈顶)
| Stack (CSTACK) | ↓ 堆栈向下生长(递减 SP)
| (向下) |
| |
| |
|-----------------------| ← 堆和栈的潜在碰撞区
| Heap | ↑ 堆向上生长(malloc 扩展)
| (向上) |
+-----------------------+ ← 0x20007458 (HEAP 起始地址)
| .bss | 未初始化全局/静态变量(已清零)
+-----------------------+ ← 0x20001458 (.bss 段起始)
| .data | 已初始化的全局/静态变量
+-----------------------+ ← 0x20000000 (SRAM 起始地址)
4.3 主Flash和SRAM的关系
地址 (Hex) 值 (Hex) 说明
------------------------------------------------------------
0x0800 0000 0x2000 7458 ← MSP 初始值(栈顶)
0x0800 0004 0x0800 4001 ← Reset_Handler 入口地址
主Flash起始0x0800 0000地址存储的是主堆栈指针的地址,也就是0x0800 0000地址中存放的是0x2000 7458。0x2000 7458这个地址表示的是主堆栈的起始地址。
4.4 跳转规则
// =======================
// 跳转到蜂鸣器应用程序
// =======================
void jump_to_app(void) {
typedef void (*pFunction)(void);
pFunction Jump_To_App;
uint32_t JumpAddress;
// 检查栈顶地址是否在 SRAM 范围内 (0x20000000 ~ 0x20010000)
if (((*(__IO uint32_t*)APP_START_ADDR) & 0x2FFE0000) == 0x20000000) {
JumpAddress = *(__IO uint32_t*)(APP_START_ADDR + 4);
Jump_To_App = (pFunction)JumpAddress;
__set_MSP(*(__IO uint32_t*)APP_START_ADDR); // 设置主堆栈指针
Jump_To_App(); // 跳转
}
}
函数内部的if判断
if ((*(__IO uint32_t*)APP_START_ADDR) >= 0x20000000 && (*(__IO uint32_t*)APP_START_ADDR) < 0x20020000)
取出主Flash的第一个地址中的内部值(也就是主堆栈的起始地址),看看这个地址是否在SRAM的范围内。如果在就进入到判断,如果不在,就不进入判断内部。
第5章 YModem协议
SecureCRT内部和Uboot内部都默认内嵌了Ymodem协议。
5.1 Ymodem协议简介
| 协议 | 优点 | 缺点 | 适用场景 |
| Xmodem | 极其简单,资源占用最小 | 无法传输文件名和大小,检错能力弱 (Checksum) |
非常古老或资源极度受限的场景,现已很少用于IAP |
| Ymodem | 功能/资源/实现复杂度平衡,传输元数据,CRC校验 | 速度不如Zmodem(停等协议),无内置安全机制 | 最经典的串口IAP场景,资源受限的MCU |
| Zmodem | 速度快(流式传输,出错才重传),支持断点续传 | 实现比Ymodem复杂,资源占用稍多 | 对传输速度要求较高的场景 |
5.2 Ymodem协议帧格式
5.2.1 起始帧

5.2.2 数据帧

5.2.3 结束帧

5.3 Ymodem传输数据大小选择
包头=SOH(0x01)时,帧长度是128bytes + 5bytes(包头+包号+包号取反+CRC16)。
同理,包头=STX(0x02)时,帧长度是1024bytes + 5bytes。
Ymodem协议的设计,有点像一个物流系统,可以选用不同尺寸的箱子来寄送货物。
SOH (0x01): 代表一个“小箱子”,能装 128 字节的货物。
STX (0x02): 代表一个“大箱子”,能装 1024 字节的货物。
在一次完整的“寄件”(文件传输)过程中,发送方和接收方可以协商决定主要使用哪种箱子。
灵活策略(混合使用大小箱子):
发送方可以根据每批“货物”(数据块)的多少,来动态选择用大箱子还是小箱子。比如,大部分时候用1024字节的STX大箱子,但最后一批货物只有80字节,就可以换成128字节的SOH小箱子来装,这样更节省空间(传输带宽)。这是完全符合YMODEM协议规范的。
简单策略(只用一种大箱子)- (我的代码目前采用的策略):
类比: 为了简化流程,物流公司决定,无论货物多少,一律使用1024字节的STX大箱子。如果货物装不满,就用填充物(比如泡沫或旧报纸)把箱子塞满。
5.4 Ymodem协议传输过程

5.5 工程实际(从机)
/* main.c - YMODEM Bootloader for STM32F1 (Standard Peripheral Library) */
#include "stm32f10x.h"
#include <stdint.h>
#include <string.h>
// =======================
// 配置区(根据硬件修改)
// =======================
// 应用程序起始地址(跳过 32KB Bootloader)
#define APP_START_ADDR 0x08008000
// 擦除扇区数(每扇区 1KB,共 64KB)
#define APP_ERASE_SECTORS 64
// 串口波特率
#define USART_BAUDRATE 115200
// 升级触发按键(PA0)
#define KEY_PORT GPIOA
#define KEY_PIN GPIO_Pin_0
#define KEY_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)
// LED 指示灯(可选,PD2)
#define LED_PORT GPIOD
#define LED_PIN GPIO_Pin_2
#define LED_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE)
// YMODEM 协议控制字符
#define YMODEM_SOH 0x01 // 128-byte data
#define YMODEM_STX 0x02 // 1024-byte data
#define YMODEM_EOT 0x04 // End of Transmission
#define YMODEM_ACK 0x06 // Acknowledge
#define YMODEM_NAK 0x15 // Negative Acknowledge
#define YMODEM_C 0x43 // 'C' - Request CRC mode
#define YMODEM_END 0x4F // 'O' - Custom end signal
// 状态定义
#define UPDATE_IDLE 0
#define UPDATE_BUSY 1
#define UPDATE_SUCCESS 2
// 接收缓冲区(最大支持 1024 字节数据包)
typedef struct {
uint8_t data[1024 + 3 + 2];
uint16_t len;
} download_buf_t;
// YMODEM 状态结构
typedef struct {
uint32_t addr; // 当前写入地址
uint8_t status; // 状态机:0=等待头, 1=接收数据, 2=EOT, 3=结束
uint8_t process; // 运行状态
void (*cb)(void); // 回调函数
} ymodem_t;
// 全局变量
ymodem_t ymodem = {0};
download_buf_t g_rx_buf;
// 函数声明
void SystemInit(void);
void Delay(__IO uint32_t nCount);
void GPIO_Config(void);
void USART1_Config(void);
void Flash_Erase(uint32_t addr, uint32_t num_sectors);
void Flash_Write(uint32_t addr, uint8_t *data, uint32_t len);
void USART_Send_Byte(uint8_t ch);
void jump_to_app(void);
// YMODEM 函数
void ymodem_ack(void);
void ymodem_nack(void);
void ymodem_c(void);
void ymodem_end(void);
void ymodem_start(void (*cb)(void));
void ymodem_recv(download_buf_t *p);
// =======================
// 主函数
// =======================
int main(void) {
// 初始化系统(标准库已调用 SystemInit())
// RCC, Flash 等已在启动文件中配置
// 时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO 初始化
GPIO_Config();
// 串口初始化
USART1_Config();
// 延时函数测试
Delay(0x5FFFF);
// 判断是否进入升级模式:按键是否按下(PA0 接地)
if (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN) == Bit_RESET) {
// 点亮 LED 表示进入升级模式
GPIO_WriteBit(LED_PORT, LED_PIN, Bit_SET);
// 发送 'C' 请求 YMODEM 传输
ymodem_start(jump_to_app);
ymodem_c();
// 循环等待升级完成
while (ymodem.process != UPDATE_SUCCESS) {
Delay(0x1FFFF); // 简单延时
}
// 升级成功,跳转
if (ymodem.cb) {
ymodem.cb();
}
} else {
// 正常启动应用程序
jump_to_app();
}
while (1);
}
// =======================
// YMODEM 协议实现
// =======================
void ymodem_ack(void) {
USART_Send_Byte(YMODEM_ACK);
}
void ymodem_nack(void) {
USART_Send_Byte(YMODEM_NAK);
}
void ymodem_c(void) {
USART_Send_Byte(YMODEM_C);
}
void ymodem_end(void) {
USART_Send_Byte(YMODEM_END);
}
void ymodem_start(void (*cb)(void)) {
if (ymodem.status == 0) {
ymodem.cb = cb;
ymodem.process = UPDATE_IDLE;
}
}
void ymodem_recv(download_buf_t *p) {
uint8_t type = p->data[0];
switch (ymodem.status) {
case 0:
if (type == YMODEM_SOH) {
ymodem.process = UPDATE_BUSY;
ymodem.addr = APP_START_ADDR;
Flash_Erase(ymodem.addr, APP_ERASE_SECTORS);
ymodem_ack();
ymodem_c();
ymodem.status = 1;
}
break;
case 1:
if (type == YMODEM_SOH || type == YMODEM_STX) {
uint8_t *data = &p->data[3];
uint32_t len = (type == YMODEM_SOH) ? 128 : 1024;
Flash_Write(ymodem.addr, data, len);
ymodem.addr += len;
ymodem_ack();
} else if (type == YMODEM_EOT) {
ymodem_nack();
ymodem.status = 2;
} else {
ymodem.status = 0;
}
break;
case 2:
if (type == YMODEM_EOT) {
ymodem_ack();
ymodem_c();
ymodem.status = 3;
} else {
ymodem.status = 0;
}
break;
case 3:
if (type == YMODEM_SOH) {
ymodem_ack();
ymodem_end();
ymodem.status = 0;
ymodem.process = UPDATE_SUCCESS;
}
break;
}
p->len = 0;
}
// =======================
// 串口发送与接收(中断方式)
// =======================
void USART_Send_Byte(uint8_t ch) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, ch);
}
// 串口中断服务函数(在 stm32f10x_it.c 中定义)
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t ch = USART_ReceiveData(USART1);
if (ch == YMODEM_SOH || ch == YMODEM_STX || ch == YMODEM_EOT) {
g_rx_buf.len = 0;
g_rx_buf.data[g_rx_buf.len++] = ch;
} else if (g_rx_buf.len < sizeof(g_rx_buf.data)) {
g_rx_buf.data[g_rx_buf.len++] = ch;
// 判断是否收到完整帧
if (g_rx_buf.len >= 3) {
uint32_t expected = 0;
if (g_rx_buf.data[0] == YMODEM_SOH) expected = 128 + 3 + 2;
else if (g_rx_buf.data[0] == YMODEM_STX) expected = 1024 + 3 + 2;
else if (g_rx_buf.data[0] == YMODEM_EOT) expected = 1;
if (g_rx_buf.len >= expected) {
ymodem_recv(&g_rx_buf);
}
}
}
}
}
// =======================
// Flash 操作(标准库)
// =======================
void Flash_Erase(uint32_t addr, uint32_t num_sectors) {
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
for (uint32_t i = 0; i < num_sectors; i++) {
FLASH_ErasePage(addr + i * 1024); // STM32F1 每页 1KB
}
FLASH_Lock();
}
void Flash_Write(uint32_t addr, uint8_t *data, uint32_t len) {
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
for (uint32_t i = 0; i < len; i += 4) {
uint32_t word = ((uint32_t)data[i]) |
((uint32_t)data[i+1] << 8) |
((uint32_t)data[i+2] << 16) |
((uint32_t)data[i+3] << 24);
FLASH_ProgramWord(addr + i, word);
}
FLASH_Lock();
}
// =======================
// 硬件初始化
// =======================
void GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 启动时钟
KEY_CLK_ENABLE();
LED_CLK_ENABLE();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
// 配置 PA0 为输入(按键)
GPIO_InitStructure.GPIO_Pin = KEY_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY_PORT, &GPIO_InitStructure);
// 配置 PD2 为输出(LED)
GPIO_InitStructure.GPIO_Pin = LED_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LED_PORT, &GPIO_InitStructure);
// 配置 USART1 TX (PA9) 为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 USART1 RX (PA10) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void USART1_Config(void) {
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 配置串口
USART_InitStructure.USART_BaudRate = USART_BAUDRATE;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 配置 NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能串口
USART_Cmd(USART1, ENABLE);
}
// =======================
// 跳转到应用程序
// =======================
void jump_to_app(void) {
typedef void (*pFunction)(void);
pFunction Jump_To_App;
uint32_t JumpAddress;
// 检查栈顶地址是否在 SRAM 范围内 (0x20000000 ~ 0x20010000)
if (((*(__IO uint32_t*)APP_START_ADDR) & 0x2FFE0000) == 0x20000000) {
JumpAddress = *(__IO uint32_t*)(APP_START_ADDR + 4);
Jump_To_App = (pFunction)JumpAddress;
__set_MSP(*(__IO uint32_t*)APP_START_ADDR); // 设置主堆栈指针
Jump_To_App(); // 跳转
}
}
// =======================
// 简单延时函数
// =======================
void Delay(__IO uint32_t nCount) {
for (; nCount != 0; nCount--);
}
更多推荐



所有评论(0)