Skip to content

附录A CubeMX编程指南

A 附录 A:CubeMX 编程指南

A.1 本章知识导图

uml diagram

图 A-1


A.2 CubeMX 简介与安装

A.2.1 工具定位

STM32CubeMX 是 ST 官方提供的图形化初始化代码生成工具。其核心作用是:

用鼠标点选完成芯片所有外设的初始化配置,然后一键生成可直接编译的 C 工程框架,开发者只需在框架中填写业务逻辑。

CubeMX 解决的核心痛点:

表 A-1 A.2.1 工具定位

痛点 传统手写方式 CubeMX 方式
时钟树配置 手动计算 PLL,易出错 填目标频率,自动计算
引脚复用冲突 翻数据手册逐一核查 图形界面实时检测并高亮冲突
外设初始化代码 手写数十行结构体赋值 图形配置后全部自动生成
FreeRTOS 移植 手动复制源码、修改配置 勾选即可,自动配置调度器

上表对 A.2.1 工具定位中各方案的特性进行了横向对比,便于读者根据实际需求选择最合适的技术路线。

A.2.2 安装方式

CubeMX 提供两种使用方式,功能完全相同:

STM32CubeIDE CubeMX CubeMX Keil MDK 下载:https://www.st.com/stm32cubeide 下载:https://www.st.com/stm32cubemx STM32CubeIDE( STM32CubeMX IDE

图 A-2 A.2.2 安装方式

本课程推荐使用 STM32CubeIDE,因为它将 CubeMX 配置、代码编辑、编译、烧录、调试集成在同一窗口,无需在多个软件间切换。

A.2.3 主界面布局

STM32CubeMX(或 CubeIDE 的 .ioc 编辑界面)分为五个主要区域:

Window Help mode Speed 🟢 🔴 绿 使 [Pinout & Config] [Clock Config] [Project Manager] [Tools] Configuration HSE PLL SYSCLK AHB APB1 APB2 Manager / / (Pin View) / / / / / / File GPIO Clock Project IDE选 HAL库

图 A-3 A.2.3 主界面布局


A.3 创建工程的完整流程

Blue Pill(STM32F103C8T6)LED 闪烁 为例,演示从零创建一个 CubeMX 工程的六个步骤。

A.3.1 步骤① 选择目标芯片

打开 STM32CubeIDE → File → New → STM32 Project → 在芯片选型界面:

Core Freq Flash: RAM: Package: ARM Cortex M3 72 MHz 64 KB 20 KB LQFP48 STM32F103C8 STM32F103C8Tx Next Finish LED_Blink)

图 A-4 A.3.1 步骤① 选择目标芯片

A.3.2 步骤② 系统配置(SYS / RCC)

Pinout & Configuration 标签页左侧的 System Core 类别中:

配置 SYS(调试接口):

SYS Debug SWD Wire ST Link (SWD) Serial

图 A-5 A.3.2 步骤② 系统配置(SYS / RCC)

配置 RCC(时钟源):

RCC HSE PLL Resonator 8MHz 72MHz (High Speed External) Crystal/Ceramic

图 A-6 A.3.2 步骤② 系统配置(SYS / RCC)

完成后,芯片图上 PC14/PC15(晶振引脚)和 PA13/PA14(SWD 引脚)会自动变为绿色,无需手动分配。

A.3.3 步骤③ 引脚功能分配

在芯片引脚图上,鼠标左键点击 PC13 引脚 → 从弹出菜单中选择 GPIO_Output

PC13 GPIO PC13 GPIO High GPIO Output Push Pull GPIO No pull up... Maximum output speed: Low User Label: LED output mode: level: LED LED Pull-up/down: 击PC13 行→

图 A-7 A.3.3 步骤③ 引脚功能分配

A.3.4 步骤④ 时钟树配置

切换到 Clock Configuration 标签页:

Blue Pill 72MHz [PLLXTPRE: [PLLMUL: PLL 72MHz 72MHz [SW: PLL] SYSCLK Presc AHB APB1 Presc HCLK 72MHz PCLK1 36MHz CubeMX 72, HSE(8MHz) /1] /1 /2 (I2C1/USART2/SPI2) HCLK(MHz) 自动完成所有分频/倍频参数的计算与填充 × 9]

