目录

第1章 程序的分散装载技术

第2章 内存映射

2.1 Flash内存规划

第3章 寄存器映射

第4章 程序跳转

4.1 主flash的存放规则

4.2 SRAM的存储规则

4.3 主Flash和SRAM的关系

4.4 跳转规则

第5章 YModem协议

5.1 Ymodem协议简介

5.2 Ymodem协议帧格式

5.2.1 起始帧

5.2.2 数据帧

5.2.3 结束帧

5.3 Ymodem传输数据大小选择

5.4 Ymodem协议传输过程

5.5 工程实际(从机)


第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_Handlermain 函数以及其他所有函数的机器码)和常量数据

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--);
}


参考链接1

参考链接2

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