【STM32】HAL库学习笔记20260707-STM32CubeMX 代码保护机制详解 —— main.c 中如何规范放置用户代码

本文档详细说明 STM32CubeMX 重新生成代码时,如何通过 USER CODE 区域标记保护用户自定义代码不被覆盖,并给出完整的区域划分规则与实战示例。


一、核心原理:USER CODE 标记机制

STM32CubeMX 在生成代码时,使用成对的注释标记来划分"用户区"和"自动生成区":

/* USER CODE BEGIN 标识名 */

//  ↑↑↑ 这一区间内的代码,CubeMX 重新生成时【会保留】

/* USER CODE END 标识名 */

规则速查表

位置 重新生成代码时的行为
标记 之间 (BEGIN ~ END 内部) 保留,不会被覆盖
标记 之外 (无标记的区域) 覆盖,用户代码会丢失
修改标记本身 危险,CubeMX 可能无法识别,导致代码丢失

黄金法则:所有自定义代码,必须写在 USER CODE BEGINUSER 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 重新生成代码时 零丢失 地保留所有用户代码。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