图 A-8 A.3.4 步骤④ 时钟树配置

A.3.5 步骤⑤ 外设参数配置

对于本示例(LED 闪烁),GPIO 已在步骤③配置完毕。若有其他外设(如 USART),在 Pinout & Configuration 左侧列表中展开对应类别进行配置(详见第 4 节)。

A.3.6 步骤⑥ 工程设置与代码生成

切换到 Project Manager 标签页:

Project Manager Project Name: Location: STM32CubeIDE Code Generator peripheral initialization of files per peripheral Keep User Code when re generating CODE Project as a pair Generate Code Open Project? Yes, CubeIDE LED_Blink C:\Users\...\STM32Projects\ Toolchain/IDE: '.c/.h' .c/.h,结构更清晰) 标IDE Generate 留USER

图 A-9 A.3.6 步骤⑥ 工程设置与代码生成


A.4 生成代码的结构与编写规范

A.4.1 工程目录结构

CubeMX 生成的工程具有固定目录结构:

Inc Src main.c stm32f1xx hal msp.c stm32f1xx it.c system stm32f1xx.c main.h HAL Handler) SystemInit STM32F1xx HAL Driver CMSIS HAL ST ARM CubeMX LED_Blink/ Core/ 引脚宏定义(LED_Pin LED_GPIO_Port) stm32f1xx_hal_conf.h stm32f1xx_it.h Drivers/ Inc/ Src/ LED_Blink.ioc LED_Blink.elf HAL MSP( 脚时 如SysTick

图 A-10 A.4.1 工程目录结构

规则:只在 Core/Src/Core/Inc/ 中编写用户代码,Drivers/ 目录内容不要手动修改。

A.4.2 main.h 中的引脚宏定义

CubeMX 根据 User Labelmain.h 中自动生成宏定义:

/* main.h(CubeMX 自动生成,勿手动修改此区域) */
#define LED_Pin          GPIO_PIN_13   /* 来自 User Label: LED */

#define LED_GPIO_Port    GPIOC

main.c 中使用宏定义而非硬编码引脚,好处:

/* 推荐写法(使用宏定义)*/
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);

/* 不推荐写法(硬编码,电路改动后需逐处修改)*/
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);

A.4.3 USER CODE BEGIN / END 编写规范

CubeMX 生成的 main.c 中遍布成对的注释标记,用户代码必须写在标记之间

int main(void)
{
    /* USER CODE BEGIN 1 */
    /* 在 HAL_Init 之前的变量声明或早期初始化 */
    uint32_t tick_count = 0;
    /* USER CODE END 1 */

    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    /* USER CODE BEGIN 2 */
    /* GPIO 初始化完成后,主循环之前的一次性操作 */
    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); /* LED 灭 */
    /* USER CODE END 2 */

    while (1)
    {
        /* USER CODE BEGIN WHILE */
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        HAL_Delay(500);
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
        /* USER CODE END 3 */
    }
}

表 A-2 常见 USER CODE 区域说明:

区域标记 位置 适合写什么
USER CODE BEGIN Includes 头文件区 添加自定义头文件
USER CODE BEGIN PD 宏定义区 添加 #define 常量
USER CODE BEGIN PV 全局变量区 声明全局变量
USER CODE BEGIN 0 函数实现前 定义辅助函数
USER CODE BEGIN 1 main 开头 局部变量声明
USER CODE BEGIN 2 初始化后 一次性启动逻辑
USER CODE BEGIN WHILE while(1) 内 主循环业务逻辑

⚠️ 警告:写在 USER CODE BEGIN/END 之外的代码,在下次执行 "Generate Code" 时会被 CubeMX 覆盖删除

A.4.4 中断回调函数的编写规范

HAL 库采用弱符号回调(weak callback)机制:HAL 驱动内部定义了 __weak 修饰的空回调函数,用户在 USER CODE BEGIN 0 区域重写同名函数即可覆盖:

/* 在 main.c 的 USER CODE BEGIN 0 区域重写 */

