STM32 BootLoader(Cortex-M3)入门指南
读前须知:
PC : Win10
Keil5 Version :5.30
STM32CubeMX Version:6.2.1
芯片 : STM32F103ZET6
不指定 板子 , M3 芯片 都可 , 在这里 使用 HAL 库 ,标准库不做说明.
**M0 内核 注意 ,M0 的芯片 内部 没有 向量表 偏移寄存器 VTOR ,需要手动 修改 启动文件实现(xxx.s) **
特殊说明:(用作了解)
1.为了 正常 执行 我们的代码 ,我们先 对 板子的 启动 方式进行检查
选择: BOOT1 = 0 , BOOT0 = 0 ( 主闪存存储器 )(ref :STM32中文参考手册_V10 ,P33)
2.为了正常配置 Boot 分区 我们需要查需要 一下 Datasheet中的 闪存 组织 ( 以下是 我所使用的芯片对应的闪存组织 )
3.向量表选择位置
我们选定的的地址 必需是 **可以被 64 * 4 (每个向量 占用 一个 Word ,也就是 4 字节,所以 我们可知 一个向量表 占用 256 字节 ) 整除 ** 的地址 .
4.部分 知识 :
Boot Loader ( 引导 加载 ) 是 这个 单词的 含义 . Bootloader 代码 所以 我们一般 可以 称之为 引导加载 程序.
关于 Bootloader 由来 ,互联网上 有着 许多 文献 ,自行 查找 ( 想要了解的)
以及 关于 0x0800 0000 作为 程序 开始的 由来
BootLoader 流程 :
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字节大小 )
这是 Reset Handler 中的程序 ,加载 ,跳转执行 ,返回 ,加载 _main 函数 ,然后 ,无返回值的跳转到 __main 函数中 .即跳入 你在main 函数中写的代码
//__Main 执行了什么
//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
因为要使用 C 语言 ,这些都是 执行 C语言 所需要的 环境设置,(也可以更改自己的__main 函数 )
正式进入 Bootloader 的编写 (笔者 使用 J-Link 下载器 ,使用 其他的 ,在下载器设置的位置 自行设置)
(这里再次说明,本次使用的 是 Cortex-M3 的处理器 , 这个处理器 是有 向量偏移寄存器的, 地址 :0xE000_ED08,好处就是我们不需要,在执行 应用程序时 ,再次在 应用程序 flash 头 前 重写一遍 这些异常 handler 的执行地址)
项目配置
创建项目
① 选择芯片
②配置时钟 (外部高速时钟,使用晶振)
③配置下载口(我这里时J-Link,我配置 四口 ,如果你们时 DAP ,或者是 STLink ,可以 Serial Wire )
J-LINK :
DAP,ST-LINK:
④ 配置 自己的 LED 引脚:
⑤配置时钟树
⑥ 配置项目生成路径 ,以及项目需要生成的依赖
⑦ 生成 并 打开 项目
项目中代码
/* 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 */
/* USER CODE BEGIN 1 */
unsigned char ucI = 0;
/* USER CODE END 1 */
在 Main 函数的 while 的 / USER CODE BEGIN 3 / 循环中写入
/* 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++;
步骤 1
打开魔法棒
设置 对应 程序 存储 的 ROM地址 ,RAM 不需要调整 默认 .
因为 Bootloader 是 默认 上电执行的程序 BOOT0 = 0 时.
步骤 2
fromelf --bin -o ".\bin_file\Bootloader.bin" "#L"
在这个地方时写入 上图的 执行指令 ( 在编译之后运行 )
就会在你的 项目 地址 – > MDK-ARM – > bin_file 文件夹中生成你设定的名字的bin 文件
我们知道了 BootLoader 的文件 大小 就可以设置 对应 程序 存放 地址了
步骤3
下载器设置
至此,Bootloader 的项目环境 配置完成
我们可以 重新生成一份新的也可以 ,复制现在的项目 用作 Application 项目 ,推荐重新生成一个新的项目.这样两个项目就没有重叠的地方 独立设置(放在boot loader 设置了的东西 ,你忘记改回来)
Application 项目的环境设置
除了 Bootloader 中的步骤 1 不同,其他 修改 如上
这里需要注意 :
一定要看你的Bootloader 的大小 , 然后划分出大于你Bootloader 程序的 Flash 给 Bootloader 存储
并且 程序 的 首地址 一定要符合 X / 254 = ( Int) 是 个整数 , 原因 在这里 讲清除 了 —-> 3.向量表选择位置
步骤 1 修改成 下面的值
Application 的程序 :
至此 分别把他们烧录进去就可以了
// Bootloader Main 函数 代码
#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 */
//Application Main 函数
#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 */
* 以上代码不能直接运行,需要配合硬件 ,修改*
代码链接:
https://gitee.com/kysfh/stm32_-bootloader
转载 需要 署名 和 注明来源
作者:KYSDFH