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

选择: BOOT1 = 0 , BOOT0 = 0 ( 主闪存存储器 )(ref :STM32中文参考手册_V10 ,P33)

2.为了正常配置 Boot 分区 我们需要查需要 一下 Datasheet中的 闪存 组织 ( 以下是 我所使用的芯片对应的闪存组织 )

image-20250218215323955

  • 每个Page 是 2K 字节 大小 ,共 255+1 页 , 总 Flash 大小 :512K . 用来确定 Boot分区大小 (不产生冲突的情况下)(ref:TM32中文参考手册_V10 , P30 )
3.向量表选择位置

image-20250218220428597

我们选定的的地址 必需是 **可以被 64 * 4 (每个向量 占用 一个 Word ,也就是 4 字节,所以 我们可知 一个向量表 占用 256 字节 ) 整除 ** 的地址 .

4.部分 知识 :

Boot Loader ( 引导 加载 ) 是 这个 单词的 含义 . Bootloader 代码 所以 我们一般 可以 称之为 引导加载 程序.

关于 Bootloader 由来 ,互联网上 有着 许多 文献 ,自行 查找 ( 想要了解的)

以及 关于 0x0800 0000 作为 程序 开始的 由来

image-20250218222321573

BootLoader 流程 :

img

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

这是 Reset Handler 中的程序 ,加载 ,跳转执行 ,返回 ,加载 _main 函数 ,然后 ,无返回值的跳转到 __main 函数中 .即跳入 你在main 函数中写的代码

image-20250221204818778

//__Main 执行了什么

image-20250221211944447

//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

因为要使用 C 语言 ,这些都是 执行 C语言 所需要的 环境设置,(也可以更改自己的__main 函数 )

正式进入 Bootloader 的编写 (笔者 使用 J-Link 下载器 ,使用 其他的 ,在下载器设置的位置 自行设置)

(这里再次说明,本次使用的 是 Cortex-M3 的处理器 , 这个处理器 是有 向量偏移寄存器的, 地址 :0xE000_ED08,好处就是我们不需要,在执行 应用程序时 ,再次在 应用程序 flash 头 前 重写一遍 这些异常 handler 的执行地址)

项目配置

创建项目

​ ① 选择芯片

image-20250221221419399

②配置时钟 (外部高速时钟,使用晶振)

image-20250221221530127

③配置下载口(我这里时J-Link,我配置 四口 ,如果你们时 DAP ,或者是 STLink ,可以 Serial Wire )

J-LINK :

image-20250221221629700


DAP,ST-LINK:

image-20250221221809592

④ 配置 自己的 LED 引脚:

image-20250221222002303

⑤配置时钟树

image-20250221222032679

⑥ 配置项目生成路径 ,以及项目需要生成的依赖

image-20250221222154625


image-20250221222244625

⑦ 生成 并 打开 项目

image-20250221222322300

项目中代码

image-20250221223734807

/* 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

  /* 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++;
		
		

image-20250221223808661

步骤 1

打开魔法棒

image-20250221213013603

image-20250222144325632

设置 对应 程序 存储 的 ROM地址 ,RAM 不需要调整 默认 .

因为 Bootloader 是 默认 上电执行的程序 BOOT0 = 0 时.

步骤 2

image-20250221215336037

fromelf --bin -o ".\bin_file\Bootloader.bin" "#L"

在这个地方时写入 上图的 执行指令 ( 在编译之后运行 )

就会在你的 项目 地址 - > MDK-ARM - > bin_file 文件夹中生成你设定的名字的bin 文件

image-20250221222646101

我们知道了 BootLoader 的文件 大小 就可以设置 对应 程序 存放 地址了

步骤3

下载器设置

image-20250221220604371

至此,Bootloader 的项目环境 配置完成


我们可以 重新生成一份新的也可以 ,复制现在的项目 用作 Application 项目 ,推荐重新生成一个新的项目.这样两个项目就没有重叠的地方 独立设置(放在boot loader 设置了的东西 ,你忘记改回来)



Application 项目的环境设置

除了 Bootloader 中的步骤 1 不同,其他 修改 如上

这里需要注意 :

一定要看你的Bootloader 的大小 , 然后划分出大于你Bootloader 程序的 Flash 给 Bootloader 存储

并且 程序 的 首地址 一定要符合 X / 254 = ( Int) 是 个整数 , 原因 在这里 讲清除 了 ----> 3.向量表选择位置

步骤 1 修改成 下面的值

image-20250222144216446

Application 的程序 :

image-20250222145153686

至此 分别把他们烧录进去就可以了

// 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
转载  需要 署名 和 注明来源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值