# STM32_BOOTLOADER **Repository Path**: kysfh/stm32_-bootloader ## Basic Information - **Project Name**: STM32_BOOTLOADER - **Description**: 一些学习记录 - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-02-22 - **Last Updated**: 2025-04-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## STM32 BootLoader (Cortex-M3) 新手向 **读前须知:** ​ **PC : Win10** ​ **Keil5 Version :5.30** ​ **STM32CubeMX Version:6.2.1** ​ **芯片 : STM32F103ZET6** ​ 不指定 板子 , M3 芯片 都可 , 在这里 使用 HAL 库 ,标准库不做说明. ​ **M0 内核 注意 ,M0 的芯片 内部 没有 向量表 偏移寄存器 VTOR ,需要手动 修改 启动文件实现(xxx.s) ** **特殊说明:**(用作了解) #### 1.为了 正常 执行 我们的代码 ,我们先 对 板子的 **启动 方式**进行检查 ![image-20250218214254252](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250218214254252.png) 选择: **BOOT1 = 0 , BOOT0 = 0** ( 主闪存存储器 )(ref :STM32中文参考手册_V10 ,P33) #### 2.为了正常配置 Boot 分区 我们需要查需要 一下 Datasheet中的 闪存 组织 ( 以下是 我所使用的芯片对应的闪存组织 ) ![image-20250218215323955](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250218215323955.png) - 每个Page 是 2K 字节 大小 ,共 255+1 页 , 总 Flash 大小 :512K . 用来确定 Boot分区大小 (不产生冲突的情况下)(ref:TM32中文参考手册_V10 , P30 ) #### 3.向量表选择位置 ![image-20250218220428597](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250218220428597.png) 我们选定的的地址 必需是 **可以被 64 * 4 (每个向量 占用 一个 Word ,也就是 4 字节,所以 我们可知 一个向量表 占用 256 字节 ) 整除 ** 的地址 . #### 4.部分 知识 : Boot Loader ( 引导 加载 ) 是 这个 单词的 含义 . Bootloader 代码 所以 我们一般 可以 称之为 引导加载 程序. 关于 Bootloader 由来 ,互联网上 有着 许多 文献 ,自行 查找 ( 想要了解的) 以及 关于 0x0800 0000 作为 程序 开始的 由来 ![image-20250218222321573](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250218222321573.png) BootLoader 流程 : ​ ![img](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM4MTA2OTIz,size_16,color_FFFFFF,t_70.png) ~~~ Url: https://bbs.huaweicloud.com/blogs/233359 ~~~ 我们简单 讲述 一下 .以 文本的方式(水平有限, 如有错误请批评指正): ​ 1.(BOOT0 = 0 ,BOOT1 = X )芯片 上电 ,cpu 从栈顶 获取 主程序 的(0x0000_0000) 堆栈地址(MSP Main Stack Pointer) ,然后跳到 Reset Handler 异常 执行 (0x0000_0004), 这个使用 主闪存映射(0x0800_0000)到 0x0000_0000这个地址,那么 ,上电执行就是 0x0800_0000 里面的内容 ,之后就执行 Reset Handler(0x0800_0004) 异常执行的函数.( **DCD** 是 一个 字 在32 中 代表4字节大小 ) ​ ![image-20250221204439702](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221204439702.png) 这是 Reset Handler 中的程序 ,加载 ,跳转执行 ,返回 ,加载 _main 函数 ,然后 ,无返回值的跳转到 __main 函数中 .即跳入 你在main 函数中写的代码 ![image-20250221204818778](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221204818778.png) ~~~c //__Main 执行了什么 ~~~ ![image-20250221211944447](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221211944447.png) ~~~ c //Doc //Url : https://developer.arm.com/documentation/100748/0618/Embedded-Software-Development/Application-startup //国内可能访问不了 // 更详细 一点 在 << Arm Compiler C Library Startup and Initialization >> (Arm 编译器 C 库的启动和初始化) //url:https://developer.arm.com/documentation/dai0241/latest ~~~ ![image-20250221212627835](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221212627835.png) 因为要使用 C 语言 ,这些都是 执行 C语言 所需要的 环境设置,(也可以更改自己的__main 函数 ) #### 正式进入 Bootloader 的编写 (笔者 使用 J-Link 下载器 ,使用 其他的 ,在下载器设置的位置 自行设置) **(这里再次说明,本次使用的 是 Cortex-M3 的处理器 , 这个处理器 是有 向量偏移寄存器的, 地址 :0xE000_ED08,好处就是我们不需要,在执行 应用程序时 ,再次在 应用程序 flash 头 前 重写一遍 这些异常 handler 的执行地址)** ##### 项目配置 创建项目 ​ ① 选择芯片 ![image-20250221221419399](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221221419399.png) ②配置时钟 (外部高速时钟,使用晶振) ![image-20250221221530127](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221221530127.png) ③配置下载口(我这里时J-Link,我配置 四口 ,如果你们时 DAP ,或者是 STLink ,可以 Serial Wire ) **J-LINK :** ![image-20250221221629700](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221221629700.png) ---- **DAP,ST-LINK:** ![image-20250221221809592](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221221809592.png) ④ 配置 自己的 LED 引脚: ![image-20250221222002303](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221222002303.png) ⑤配置时钟树 ![image-20250221222032679](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221222032679.png) ⑥ 配置项目生成路径 ,以及项目需要生成的依赖 ![image-20250221222154625](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221222154625.png) ---- ![image-20250221222244625](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221222244625.png) ⑦ 生成 并 打开 项目 ![image-20250221222322300](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221222322300.png) 项目中代码 ![image-20250221223734807](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221223734807.png) ~~~c /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ uint32_t JumpAddress = 0; // typedef void ( *pFunction )(void); // pFunction JumpToApplication; #define FLASH_BASE_ADDRESS (( uint32_t ) 0x8000000) #define BOOTLOADER_OFFSET (( uint32_t ) 0x0004000) #define VERTION_CONTROL (( uint32_t ) 0x0001000) #define APPLICATION_ADDRESS VERTION_CONTROL+BOOTLOADER_OFFSET+FLASH_BASE_ADDRESS// unsigned char ucJumpToAppcationFlag = 0; /* USER CODE END 0 */ ~~~ ![image-20250221223851427](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221223851427.png) ~~~c /* USER CODE BEGIN 1 */ unsigned char ucI = 0; /* USER CODE END 1 */ ~~~ 在 Main 函数的 while 的 **/* USER CODE BEGIN 3 */** 循环中写入 ~~~c /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); HAL_Delay(300); if( ucJumpToAppcationFlag == 1) { ucJumpToAppcationFlag = 0; if (((*(__IO uint32_t*)FLASH_BASE_ADDRESS) & 0x2FFE0000 ) == 0x20000000) // { JumpAddress = *(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ); // JumpToApplication = (pFunction) JumpAddress; // __set_MSP(*(__IO uint32_t *) APPLICATION_ADDRESS); // __disable_irq(); JumpToApplication(); } } if(ucI > 30) ucJumpToAppcationFlag = 1 ; ucI++; ~~~ ![image-20250221223808661](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221223808661.png) ##### 步骤 1 打开魔法棒 ![image-20250221213013603](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221213013603.png) ![image-20250222144325632](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250222144325632.png) 设置 对应 程序 存储 的 ROM地址 ,RAM 不需要调整 默认 . 因为 Bootloader 是 默认 上电执行的程序 BOOT0 = 0 时. ##### 步骤 2 ![image-20250221215336037](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221215336037.png) ~~~ fromelf --bin -o ".\bin_file\Bootloader.bin" "#L" ~~~ 在这个地方时写入 上图的 执行指令 ( **在编译之后运行** ) 就会在你的 项目 地址 - > MDK-ARM - > bin_file 文件夹中生成你设定的名字的bin 文件 ![image-20250221222646101](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221222646101.png) 我们知道了 BootLoader 的文件 大小 就可以设置 对应 程序 存放 地址了 ##### 步骤3 下载器设置 ![image-20250221220604371](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250221220604371.png) 至此,Bootloader 的项目环境 配置完成 ------ **我们可以 重新生成一份新的也可以 ,复制现在的项目 用作 Application 项目 ,推荐重新生成一个新的项目.这样两个项目就没有重叠的地方 独立设置(放在boot loader 设置了的东西 ,你忘记改回来)** ---------------- ----------------- ##### Application 项目的环境设置 除了 Bootloader 中的步骤 1 不同,其他 修改 如上 这里需要注意 : ​ **一定要看你的Bootloader 的大小 , 然后划分出大于你Bootloader 程序的 Flash 给 Bootloader 存储** 并且 程序 的 首地址 一定要符合 **X / 254 = ( Int)** 是 个整数 , 原因 在这里 讲清除 了 **----> 3.向量表选择位置** **步骤 1 修改成 下面的值** ![image-20250222144216446](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250222144216446.png) Application 的程序 : ![image-20250222145153686](https://gitee.com/kysfh/stm32_-bootloader/raw/master/Picgo_Image/image-20250222145153686.png) 至此 分别把他们烧录进去就可以了 ~~~C // Bootloader Main 函数 代码 ~~~ ~~~c #include "main.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ uint32_t JumpAddress = 0; // typedef void ( *pFunction )(void); // 函数 指针 pFunction JumpToApplication; //定义一个 函数指针 变量 #define FLASH_BASE_ADDRESS (( uint32_t ) 0x8000000) // Flash 首地址 (用来存放 Bootloader ) #define BOOTLOADER_OFFSET (( uint32_t ) 0x0001000) // bootloader 的大小 #define VERTION_CONTROL (( uint32_t ) 0x0000000) // 给产品 ,以及 升级做 记录的区域 #define APPLICATION_ADDRESS VERTION_CONTROL+BOOTLOADER_OFFSET+FLASH_BASE_ADDRESS // 主程序 开始地址 unsigned char ucJumpToAppcationFlag = 0; //用来做 延迟 进入主程序的变量 , // 在实际开发的时候可以使用 flash 来存储 /** *uint32_t BOOTLOADER_UPLOADING __attribute__((aligned(4), at(BOOTLOADER_OFFSET + FLASH_BASE_ADDRESS + 0))) = 0; // 字节对齐 **/ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ unsigned char ucI = 0; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); HAL_Delay(300); if( ucJumpToAppcationFlag == 1) { ucJumpToAppcationFlag = 0; if (((*(__IO uint32_t*)FLASH_BASE_ADDRESS) & 0x2FFE0000 ) == 0x20000000) // 判断堆栈指针是否在 主 堆栈 { JumpAddress = *(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ); // 获取 Reset handler 的执行地址 //(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ) -- >这个 说的是 将它 强制 转成 __IO uint32_t* 变量 , __IO 一个重定义volatile ,告诉编译器 不需要 优化 我这个变量. 对于敏感的 变量 ,一定要 用 volatile . // *(__IO uint32_t* ) (APPLICATION_ADDRESS + 4 ) 就是 取 这个地址 中的 值 JumpToApplication = (pFunction) JumpAddress; // __set_MSP(*(__IO uint32_t *) APPLICATION_ADDRESS); // 设置堆栈指针 ,我们从 .s 文件中 ,就知道 程序的 首地址 就是 堆栈顶 地址 __disable_irq(); // 关闭中断 // 这里其实还要 清除一下 中断标志位 ,防止 后续 触发 JumpToApplication(); //跳转到 Application } } if(ucI > 30) ucJumpToAppcationFlag = 1 ; ucI++; } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ ~~~ ~~~c //Application Main 函数 ~~~ ~~~c #include "main.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ // BOOTLOADER 3KB 3*1024 = 3072 0xC00 // 这个地址必须要被 256 整除 // 这里我们可以 稍微 在大一点 ,给 Bootloader 4k 的大小 也就是 0x1000 #define FLASH_BASE_ADDRESS (( uint32_t ) 0x8000000) #define BOOTLOADER_OFFSET (( uint32_t ) 0x0001000) #define VERTION_CONTROL (( uint32_t ) 0x0000000) #define APPLICATION_ADDRESS VERTION_CONTROL+BOOTLOADER_OFFSET+FLASH_BASE_ADDRESS /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ // 向量表偏移 SCB->VTOR = APPLICATION_ADDRESS; // 设置向量表的偏移 //有些人 疑惑 为什么这个 向量表的偏移 可以写在 Application 中 ,或者为什么这个时候 才执行 /** * 我的理解 (有误请指针): * MSP PUSH 是 --SP 的, 当跳转到这个 主函数 时,直接运行 Reset Handle ( 主程序 ),主程序中的 Reset Handler 重新 执行 SystemInit(),和__main * __main 执行完 之后 就 调用 c main 函数 ,这个时候就到了 执行上面的语句 .这个期间 MSP 一直在 Reset Handler (进入 主程序 开始),中间 中断我们也 * 关闭了,所以,可以放到这里.能不能放 BootLoader 区 你们 可以 自己 搜索下 .(我没有思考过) * **/ __enable_irq(); //使能 中断 /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); HAL_Delay(100); } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ ~~~ **\** 以上代码不能直接运行,需要配合硬件 ,修改**