STM32实战:基于STM32F103的Bootloader设计与IAP在线升级

文章目录

    • 一、前言
      • [1.1 技术背景](#1.1 技术背景)
      • [1.2 本文目标](#1.2 本文目标)
      • [1.3 技术栈](#1.3 技术栈)
    • 二、环境准备
      • [2.1 硬件准备](#2.1 硬件准备)
      • [2.2 Flash分区规划](#2.2 Flash分区规划)
    • 三、Bootloader设计
      • [3.1 启动流程](#3.1 启动流程)
      • [3.2 工程配置](#3.2 工程配置)
    • 四、核心实现
      • [4.1 Flash操作驱动](#4.1 Flash操作驱动)
      • [4.2 YMODEM协议实现](#4.2 YMODEM协议实现)
      • [4.3 应用程序跳转](#4.3 应用程序跳转)
      • [4.4 Bootloader主程序](#4.4 Bootloader主程序)
      • [4.5 应用程序配置](#4.5 应用程序配置)
    • 五、编译与下载
      • [5.1 编译Bootloader](#5.1 编译Bootloader)
      • [5.2 编译应用程序](#5.2 编译应用程序)
      • [5.3 使用SecureCRT升级](#5.3 使用SecureCRT升级)
    • 六、故障排查与问题解决
      • [6.1 跳转失败](#6.1 跳转失败)
      • [6.2 Flash写入失败](#6.2 Flash写入失败)
    • 七、总结
      • [7.1 核心知识点回顾](#7.1 核心知识点回顾)
      • [7.2 扩展学习方向](#7.2 扩展学习方向)
      • [7.3 学习资源](#7.3 学习资源)

一、前言

1.1 技术背景

在嵌入式产品开发中,固件更新是一个重要的维护手段。传统的烧录方式需要连接调试器,对于已经部署到现场的设备来说非常不便。IAP(In-Application Programming)技术允许设备在运行过程中接收新固件并更新自身,实现远程升级功能。

Bootloader是位于Flash起始位置的引导程序,负责初始化硬件、检查升级条件、加载应用程序或执行固件更新。

1.2 本文目标

通过本教程,你将学会:

  • Bootloader的设计原理
  • STM32启动流程和向量表重映射
  • Flash读写操作
  • YMODEM协议实现
  • 固件升级流程设计

1.3 技术栈

硬件平台:

  • STM32F103C8T6(主控芯片)
  • ST-Link V2(调试器)
  • USB转TTL模块(串口通信)

软件环境:

  • STM32CubeIDE v1.10.0+
  • HAL库
  • YMODEM协议

二、环境准备

2.1 硬件准备

设备 数量 说明
STM32F103C8T6最小系统板 1块 主控芯片
ST-Link V2 1个 程序下载
USB转TTL模块 1个 串口升级

2.2 Flash分区规划

STM32F103C8T6有64KB Flash,分区如下:

复制代码
Flash地址空间(0x08000000 - 0x08010000)

0x08000000 - 0x08004000  Bootloader (16KB)
0x08004000 - 0x0800F000  Application (44KB)
0x0800F000 - 0x08010000  Configuration (4KB)

三、Bootloader设计

3.1 启动流程

按键按下
无升级请求
有效
无效
成功
失败
复位
硬件初始化
检查升级条件
进入升级模式
检查应用程序
跳转到应用程序
YMODEM接收
写入Flash
校验
错误处理
应用程序运行

3.2 工程配置

创建Bootloader工程:

  1. 工程名称:Bootloader
  2. 芯片型号:STM32F103C8T6
  3. 配置串口USART1用于升级通信

Flash配置:

复制代码
ROM起始地址:0x08000000
ROM大小:0x4000 (16KB)

四、核心实现

4.1 Flash操作驱动

📄 创建文件:Core/Src/flash_driver.c

c 复制代码
/*
 * flash_driver.c - Flash操作驱动
 * 
 * 功能:
 * - Flash解锁/锁定
 * - Flash页擦除
 * - Flash编程
 * - Flash读取
 */

#include "flash_driver.h"
#include "stm32f1xx_hal.h"

/* Flash页大小 */
#define FLASH_PAGE_SIZE     1024

/* Flash起始地址 */
#define FLASH_BASE_ADDR     0x08000000

/* 应用程序起始地址 */
#define APP_START_ADDR      0x08004000

/* Flash解锁 */
void flash_unlock(void)
{
    HAL_FLASH_Unlock();
}

/* Flash锁定 */
void flash_lock(void)
{
    HAL_FLASH_Lock();
}

/* 擦除Flash页 */
uint8_t flash_erase_page(uint32_t page_addr)
{
    FLASH_EraseInitTypeDef erase_init;
    uint32_t page_error;
    HAL_StatusTypeDef status;
    
    /* 计算页号 */
    uint32_t page = (page_addr - FLASH_BASE_ADDR) / FLASH_PAGE_SIZE;
    
    erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
    erase_init.PageAddress = page_addr;
    erase_init.NbPages = 1;
    
    status = HAL_FLASHEx_Erase(&erase_init, &page_error);
    
    return (status == HAL_OK) ? 0 : 1;
}

/* 擦除应用程序区域 */
uint8_t flash_erase_app(void)
{
    uint32_t addr;
    
    for (addr = APP_START_ADDR; addr < 0x08010000; addr += FLASH_PAGE_SIZE) {
        if (flash_erase_page(addr) != 0) {
            return 1;
        }
    }
    
    return 0;
}

/* 写入Flash(半字) */
uint8_t flash_write_halfword(uint32_t addr, uint16_t data)
{
    HAL_StatusTypeDef status;
    
    status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, data);
    
    return (status == HAL_OK) ? 0 : 1;
}

/* 写入Flash(字) */
uint8_t flash_write_word(uint32_t addr, uint32_t data)
{
    HAL_StatusTypeDef status;
    
    status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, data);
    
    return (status == HAL_OK) ? 0 : 1;
}

/* 写入Flash缓冲区 */
uint8_t flash_write_buffer(uint32_t addr, uint8_t *buf, uint32_t len)
{
    uint32_t i;
    uint16_t halfword;
    
    for (i = 0; i < len; i += 2) {
        halfword = buf[i] | (buf[i + 1] << 8);
        if (flash_write_halfword(addr + i, halfword) != 0) {
            return 1;
        }
    }
    
    return 0;
}

/* 读取Flash */
void flash_read(uint32_t addr, uint8_t *buf, uint32_t len)
{
    uint32_t i;
    
    for (i = 0; i < len; i++) {
        buf[i] = *(__IO uint8_t *)(addr + i);
    }
}

/* 获取应用程序大小 */
uint32_t flash_get_app_size(void)
{
    /* 从配置区读取 */
    return *(__IO uint32_t *)0x0800F000;
}

/* 设置应用程序大小 */
void flash_set_app_size(uint32_t size)
{
    flash_unlock();
    flash_write_word(0x0800F000, size);
    flash_lock();
}

/* 校验应用程序 */
uint8_t flash_verify_app(uint32_t crc)
{
    /* 从配置区读取CRC */
    uint32_t stored_crc = *(__IO uint32_t *)0x0800F004;
    return (stored_crc == crc) ? 0 : 1;
}

📄 创建文件:Core/Inc/flash_driver.h

c 复制代码
/*
 * flash_driver.h - Flash驱动头文件
 */

#ifndef __FLASH_DRIVER_H__
#define __FLASH_DRIVER_H__

#include <stdint.h>

/* Flash解锁 */
void flash_unlock(void);

/* Flash锁定 */
void flash_lock(void);

/* 擦除Flash页 */
uint8_t flash_erase_page(uint32_t page_addr);

/* 擦除应用程序区域 */
uint8_t flash_erase_app(void);

/* 写入Flash半字 */
uint8_t flash_write_halfword(uint32_t addr, uint16_t data);

/* 写入Flash字 */
uint8_t flash_write_word(uint32_t addr, uint32_t data);

/* 写入Flash缓冲区 */
uint8_t flash_write_buffer(uint32_t addr, uint8_t *buf, uint32_t len);

/* 读取Flash */
void flash_read(uint32_t addr, uint8_t *buf, uint32_t len);

/* 获取应用程序大小 */
uint32_t flash_get_app_size(void);

/* 设置应用程序大小 */
void flash_set_app_size(uint32_t size);

/* 校验应用程序 */
uint8_t flash_verify_app(uint32_t crc);

#endif /* __FLASH_DRIVER_H__ */

4.2 YMODEM协议实现

📄 创建文件:Core/Src/ymodem.c

c 复制代码
/*
 * ymodem.c - YMODEM协议实现
 * 
 * 功能:
 * - 文件接收
 * - CRC校验
 * - 超时重传
 */

#include "ymodem.h"
#include "flash_driver.h"
#include <string.h>
#include <stdio.h>

/* YMODEM控制字符 */
#define SOH     0x01    /* 128字节数据包 */
#define STX     0x02    /* 1024字节数据包 */
#define EOT     0x04    /* 传输结束 */
#define ACK     0x06    /* 确认 */
#define NAK     0x15    /* 否认 */
#define CAN     0x18    /* 取消 */
#define CRC16   0x43    /* 'C' - CRC校验请求 */

/* 包大小 */
#define PACKET_SIZE_128     128
#define PACKET_SIZE_1024    1024
#define PACKET_HEADER_SIZE  3
#define PACKET_TRAILER_SIZE 2
#define PACKET_OVERHEAD     (PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE)

/* 超时时间 */
#define TIMEOUT_MS          1000
#define MAX_ERRORS          10

/* 串口句柄 */
extern UART_HandleTypeDef huart1;

/* 缓冲区 */
static uint8_t packet_data[PACKET_SIZE_1024 + PACKET_OVERHEAD];
static uint8_t file_name[64];
static uint32_t file_size;

/* 函数声明 */
static uint16_t crc16(const uint8_t *data, uint16_t len);
static uint8_t receive_byte(uint32_t timeout);
static uint8_t receive_packet(uint8_t *data, uint16_t *len, uint32_t timeout);
static void send_byte(uint8_t byte);

/* CRC16计算 */
static uint16_t crc16(const uint8_t *data, uint16_t len)
{
    uint16_t crc = 0;
    uint16_t i, j;
    
    for (i = 0; i < len; i++) {
        crc ^= (uint16_t)data[i] << 8;
        for (j = 0; j < 8; j++) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    
    return crc;
}

/* 接收一个字节 */
static uint8_t receive_byte(uint32_t timeout)
{
    uint8_t byte;
    HAL_UART_Receive(&huart1, &byte, 1, timeout);
    return byte;
}

/* 发送一个字节 */
static void send_byte(uint8_t byte)
{
    HAL_UART_Transmit(&huart1, &byte, 1, HAL_MAX_DELAY);
}

/* 接收数据包 */
static uint8_t receive_packet(uint8_t *data, uint16_t *len, uint32_t timeout)
{
    uint8_t ch;
    uint16_t packet_size;
    uint16_t crc, rx_crc;
    uint8_t packet_num, packet_num_comp;
    
    /* 等待包头 */
    do {
        ch = receive_byte(timeout);
        if (ch == SOH) {
            packet_size = PACKET_SIZE_128;
            break;
        } else if (ch == STX) {
            packet_size = PACKET_SIZE_1024;
            break;
        } else if (ch == EOT) {
            return 2;  /* 传输结束 */
        } else if (ch == CAN) {
            return 3;  /* 取消传输 */
        }
    } while (1);
    
    /* 接收包序号 */
    packet_num = receive_byte(timeout);
    packet_num_comp = receive_byte(timeout);
    
    if (packet_num != (uint8_t)(~packet_num_comp)) {
        return 1;  /* 包序号错误 */
    }
    
    /* 接收数据 */
    for (uint16_t i = 0; i < packet_size; i++) {
        data[i] = receive_byte(timeout);
    }
    
    /* 接收CRC */
    rx_crc = receive_byte(timeout) << 8;
    rx_crc |= receive_byte(timeout);
    
    /* 计算CRC */
    crc = crc16(data, packet_size);
    
    if (crc != rx_crc) {
        return 1;  /* CRC错误 */
    }
    
    *len = packet_size;
    return 0;  /* 成功 */
}

/* YMODEM接收 */
uint8_t ymodem_receive(void)
{
    uint8_t packet_data[PACKET_SIZE_1024];
    uint16_t packet_len;
    uint8_t packet_num = 0;
    uint8_t errors = 0;
    uint8_t result;
    uint32_t flash_addr = 0x08004000;
    uint32_t total_received = 0;
    
    file_name[0] = '\\0';
    file_size = 0;
    
    printf("\\r\\nWaiting for file...\\r\\n");
    
    /* 发送'C'请求CRC校验 */
    for (int i = 0; i < 3; i++) {
        send_byte(CRC16);
        HAL_Delay(1000);
    }
    
    /* 接收第一个包(文件名包) */
    while (1) {
        result = receive_packet(packet_data, &packet_len, TIMEOUT_MS);
        
        if (result == 0) {
            /* 解析文件名 */
            if (packet_num == 0) {
                /* 提取文件名 */
                uint8_t *p = packet_data;
                uint8_t i = 0;
                while (*p && i < sizeof(file_name) - 1) {
                    file_name[i++] = *p++;
                }
                file_name[i] = '\\0';
                
                /* 提取文件大小 */
                while (*p) p++;
                p++;
                file_size = 0;
                while (*p >= '0' && *p <= '9') {
                    file_size = file_size * 10 + (*p++ - '0');
                }
                
                printf("File: %s, Size: %lu bytes\\r\\n", file_name, file_size);
                
                /* 擦除应用程序区域 */
                printf("Erasing Flash...\\r\\n");
                flash_unlock();
                if (flash_erase_app() != 0) {
                    printf("Erase failed!\\r\\n");
                    flash_lock();
                    return 1;
                }
                printf("Erase complete\\r\\n");
                
                send_byte(ACK);
                send_byte(CRC16);
                packet_num = 1;
            } else {
                /* 数据包 */
                if (flash_write_buffer(flash_addr, packet_data, packet_len) != 0) {
                    printf("Flash write error!\\r\\n");
                    flash_lock();
                    return 1;
                }
                
                flash_addr += packet_len;
                total_received += packet_len;
                
                /* 显示进度 */
                if (file_size > 0) {
                    uint8_t percent = (total_received * 100) / file_size;
                    printf("Progress: %3d%%\\r", percent);
                }
                
                send_byte(ACK);
                packet_num++;
            }
            
            errors = 0;
        } else if (result == 2) {
            /* 传输结束 */
            send_byte(ACK);
            printf("\\r\\nTransfer complete, received %lu bytes\\r\\n", total_received);
            flash_lock();
            return 0;
        } else if (result == 3) {
            /* 取消传输 */
            printf("\\r\\nTransfer cancelled\\r\\n");
            flash_lock();
            return 1;
        } else {
            /* 错误 */
            errors++;
            if (errors > MAX_ERRORS) {
                printf("\\r\\nToo many errors, aborting\\r\\n");
                send_byte(CAN);
                send_byte(CAN);
                flash_lock();
                return 1;
            }
            send_byte(NAK);
        }
    }
}

/* 获取文件名 */
char* ymodem_get_filename(void)
{
    return (char *)file_name;
}

/* 获取文件大小 */
uint32_t ymodem_get_filesize(void)
{
    return file_size;
}

📄 创建文件:Core/Inc/ymodem.h

c 复制代码
/*
 * ymodem.h - YMODEM头文件
 */

#ifndef __YMODEM_H__
#define __YMODEM_H__

#include <stdint.h>

/* YMODEM接收 */
uint8_t ymodem_receive(void);

/* 获取文件名 */
char* ymodem_get_filename(void);

/* 获取文件大小 */
uint32_t ymodem_get_filesize(void);

#endif /* __YMODEM_H__ */

4.3 应用程序跳转

📄 创建文件:Core/Src/app_jump.c

c 复制代码
/*
 * app_jump.c - 应用程序跳转
 */

#include "app_jump.h"
#include "stm32f1xx_hal.h"

/* 应用程序起始地址 */
#define APP_START_ADDR      0x08004000

/* 检查应用程序有效性 */
uint8_t check_app_valid(void)
{
    /* 检查栈顶地址是否合法 */
    uint32_t stack_top = *(__IO uint32_t *)APP_START_ADDR;
    
    /* 栈顶应在SRAM范围内(0x20000000 - 0x20005000) */
    if ((stack_top & 0x2FFF0000) != 0x20000000) {
        return 0;
    }
    
    /* 检查复位向量 */
    uint32_t reset_vector = *(__IO uint32_t *)(APP_START_ADDR + 4);
    if ((reset_vector & 0xFF000000) != 0x08000000) {
        return 0;
    }
    
    return 1;
}

/* 跳转到应用程序 */
void jump_to_app(void)
{
    uint32_t app_stack;
    uint32_t app_reset_vector;
    void (*app_reset_handler)(void);
    
    printf("Jumping to application...\\r\\n");
    
    /* 获取应用程序栈顶地址 */
    app_stack = *(__IO uint32_t *)APP_START_ADDR;
    
    /* 获取应用程序复位向量 */
    app_reset_vector = *(__IO uint32_t *)(APP_START_ADDR + 4);
    
    /* 创建函数指针 */
    app_reset_handler = (void (*)(void))app_reset_vector;
    
    /* 关闭中断 */
    __disable_irq();
    
    /* 关闭外设 */
    HAL_DeInit();
    
    /* 设置主堆栈指针 */
    __set_MSP(app_stack);
    
    /* 设置向量表偏移 */
    SCB->VTOR = APP_START_ADDR;
    
    /* 跳转到应用程序 */
    app_reset_handler();
}

📄 创建文件:Core/Inc/app_jump.h

c 复制代码
/*
 * app_jump.h - 应用程序跳转头文件
 */

#ifndef __APP_JUMP_H__
#define __APP_JUMP_H__

#include <stdint.h>

/* 检查应用程序有效性 */
uint8_t check_app_valid(void);

/* 跳转到应用程序 */
void jump_to_app(void);

#endif /* __APP_JUMP_H__ */

4.4 Bootloader主程序

📄 创建文件:Core/Src/bootloader_main.c

c 复制代码
/*
 * bootloader_main.c - Bootloader主程序
 */

#include "main.h"
#include "flash_driver.h"
#include "ymodem.h"
#include "app_jump.h"
#include <stdio.h>
#include <string.h>

/* 版本信息 */
#define BOOTLOADER_VERSION  "1.0.0"

/* 升级按键引脚 */
#define UPDATE_KEY_PORT     GPIOA
#define UPDATE_KEY_PIN      GPIO_PIN_0

/* 串口句柄 */
UART_HandleTypeDef huart1;

/* 重定向printf */
int _write(int file, char *ptr, int len)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
    return len;
}

/* 检查升级按键 */
static uint8_t check_update_key(void)
{
    /* 读取按键状态 */
    if (HAL_GPIO_ReadPin(UPDATE_KEY_PORT, UPDATE_KEY_PIN) == GPIO_PIN_RESET) {
        HAL_Delay(50);  /* 消抖 */
        if (HAL_GPIO_ReadPin(UPDATE_KEY_PORT, UPDATE_KEY_PIN) == GPIO_PIN_RESET) {
            return 1;
        }
    }
    return 0;
}

/* 显示菜单 */
static void show_menu(void)
{
    printf("\\r\\n");
    printf("========================================\\r\\n");
    printf("  STM32 Bootloader v%s\\r\\n", BOOTLOADER_VERSION);
    printf("========================================\\r\\n");
    printf("  1. Update firmware (YMODEM)\\r\\n");
    printf("  2. Jump to application\\r\\n");
    printf("  3. Erase application\\r\\n");
    printf("  4. System info\\r\\n");
    printf("  5. Reboot\\r\\n");
    printf("========================================\\r\\n");
    printf("Select option: ");
}

/* 系统信息 */
static void show_info(void)
{
    printf("\\r\\nSystem Information:\\r\\n");
    printf("  MCU: STM32F103C8T6\\r\\n");
    printf("  Flash: 64KB\\r\\n");
    printf("  RAM: 20KB\\r\\n");
    printf("  Bootloader: v%s\\r\\n", BOOTLOADER_VERSION);
    printf("  App valid: %s\\r\\n", check_app_valid() ? "Yes" : "No");
    
    /* 读取设备ID */
    uint32_t id[3];
    id[0] = HAL_GetUIDw0();
    id[1] = HAL_GetUIDw1();
    id[2] = HAL_GetUIDw2();
    printf("  Device ID: %08X%08X%08X\\r\\n", id[0], id[1], id[2]);
}

/* 升级固件 */
static void update_firmware(void)
{
    printf("\\r\\nStarting firmware update...\\r\\n");
    
    if (ymodem_receive() == 0) {
        printf("\\r\\nUpdate successful!\\r\\n");
        
        /* 等待用户确认 */
        printf("Press any key to jump to application...\\r\\n");
        uint8_t ch;
        HAL_UART_Receive(&huart1, &ch, 1, HAL_MAX_DELAY);
        
        /* 检查并跳转 */
        if (check_app_valid()) {
            jump_to_app();
        } else {
            printf("Application invalid!\\r\\n");
        }
    } else {
        printf("\\r\\nUpdate failed!\\r\\n");
    }
}

/* 擦除应用程序 */
static void erase_application(void)
{
    printf("\\r\\nErasing application...\\r\\n");
    
    flash_unlock();
    if (flash_erase_app() == 0) {
        printf("Erase complete\\r\\n");
    } else {
        printf("Erase failed!\\r\\n");
    }
    flash_lock();
}

/* 主函数 */
int main(void)
{
    uint8_t option;
    
    /* 初始化HAL库 */
    HAL_Init();
    SystemClock_Config();
    
    /* 初始化GPIO */
    MX_GPIO_Init();
    
    /* 初始化串口 */
    MX_USART1_UART_Init();
    
    printf("\\r\\n");
    printf("Bootloader started\\r\\n");
    
    /* 检查升级按键 */
    if (!check_update_key()) {
        /* 检查应用程序是否有效 */
        if (check_app_valid()) {
            printf("Application valid, jumping...\\r\\n");
            HAL_Delay(500);
            jump_to_app();
        }
    }
    
    /* 进入升级模式 */
    printf("Entering update mode\\r\\n");
    
    while (1) {
        show_menu();
        
        /* 接收选项 */
        HAL_UART_Receive(&huart1, &option, 1, HAL_MAX_DELAY);
        printf("%c\\r\\n", option);
        
        switch (option) {
        case '1':
            update_firmware();
            break;
            
        case '2':
            if (check_app_valid()) {
                jump_to_app();
            } else {
                printf("\\r\\nNo valid application!\\r\\n");
            }
            break;
            
        case '3':
            erase_application();
            break;
            
        case '4':
            show_info();
            break;
            
        case '5':
            printf("\\r\\nRebooting...\\r\\n");
            HAL_NVIC_SystemReset();
            break;
            
        default:
            printf("\\r\\nInvalid option!\\r\\n");
            break;
        }
    }
}

/* GPIO初始化 */
static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    
    /* 配置升级按键 */
    GPIO_InitStruct.Pin = UPDATE_KEY_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(UPDATE_KEY_PORT, &GPIO_InitStruct);
    
    /* 配置LED */
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}

/* 串口初始化 */
static void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    HAL_UART_Init(&huart1);
}

4.5 应用程序配置

应用程序需要配置中断向量表偏移:

📄 在应用程序的 system_stm32f1xx.c 中修改:

c 复制代码
/* 向量表偏移 */
#define VECT_TAB_OFFSET  0x4000  /* 16KB偏移 */

或在 main.c 中添加:

c 复制代码
/* 设置向量表偏移 */
SCB->VTOR = 0x08004000;

五、编译与下载

5.1 编译Bootloader

  1. 配置Flash起始地址为0x08000000,大小16KB
  2. 编译工程

5.2 编译应用程序

  1. 创建新工程,配置Flash起始地址为0x08004000
  2. 添加向量表偏移配置
  3. 编译生成bin文件

5.3 使用SecureCRT升级

  1. 连接串口(115200波特率)
  2. 按住升级按键上电
  3. 选择菜单选项1
  4. 在SecureCRT中选择 Transfer -> Send YMODEM
  5. 选择应用程序bin文件
  6. 等待传输完成

六、故障排查与问题解决

6.1 跳转失败

原因分析:

  • 向量表未正确设置
  • 中断未关闭
  • 外设未复位

解决方案:

c 复制代码
/* 确保正确关闭中断和复位外设 */
__disable_irq();
HAL_DeInit();
__set_MSP(app_stack);
SCB->VTOR = APP_START_ADDR;

6.2 Flash写入失败

原因分析:

  • Flash未解锁
  • 页未擦除
  • 写入地址不对齐

解决方案:

c 复制代码
/* 确保解锁和擦除 */
flash_unlock();
flash_erase_page(addr);
flash_write_halfword(addr, data);
flash_lock();

七、总结

7.1 核心知识点回顾

  1. Bootloader设计:理解启动流程和分区规划
  2. Flash操作:掌握Flash的擦除和编程
  3. YMODEM协议:实现可靠的文件传输
  4. 程序跳转:理解向量表和中断处理

7.2 扩展学习方向

  • 加密升级:实现固件加密和签名验证
  • 双备份机制:实现A/B分区升级
  • 网络升级:通过以太网或WiFi升级
  • USB升级:通过USB Mass Storage升级

7.3 学习资源

官方文档:

相关推荐
不怕犯错,就怕不做3 小时前
Linux-Sensor驱动移植与调试(转载)
linux·驱动开发·嵌入式硬件
LCMICRO-133108477463 小时前
长芯微LCMDC8584完全P2P替代ADS8584,是一款16位、4通道同步采样的逐次逼近型(SAR)模数转换器(ADC)
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·模数转换器adc
前端一小卒3 小时前
前端工程师的全栈焦虑,我用 60 天治好了
前端·javascript·后端
coderyi5 小时前
LLM Agent 浅析
前端·javascript·人工智能
我叫黑大帅5 小时前
TypeScript 6.0 弃用选项错误 TS5101 解决方法
javascript·后端·面试
科雷软件测试5 小时前
使用python+Midscene.js AI驱动打造企业级WEB自动化解决方案
前端·javascript·python
We་ct5 小时前
LeetCode 120. 三角形最小路径和:动态规划详解
前端·javascript·算法·leetcode·typescript·动态规划
异方辰电子6 小时前
8.原理图为什么看不到具体的电路(比如STM32的晶振等)
stm32·单片机·嵌入式硬件
richxu202510017 小时前
嵌入式学习之路->stm32篇->(11)SPI通信(下)
stm32·嵌入式硬件·学习