/* GPIO 外部中断回调(按键按下触发)*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == BTN_Pin)
    {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}

/* 定时器溢出回调 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}

A.5 常用外设配置实战

A.5.1 GPIO — 输出与输入

LED 输出(推挽输出):

CubeMX 配置:PC13 → GPIO_Output,Mode = Output Push Pull,Label = LED

/* 主循环中控制 LED */
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); /* 低电平 → 亮 */
HAL_Delay(500);
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);   /* 高电平 → 灭 */
HAL_Delay(500);

/* 或使用翻转,更简洁 */
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(500);

按键输入(上拉输入):

CubeMX 配置:PA0 → GPIO_Input,Pull = Pull-up,Label = BTN

/* 轮询方式读取按键 */
if (HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin) == GPIO_PIN_RESET)
{
    /* 按键按下(低电平有效,因为接了上拉) */
    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
}
else
{
    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
}

A.5.2 USART — 串口通信

CubeMX 配置步骤:

Connecti ity Mode: Asynchronous( Parameter Settings: Baud Rate: 115200 Word Length: 8 Bits Parity: None Stop Bits: 1 Bits/s USART1_TX,PA10 USART1_RX USART1 PA9

图 A-11 A.5.2 USART — 串口通信

生成代码中自动出现 MX_USART1_UART_Init(),用户调用 HAL API:

/* USER CODE BEGIN Includes */
#include <string.h>  /* for strlen */
/* USER CODE END Includes */

/* USER CODE BEGIN 2 */
char msg[] = "Hello from STM32!\r\n";

HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
/* USER CODE END 2 */

/* 接收单字节(阻塞模式)*/
uint8_t rx_byte;
HAL_UART_Receive(&huart1, &rx_byte, 1, HAL_MAX_DELAY);

/* 接收完成后用中断回调(非阻塞,推荐)*/
HAL_UART_Receive_IT(&huart1, &rx_byte, 1); /* 启动中断接收 */

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) /* 接收完成回调 */
{
    if (huart->Instance == USART1)
    {
        /* 处理 rx_byte,然后重新启动接收 */
        HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
    }
}

A.5.3 TIM — 定时器与 PWM

基本定时(产生周期性中断):

Timers Clock Source: Prescaler Counter Period Settings: TIM2 Internal Clock 7199 ÷ 9999 ÷ global interrupt 10 kHz 1 (PSC): (7199+1) (ARR): (9999+1) TIM2 NVIC 72MHz 10kHz Enable Hz(

图 A-12 A.5.3 TIM — 定时器与 PWM

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);  /* 启动定时器中断 */
/* USER CODE END 2 */

/* 定时器溢出回调(每 1 秒触发一次)*/

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{

    if (htim->Instance == TIM2)
    {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}

PWM 输出(控制舵机 / LED 亮度):

Timers PWM Generation CH1 Prescaler: 71 ÷72 1 MHz Counter Period: 19999 TIM3_CH1 TIM3 Channel1: 72MHz 20ms( 50Hz, PA6

图 A-13

/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); /* 启动 PWM 输出 */
/* USER CODE END 2 */

/* 修改占空比(舵机位置控制)*/
/* 脉宽 1ms(0°) ~ 2ms(180°),对应 ARR=19999 时,CCR = 1000 ~ 2000 */

__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 1500); /* 中间位置 90° */

A.5.4 I2C — 总线通信

