STM32 Customer BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建
文章目录
- [STM32 Customer BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建](#STM32 Customer BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建)
首先用STM32CubeMX 软件搭建基础工程,来作为二级BootLoader,一级BootLoader是STM32官方自带的startup_stm32f407zgtx.s。我们基于上述最小工程来实现Customer BootLoader的功能。本项目采用的是通过串口实现固件刷新。
下面简单介绍一下二级BootLoder的功能与作用:
二级Customer BootLoader(CBL,Customer BootLoader)是一种在嵌入式系统中常见的软件组件。它主要负责在系统启动时执行初始引导操作,加载和运行应用程序代码。二级Customer BootLoader与一级BootLoader(通常称为Primary BootLoader, PBL)一起工作,提供了更灵活和复杂的引导机制。
功能与作用
- 硬件初始化: 二级Customer BootLoader通常负责对特定硬件的初始化工作。虽然一级BootLoader已经完成了一些基本的硬件初始化,但二级BootLoader会进行更详细的硬件配置,如设置外设(例如UART、SPI、I2C等)、初始化存储设备(如Flash、EEPROM等)以及配置系统时钟等。
- 固件验证与更新: 二级Customer BootLoader常常用于验证固件的完整性和合法性。这可以通过校验和(Checksum)、加密签名等方式来实现。若检测到固件损坏或版本过旧,BootLoader可以从预设的位置(如网络、USB设备或备用存储区)下载并更新固件。
- 安全启动: 为了增强系统安全性,二级BootLoader可以实现安全启动机制。它会检查固件的数字签名或哈希值,确保只有经过验证和授权的固件才能被加载和执行,从而防止恶意代码的运行。
- 引导多种操作系统或应用程序: 二级Customer BootLoader可以配置为引导不同的操作系统或应用程序。例如,在嵌入式系统中,可能需要根据不同的条件引导进入不同的应用程序模块,BootLoader可以根据预设的规则进行选择和加载。
- 配置和诊断功能: 二级BootLoader可以提供一些配置和诊断功能。例如,它可以允许用户通过串口或网络接口进入配置模式,调整系统参数,进行硬件诊断和调试。
- 引导时间优化: 由于嵌入式系统通常需要快速启动,二级BootLoader可以优化引导过程,减少启动时间。它可以通过压缩固件、优化初始化代码等手段来实现快速引导。
典型工作流程
- 系统加电后,一级BootLoader(PBL)启动 :
- 负责基本硬件初始化(如设置堆栈指针、初始化RAM等)。
- 加载并执行二级BootLoader(CBL)。
- 二级BootLoader启动 :
- 执行更详细的硬件初始化。
- 验证固件的完整性和合法性。
- 根据系统配置和状态,选择合适的固件或操作系统进行引导。
- 加载并启动应用程序或操作系统。
目前本项目的Customer BootLoader具备:
- 获取软件版本;
- 读芯片Chip ID;
- 获取Flash Read Protection等级;
- 擦除指定Flash Sector;
- 更新指定Flash Sector内容;
- 使能读/写保护;
下面开始我们本章内容的工程搭建,其中部分图借用洋桃电子杜老师的STM F4系列的课程内容。
1. 硬件原理图介绍
本项目采用正点原子探索者v2开发板,选用其中的左下角的USB串口进行和上位机之间的串口通信。
正点原子STM32F4 探索者V2开发板,如下图所示,通过短接PA9-RXD,短接PA10-TXD,即将USART1与CH340芯片连接在一起,串口USART1与上位机可通过USB进行通信。
如下图电路所示,使用一根MicroUSB结构的USB数据线,一端连接计算机的USB口,一端连接开发版左下角的USB_232口上,就可以在计算机上虚拟出一个串口,通过这个虚拟串口可以进行计算机与开发板之间的串口通信。
2. STM32 CubeMX工程搭建
2.1 创建工程
打开STM32CubdeMX,点击New Project创建新工程
选择 STM32F407 ZGT6
2.2 系统配置
点击左侧System Core,选择RCC,将HSE和LSE都设置为Crystal/Ceramic Resonator(晶体/陶瓷振荡器)
点击SYS,选择Debug功能为JTAG(5 pins),跟板子调试口对应
2.3 USART串口配置
选择左侧的Connecttivity选项,点击USART1,如下图所示,点击Mode开始配置
STM32对USART模块提供了下面的这些模式,根据需求选择相应模式,本项目选择的是异步模式Asynchronous。
下面我们来对USART进行配置,首先开发板上的串口对应的USART1串口,Mode配置为异步模式Asynchronous,STMCubeMX会自动分配引脚,目前分配的USART1_RX对于PA10,USART1_TX对于PA9,和我们开发板的引脚正好对应,如果不对应的话,可以根据芯片的data Sheet改成相应的引脚。
下面的参数配置Parameter Settings按照默认配置来,波特率为 115200 bit/s,这里确保主从机是一致的,才能通信成功,数据位 8,无校验位,停止位1,数据方向:Receive and Transmit,采样:16.
点击下方的GPIO Settings,可以看到为USART1自动分配的默认引脚
2.4 时钟树配置
点击上方的Clock Configuration,开始配置时钟
下面我们来看一下时钟树的结构,如下图所示
现在开始配置开发板相关的时钟频率,首先选择做左边的Input frequency,选择外部8M的晶振,选择HSE,选择PLLCLK,在HCLK处将时钟敲定为168MHz,即STM32F407可支持的最大时钟频率
2.5 工程导出设置
如下图所示,设置工程
代码生成设置
高级设置Advanced Settings
点击右上角,生成代码GENERATE CODE
点击Open Project,本项目是使用STM32CubeIDE作为集成开发环境,做到编译和调试代码的工具
3. 代码编写
我们目前使用的是Hal库进行的工程实现,如下图所示,是串口轮询发送函数HAL_UART_Transmit(),在发送的过程中,会一直在该函数中进行发送,是Polling Mode。
下图是串口接收函数 HAL_UART_Receive(),也是Polling Mode,在接收数据的过程中,CPU无法被抢占,一直需要等到数据被发送完成后才可退出该函数
下面是在main.c中的代码实现:
引用c标准头文件
宏定义,BL_DEBUG_MSG_EN是为调试用的,重定义huart1,设置数据bl_rx_buffer
在main()函数中调用bootloader_uart_read_data()函数进行数据接收和发送
bootloader_uart_read_data()函数中先接收在发送。
printmsg()打印数据函数实现。
下面是main.c的完整代码:
c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BL_DEBUG_MSG_EN
#define BL_RX_LEN 200
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
#define C_UART &huart1
uint8_t bl_rx_buffer[BL_RX_LEN];
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
void bootloader_uart_read_data(void);
static void printmsg(char *format,...);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
bootloader_uart_read_data();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** 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.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 168;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
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_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
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;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
void bootloader_uart_read_data(void)
{
uint8_t rcv_len = 0;
uint8_t rcv_flag = 0;
memset(bl_rx_buffer, 0, 200);
//here we will read and decode the commands coming from host
//first read only one byte from the host , which is the "length" field of the command packet
HAL_UART_Receive(C_UART, (uint8_t *)bl_rx_buffer, 1, HAL_MAX_DELAY);
rcv_len= bl_rx_buffer[0];
if (rcv_len != 0)
{
HAL_UART_Transmit(C_UART,(uint8_t *)&rcv_len, 1, HAL_MAX_DELAY);
rcv_flag = 1;
}
if (rcv_flag == 1)
{
printmsg("-> Going to BL mode\n\r");
}
}
/* prints formatted string to console over UART */
void printmsg(char *format,...)
{
#ifdef BL_DEBUG_MSG_EN
char str[80];
/*Extract the the argument list using VA apis */
va_list args;
va_start(args, format);
vsprintf(str, format,args);
HAL_UART_Transmit(C_UART,(uint8_t *)str, strlen(str),HAL_MAX_DELAY);
va_end(args);
#endif
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
4. 工程下载和调试
将工程编译,之后下载到板子中。
打开设备管理器,查看串口的端口号COM
打开正点原子的串口调试助手,选择刚才设备管理器中串口的COM号,波特率设置为115200,其他默认,打开串口,随便发送一个数据,开发板接收到这个数据,都会原封不动的将该数据打印出来,然后在执行打印Going to BL mode,说明能够接收并发送数据,至此,我们开发Customer BootLoader的第一步,最小工程代码已经搭建完成,后续我们进行上位机与开发板之间的通信协议开发。
如果大家有什么疑问,请随时私信联系我。