【STM32】HAL库学习笔记20260707-STM32CubeMX 代码保护机制详解 —— main.c 中如何规范放置用户代码
【STM32】HAL库学习笔记20260707-STM32CubeMX 代码保护机制详解 —— main.c 中如何规范放置用户代码
本文档详细说明 STM32CubeMX 重新生成代码时,如何通过 USER CODE 区域标记保护用户自定义代码不被覆盖,并给出完整的区域划分规则与实战示例。
一、核心原理:USER CODE 标记机制
STM32CubeMX 在生成代码时,使用成对的注释标记来划分"用户区"和"自动生成区":
/* USER CODE BEGIN 标识名 */
// ↑↑↑ 这一区间内的代码,CubeMX 重新生成时【会保留】
/* USER CODE END 标识名 */
规则速查表
| 位置 | 重新生成代码时的行为 |
|---|---|
标记 之间 (BEGIN ~ END 内部) |
✅ 保留,不会被覆盖 |
| 标记 之外 (无标记的区域) | ❌ 覆盖,用户代码会丢失 |
| 修改标记本身 | ❌ 危险,CubeMX 可能无法识别,导致代码丢失 |
黄金法则:所有自定义代码,必须写在
USER CODE BEGIN和USER CODE END之间。
二、main.c 的标准区域分布图
以下是 CubeMX 生成的 main.c 文件结构,标注了所有可用的 USER CODE 区域位置:
main.c 文件结构总览
┌─────────────────────────────────────────────────────┐
│ /* Includes --- */ │
│ #include "main.h" │
│ #include ... (CubeMX自动生成) │
│ │
│ /* USER CODE BEGIN Includes */ ← 区域① │ ← 可放自定义头文件
│ /* USER CODE END Includes */ │
│ │
│ /* USER CODE BEGIN Private defines */ ← 区域② │ ← 可放私有宏定义
│ /* USER CODE END Private defines */ │
│ │
│ /* USER CODE BEGIN Private macro ---*/← 区域③ │ ← 可放私有宏函数
│ /* USER CODE END Private macro */ │
│ │
│ /* USER CODE BEGIN Private variables*/← 区域④ │ ← 可放全局变量
│ /* USER CODE END Private variables */ │
│ │
│ /* USER CODE BEGIN Private function */← 区域⑤ │ ← 可放函数声明
│ /* USER CODE END Private function */ │
│ │
│ /* USER CODE BEGIN 0 */ ← 区域⑥ │ ← 自由区,可放函数实现
│ /* USER CODE END 0 */ │
│ │
│ void main(void) │
│ { │
│ /* USER CODE BEGIN 1 */ ← 区域⑦ │ ← main之前的初始化
│ /* USER CODE END 1 */ │
│ │
│ // HAL_Init(); ← CubeMX自动生成 │
│ // SystemClock_Config(); │
│ // MX_GPIO_Init(); ← CubeMX自动生成 │
│ // MX_USART1_UART_Init(); 等... │
│ │
│ /* USER CODE BEGIN 2 */ ← 区域⑧ │ ← 外设初始化后的用户代码
│ /* USER CODE END 2 */ │
│ │
│ while (1) │
│ { │
│ /* USER CODE BEGIN 3 */ ← 区域⑨ │ ← 主循环代码
│ /* USER CODE END 3 */ │
│ } │
│ /* USER CODE BEGIN WHILE */ ← 区域⑩(部分版本)│
│ /* USER CODE END WHILE */ │
│ } │
│ │
│ // CubeMX自动生成的 Init 函数 (MX_GPIO_Init等) │
│ /* USER CODE BEGIN 4 */ ← 区域⑪ │ ← 可放用户自定义函数
│ /* USER CODE END 4 */ │
└─────────────────────────────────────────────────────┘
三、各区域详细说明与使用规范
区域① — Includes(头文件包含)
位置: 文件头部,CubeMX 生成的 #include 之后
用途: 添加用户自己的头文件
/* USER CODE BEGIN Includes */
#include "bsp_led.h"
#include "bsp_uart.h"
#include "bsp_timer.h"
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
⚠️ 不要在此区域外添加
#include,否则重新生成时会丢失。
区域② — Private defines(私有宏定义)
位置: Includes 之后
用途: 定义常量、缓冲区大小等
/* USER CODE BEGIN Private defines */
#define LED_ON 1
#define LED_OFF 0
#define UART_RX_BUF_SIZE 256
#define TASK_INTERVAL_MS 10
/* USER CODE END Private defines */
区域③ — Private macro(私有宏函数)
位置: defines 之后
用途: 定义带参数的宏
/* USER CODE BEGIN Private macro */
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define LED_TOGGLE(led) HAL_GPIO_TogglePin(LED_PORT, led)
/* USER CODE END Private macro */
区域④ — Private variables(全局变量)
位置: macro 之后
用途: 定义全局变量
/* USER CODE BEGIN Private variables */
uint8_t uart_rx_buf[UART_RX_BUF_SIZE];
uint16_t uart_rx_len = 0;
volatile uint8_t flag_rx_done = 0;
volatile uint32_t system_tick_count = 0;
/* 任务调度结构体 */
typedef struct
{
void (*task_func)(void);
uint32_t period_ms;
uint32_t last_run;
} task_t;
task_t task_list[] = {
{task_led_blink, 500, 0},
{task_uart_process, 100, 0},
};
/* USER CODE END Private variables */
区域⑤ — Private function prototypes(函数声明)
位置: variables 之后
用途: 声明用户自定义函数
/* USER CODE BEGIN Private function prototypes */
static void task_led_blink(void);
static void task_uart_process(void);
static void process_command(uint8_t *buf, uint16_t len);
static void delay_us(uint32_t us);
/* USER CODE END Private function prototypes */
区域⑥ — USER CODE 0(自由代码区)
位置: 函数声明之后,main() 函数之前
用途: 编写用户自定义函数的实现
/* USER CODE BEGIN 0 */
/**
* @brief LED闪烁任务
*/
static void task_led_blink(void)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/**
* @brief UART数据处理任务
*/
static void task_uart_process(void)
{
if (flag_rx_done)
{
process_command(uart_rx_buf, uart_rx_len);
flag_rx_done = 0;
uart_rx_len = 0;
HAL_UART_Receive_IT(&huart1, uart_rx_buf, 1);
}
}
/**
* @brief 微秒级延时
*/
static void delay_us(uint32_t us)
{
uint32_t start = SysTick->VAL;
uint32_t ticks = us * (SystemCoreClock / 1000000);
while ((start - SysTick->VAL) < ticks);
}
/* USER CODE END 0 */
区域⑦ — USER CODE 1(main函数开头)
位置: main() 函数内部,CubeMX 初始化代码之前
用途: 在 HAL_Init / 时钟配置 / 外设初始化之前需要执行的代码(较少使用)
int main(void)
{
/* USER CODE BEGIN 1 */
// 在 HAL_Init() 之前执行的代码
// 注意:此时HAL库尚未初始化,不能使用HAL函数!
// 通常用于硬件级别的早期初始化
/* USER CODE END 1 */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
...
区域⑧ — USER CODE 2(外设初始化之后)
位置: 所有 MX_xxx_Init() 函数调用之后,while(1) 之前
用途: 最常用的初始化区域 —— 启动外设、加载配置、初始化用户模块
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* === 用户初始化代码 === */
// 1. 开启UART接收中断
HAL_UART_Receive_IT(&huart1, uart_rx_buf, 1);
// 2. 初始化LED状态
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
// 3. 启动定时器PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
// 4. 启动ADC DMA
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_BUF_SIZE);
// 5. 打印启动信息
printf("System Started. Version: %s\r\n", FW_VERSION);
/* USER CODE END 2 */
while (1)
{
区域⑨ — USER CODE 3(主循环)
位置: while(1) 循环内部
用途: 核心业务逻辑 —— 主循环中运行的代码
while (1)
{
/* USER CODE BEGIN 3 */
// 方式一:直接编写主循环逻辑
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(500);
// 方式二(推荐):基于系统Tick的任务调度
uint32_t now = HAL_GetTick();
for (int i = 0; i < sizeof(task_list)/sizeof(task_t); i++)
{
if (now - task_list[i].last_run >= task_list[i].period_ms)
{
task_list[i].task_func();
task_list[i].last_run = now;
}
}
/* USER CODE END 3 */
}
💡 在较新版本的 CubeMX 中,
while(1)之后还有一个USER CODE BEGIN WHILE/END WHILE区域,功能与区域⑨类似,注意区分版本。
区域⑪ — USER CODE 4(main函数之后)
位置: main() 函数结束之后,CubeMX 自动生成的 MX_xxx_Init() 函数之后
用途: 编写更多的用户自定义函数实现、中断回调函数
/* USER CODE BEGIN 4 */
/**
* @brief UART接收完成回调
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
uart_rx_len++;
if (uart_rx_buf[uart_rx_len - 1] == '\n')
{
flag_rx_done = 1;
}
else
{
HAL_UART_Receive_IT(&huart1, &uart_rx_buf[uart_rx_len], 1);
}
}
}
/**
* @brief 定时器中断回调
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
system_tick_count++;
}
}
/**
* @brief 命令处理
*/
static void process_command(uint8_t *buf, uint16_t len)
{
if (strncmp((char*)buf, "LED ON", 6) == 0)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
} else if (strncmp((char*)buf, "LED OFF", 7) == 0)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
}
printf("CMD: %.*s\r\n", len, buf);
}
/* USER CODE END 4 */
四、其他文件中的 USER CODE 区域
除了 main.c,CubeMX 在其他生成文件中也有保护区域:
4.1 stm32xxxx_it.c(中断文件)
/* 在 SysTick_Handler 中 */
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
// 可在此添加用户代码
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
// 可在此添加用户代码
/* USER CODE END SysTick_IRQn 1 */
}
4.2 stm32xxxx_hal_msp.c(MSP初始化)
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
/* USER CODE BEGIN UART_MspInit 0 */
// 可在此添加代码
/* USER CODE END UART_MspInit 0 */
// CubeMX自动生成的GPIO/Clock/NVIC配置...
/* USER CODE BEGIN UART_MspInit 1 */
// 可在此添加代码
/* USER CODE END UART_MspInit 1 */
}
4.3 外设初始化函数内部(main.c 中的 MX_xxx_Init)
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
// 初始化前
/* USER CODE END USART1_Init 0 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
// ... CubeMX自动生成 ...
/* USER CODE BEGIN USART1_Init 1 */
// 初始化后、HAL_UART_Init调用前
/* USER CODE END USART1_Init 1 */
HAL_UART_Init(&huart1);
/* USER CODE BEGIN USART1_Init 2 */
// HAL_UART_Init调用后
/* USER CODE END USART1_Init 2 */
}
五、代码放置规范总结表
| 代码类型 | 应放置的区域 | 说明 |
|---|---|---|
自定义 #include |
USER CODE BEGIN Includes |
区域① |
#define 常量 |
Private defines |
区域② |
| 带参数宏 | Private macro |
区域③ |
| 全局变量 / 结构体定义 | Private variables |
区域④ |
| 函数声明 | Private function prototypes |
区域⑤ |
| 函数实现(main之前) | USER CODE 0 |
区域⑥ |
| 早期硬件初始化 | USER CODE 1 |
区域⑦,HAL未初始化 |
| 外设启动 / 模块初始化 | USER CODE 2 |
区域⑧,最常用 |
| 主循环业务逻辑 | USER CODE 3 |
区域⑨ |
| 回调函数 / 工具函数 | USER CODE 4 |
区域⑪ |
| 中断处理用户代码 | stm32xxxx_it.c 的 IRQn 区域 |
见4.1 |
| MSP 层用户代码 | hal_msp.c 的 MspInit 区域 |
见4.2 |
六、常见错误与注意事项
❌ 错误1:在标记外部写代码
#include "main.h"
// ↓↓↓ 错误!这里没有 USER CODE 标记保护
#include "my_header.h"
// ↑↑↑ CubeMX重新生成时会删除这行!
/* USER CODE BEGIN Includes */
#include "bsp_led.h" // ✅ 正确,在标记内
/* USER CODE END Includes */
❌ 错误2:修改或删除标记本身
// ↓↓↓ 错误!删除了标记,CubeMX无法识别保护区域
#include "bsp_led.h"
// ↑↑↑ 重新生成时这行代码会丢失
❌ 错误3:嵌套标记
/* USER CODE BEGIN 0 */
/* USER CODE BEGIN Includes */ // ← 错误!不能嵌套标记
#include "test.h"
/* USER CODE END Includes */
/* USER CODE END 0 */
❌ 错误4:在 USER CODE 1 中使用 HAL 函数
int main(void)
{
/* USER CODE BEGIN 1 */
HAL_Delay(100); // ❌ 错误!此时 HAL_Init() 尚未调用
/* USER CODE END 1 */
HAL_Init(); // HAL在此之后才初始化
✅ 正确做法:给标记区域添加注释说明
/* USER CODE BEGIN Includes */
/* ---- 用户头文件 ---- */
#include "bsp_led.h"
#include "bsp_uart.h"
/* USER CODE END Includes */
七、完整示例:一个规范的 main.c
以下是一个完整的、符合规范的 main.c 示例(以 STM32F103 为例):
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : 主程序 - LED闪烁 + UART命令控制
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN Private defines */
#define LED_ON 0
#define LED_OFF 1
#define UART_RX_BUF_SIZE 128
#define CMD_MAX_LEN 32
/* USER CODE END Private defines */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN Private macro */
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
/* USER CODE END Private macro */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Private variables */
uint8_t rx_byte;
uint8_t rx_buf[UART_RX_BUF_SIZE];
uint16_t rx_index = 0;
volatile uint8_t flag_cmd_ready = 0;
typedef struct {
void (*func)(void);
uint32_t period;
uint32_t last;
} task_t;
static void task_led(void);
static void task_uart(void);
task_t tasks[] = {
{task_led, 500, 0},
{task_uart, 10, 0},
};
/* USER CODE END Private variables */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN Private function prototypes */
static void task_led(void);
static void task_uart(void);
static void process_command(void);
static void send_help(void);
/* USER CODE END Private function prototypes */
/* USER CODE BEGIN 0 */
/**
* @brief 重定向printf到USART1
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/**
* @brief LED任务
*/
static void task_led(void)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/**
* @brief UART命令处理任务
*/
static void task_uart(void)
{
if (flag_cmd_ready)
{
process_command();
flag_cmd_ready = 0;
rx_index = 0;
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}
/**
* @brief 解析并执行命令
*/
static void process_command(void)
{
rx_buf[rx_index] = '\0';
if (strncmp((char*)rx_buf, "help", 4) == 0)
{
send_help();
} else if (strncmp((char*)rx_buf, "led on", 6) == 0)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, LED_ON);
printf("LED: ON\r\n");
} else if (strncmp((char*)rx_buf, "led off", 7) == 0)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, LED_OFF);
printf("LED: OFF\r\n");
} else
{
printf("Unknown cmd: %s\r\n", rx_buf);
}
}
static void send_help(void)
{
printf("=== Commands ===\r\n");
printf(" led on - Turn LED on\r\n");
printf(" led off - Turn LED off\r\n");
printf(" help - Show this message\r\n");
}
/* USER CODE END 0 */
/**
* @brief 主程序
*/
int main(void)
{
/* USER CODE BEGIN 1 */
// HAL尚未初始化,不使用HAL函数
/* USER CODE END 1 */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// 开启UART中断接收
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
// 初始LED状态
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, LED_OFF);
// 启动信息
printf("\r\n=== STM32 Boot OK ===\r\n");
printf("Type 'help' for commands.\r\n");
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
uint32_t now = HAL_GetTick();
for (uint32_t i = 0; i < ARRAY_SIZE(tasks); i++)
{
if (now - tasks[i].last >= tasks[i].period)
{
tasks[i].func();
tasks[i].last = now;
}
}
__WFI(); // 进入低功耗等待中断唤醒
/* USER CODE END 3 */
}
}
/* USER CODE BEGIN 4 */
/**
* @brief UART接收完成回调
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
if (rx_byte == '\n' || rx_byte == '\r')
{
if (rx_index > 0)
{
flag_cmd_ready = 1;
return;
}
}
else
{
if (rx_index < CMD_MAX_LEN - 1)
{
rx_buf[rx_index++] = rx_byte;
}
}
HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
}
}
/* USER CODE END 4 */
八、最佳实践清单
| # | 实践 | 说明 |
|---|---|---|
| 1 | 所有自定义代码必须写在标记内 | 这是唯一的硬性规则 |
| 2 | 绝不修改/删除标记注释 | 标记是 CubeMX 的识别依据 |
| 3 | 不在标记外添加任何代码 | 包括 #include、变量、函数 |
| 4 | 初始化代码放 USER CODE 2 | 外设 Init 之后、主循环之前 |
| 5 | 业务逻辑放 USER CODE 3 | while(1) 循环体内 |
| 6 | 回调函数放 USER CODE 4 | main 函数之后 |
| 7 | 函数实现放 USER CODE 0 或 4 | 0 在 main 前,4 在 main 后 |
| 8 | 变量声明放 Private variables | 区域④ |
| 9 | 使用版本控制(Git) | 生成前先 commit,便于对比和回退 |
| 10 | 生成前先备份 | 养成习惯,防患于未然 |
| 11 | 不手动修改 MX_xxx_Init 函数 | 配置变更应通过 CubeMX 图形界面修改 |
| 12 | 用 __weak 函数做回调 |
CubeMX 的回调已是 __weak,直接在 USER CODE 4 重写即可 |
九、CubeMX 配置项补充
在 CubeMX 的 Project Manager → Code Generator 中,有以下相关选项:
| 选项 | 推荐设置 | 说明 |
|---|---|---|
| STM32Cube MCU Package | Copy only the necessary library files | 减小项目体积 |
| Generate peripheral initialization as a pair of .c/.h | ✅ 勾选 | 外设初始化独立文件 |
| Keep User Code when re-generating | ✅ 勾选 | 关键选项,保留用户代码 |
| Delete previously generated files when not re-generated | ❌ 不勾选 | 避免误删用户文件 |
| Set all free pins as analog | 根据需要 | 减少功耗 |
十、总结
CubeMX 代码保护机制核心要点
┌──────────────────────────────────────────┐
│ BEGIN ~ END 之间 → 保留 ✅ │
│ 标记之外 → 覆盖 ❌ │
│ 绝不删除/修改标记 → 否则失效 ❌ │
└──────────────────────────────────────────┘
记住口诀:
"代码写在标记内,标记本身不能改,
初始化放CODE 2,主循环放CODE 3,
回调函数放CODE 4,变量声明放变量区。"
只要严格遵守上述规范,即可在 CubeMX 重新生成代码时 零丢失 地保留所有用户代码。
更多推荐


所有评论(0)