Connecti ity Mode: I2C Speed Mode: I2C1 Standard Mode( 100 kHz) 4.7kΩ I2C1_SCL,PB7 I2C1_SDA(自动分配,需外接 PB6

图 A-14 A.5.4 I2C — 总线通信

/* 向从设备地址 0x68(如 MPU6050)发送数据 */
uint8_t data[2] = {0x6B, 0x00};   /* 寄存器地址,数据 */
HAL_I2C_Master_Transmit(&hi2c1, 0x68 << 1, data, 2, HAL_MAX_DELAY);

/* 从从设备读取 6 字节 */

uint8_t buf[6];
HAL_I2C_Master_Receive(&hi2c1, 0x68 << 1, buf, 6, HAL_MAX_DELAY);

/* 读写指定寄存器(Mem 方式,更常用)*/
HAL_I2C_Mem_Read(&hi2c1, 0x68 << 1,   /* 设备地址 */
                  0x3B,                 /* 寄存器地址(加速度 X 高字节)*/
                  I2C_MEMADD_SIZE_8BIT,
                  buf, 6, HAL_MAX_DELAY);

A.5.5 ADC — 模拟信号采集

Analog IN0: Parameter Settings: Continuous Con ersion Scan Conversion Mode: 0 PA0 Mode: Enabled Disabled( ADC1 使

图 A-15 A.5.5 ADC — 模拟信号采集

/* USER CODE BEGIN 2 */
HAL_ADC_Start(&hadc1);                /* 启动 ADC 转换 */

/* USER CODE END 2 */

/* 读取 ADC 值(阻塞轮询)*/
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
uint32_t adc_val = HAL_ADC_GetValue(&hadc1);   /* 12位,0 ~ 4095 */

/* 换算为电压(参考电压 3.3V)*/
float voltage = adc_val * 3.3f / 4095.0f;

A.5.6 FreeRTOS 多任务配置

2.5.6.1 启用 FreeRTOS

在 CubeMX 中:Middleware and Software Packs → FreeRTOS → CMSIS_V2

FreeRTOS C nfig configTICK RATE HZ: configTOTAL HEAP SIZE: Tasks and 1000 3072 1ms) Parameters( Queues( 务defaultTask 击Add

图 A-16 启用 FreeRTOS

⚠️ 启用 FreeRTOS 后,CubeMX 会自动将 HAL 时基从 SysTick 改为 TIM(如 TIM1),因为 FreeRTOS 自身需要独占 SysTick。

2.5.6.2 创建任务

在 CubeMX 的 FreeRTOS 配置页 → Tasks and QueuesAdd Task

Task Name: ledTask Priority: osPriorityNormal Stack Size: 128 Entry Function: StartLedTask Code Generation Option: As weak (words = 512 bytes)

图 A-17 创建任务

生成代码后,在 freertos.cUSER CODE BEGIN 区填写任务逻辑:

/* freertos.c - USER CODE BEGIN Header_StartLedTask */
void StartLedTask(void *argument)
{
    /* USER CODE BEGIN StartLedTask */
    for (;;)
    {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        osDelay(500);   /* FreeRTOS 延时:让出 CPU,其他任务可以运行 */
    }
    /* USER CODE END StartLedTask */
}
2.5.6.3 任务间通信

信号量(二值信号量,用于任务同步):

在 CubeMX → FreeRTOS → Timers & SemaphoresAdd Binary Semaphore,命名为 btnSemaphore

/* 中断回调中释放信号量(通知任务有按键事件)*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == BTN_Pin)
    {
        osSemaphoreRelease(btnSemaphoreHandle);
    }
}

/* 按键处理任务:等待信号量 */
void StartBtnTask(void *argument)
{
    for (;;)
    {
        osSemaphoreAcquire(btnSemaphoreHandle, osWaitForever); /* 阻塞等待 */
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}

消息队列(用于传递数据):

在 CubeMX → FreeRTOS → Timers & SemaphoresAdd Queue,命名 adcQueue,长度 4,Item Size uint32_t

/* ADC 采集任务:发送数据到队列 */
void StartAdcTask(void *argument)
{
    for (;;)
    {
        HAL_ADC_Start(&hadc1);
        HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
        uint32_t val = HAL_ADC_GetValue(&hadc1);
        osMessageQueuePut(adcQueueHandle, &val, 0, 0);
        osDelay(10);
    }
}

/* 数据处理任务:从队列接收数据 */
void StartProcessTask(void *argument)
{
    uint32_t val;
    for (;;)
    {
        osMessageQueueGet(adcQueueHandle, &val, NULL, osWaitForever);
        /* 处理 val ... */
    }
}
2.5.6.4 osDelay 与 HAL_Delay 的区别

表 A-3 osDelay 与 HAL_Delay 的区别

函数 底层实现 使用场景
HAL_Delay(ms) SysTick 忙等待(或 TIM 基准轮询) 不可在 FreeRTOS 任务中使用,会阻塞整个调度器
osDelay(ms) FreeRTOS vTaskDelay(),任务挂起 FreeRTOS 任务中必须使用此函数,让出 CPU

A.5.7 重新配置工程

2.5.7.1 修改配置的工作流

当硬件设计发生变化(如增加传感器、修改引脚)或需要启用新外设时:

.ioc CubeMX Generate CODE Code CubeMX / / BEGIN/END USER

图 A-18 修改配置的工作流

2.5.7.2 用户代码保护机制验证

以下场景演示 USER CODE 保护是否生效:

} CubeMX USER CODE USER CODE BEGIN END 2 2 while { USER CODE BEGIN WHILE USER CODE END WHILE MX_GPIO_Init(); MX_USART1_UART_Init(); /* */ // /* */ (1) /* */ HAL_GPIO_TogglePin(...); HAL_Delay(500); /* */ main.c USART1 while(1) 在CubeMX 的main.c:

图 A-19 用户代码保护机制验证

2.5.7.3 常见问题与最佳实践

表 A-4 常见问题与最佳实践

问题 原因 解决方法
代码被覆盖 用户代码写在 USER CODE 标记之外 严格遵守 BEGIN/END 规范
引脚冲突(红色高亮) 两个外设分配了同一引脚 在 CubeMX 中重新分配其中一个引脚
烧录后程序不运行 SYS 未配置 SWD,或 BOOT0 未正确设置 确认 SYS→Debug→Serial Wire 已启用
FreeRTOS 任务卡死 在任务中使用了 HAL_Delay 全部替换为 osDelay
堆栈溢出崩溃 任务栈空间不足 增大 Stack Size,或使用 uxTaskGetStackHighWaterMark 监控
HAL_Delay 不准 FreeRTOS 占用了 SysTick CubeMX 已自动切换时基到 TIM,无需手动处理

2.5.7.4 使用 Coolify 配置容器运行 PicSimLab 仿真

PicSimLab 是一种用于嵌入式/传感器仿真的轻量化仿真环境。可通过 Coolify 将 PicSimLab 以容器形式部署,便于在云或本地服务器上提供仿真服务给学生和实验环境。

示例 docker-compose.yml(作为 Coolify 的仓库部署或本地测试):

version: "3.8"
services:
  picsimlab:
    image: picsimlab/picsimlab:latest    # 若无官方镜像,改为自己构建的镜像名
    restart: unless-stopped
    ports:
      - "8080:8080"                     # Web UI 或 API 端口
    volumes:
      - ./picsimlab-work:/home/picsimlab/work  # 持久化仿真工程与数据
    environment:
      - TZ=Asia/Shanghai

在 Coolify 中部署步骤(简要):

  1. 打开 Coolify 控制台,点击 Create → Application。
  2. 选择 "Container"(容器)或使用 "Repository" 并指向包含 docker-compose.yml 的仓库(推荐)。
  3. 如果选 Container:填写 Image(例如 picsimlab/picsimlab:latest),设置端口映射 8080,配置环境变量与卷挂载(将宿主路径映射到容器的 /home/picsimlab/work)。
  4. 如果选 Repository:指定分支与 Docker Compose 文件路径,Coolify 会根据 docker-compose.yml 构建并部署服务。
  5. 部署完成后,通过 Coolify 提供的域名或映射端口访问 PicSimLab Web 界面(例如 http://:8080 或应用子域)。

注意: - 若没有官方镜像,请在仓库中包含 Dockerfile 或 docker-compose.yml,由 Coolify 从源码构建镜像。 - 保证宿主机有必要的端口、磁盘和 CPU 权限来运行仿真。


A.5.8 本章在线测试(10 题)

Quiz results are saved to your browser's local storage and will persist between sessions.

#

1) 在 CubeMX 中,若要使用 ST-Link 下载与调试,SYS -> Debug 应设置为哪一项?

#

2) Blue Pill 标准 72MHz 配置中,外部晶振通常设置为:

#

3) 在引脚视图中,红色高亮通常表示:

#

4) 下列哪个步骤用于让 CubeMX 自动计算主频相关参数?

#

5) 关于 User Label(例如给 PC13 标记为 LED),正确的是:

#

6) 在 FreeRTOS 任务函数中应优先使用哪个延时函数?

#

7) 重新生成代码时,哪类代码最容易被覆盖?

#

8) 下列关于工程目录的说法哪些正确?

#

9) 若新增 USART1 后重新 Generate Code,最合理的预期是:

#

10) 当 CubeMX 中出现引脚冲突时,优先处理方式是:

Quiz Progress

0 / 0 questions answered (0%)

0 correct