stm20260709-STM32CubeMX STM32F103RCT6 完整工程搭建图文学习笔记

芯片: STM32F103RCT6 (LQFP64封装, Flash 256KB, RAM 48KB, Cortex-M3 72MHz)
工具: STM32CubeMX V6.x + MDK-ARM V5.32
日期: 2026年7月9日


前期准备

1. 安装 STM32CubeMX

  • 从 ST 官网下载 STM32CubeMX 安装程序(当前版本 V6.x),安装路径建议默认即可。
  • 安装过程中需要选择 Java 运行环境(CubeMX 内置 JRE,一般无需额外安装)。
  • 首次启动后,点击 Help → Manage Embedded Software Packages,下载并安装 STM32F1 系列 Firmware Package(包含 HAL 库源码、启动文件等),版本建议 ≥ 1.8.x。

2. 安装 MDK-ARM (Keil uVision5)

  • 从 Arm Keil 官网下载 MDK-ARM V5.32+ 安装程序。
  • 安装完成后,打开 Pack Installer,下载 STM32F1xx_DFP(Device Family Pack),确保芯片支持包已安装。
  • 如需使用 ST-Link 调试器,还需安装 ST-Link 驱动(STM32 ST-LINK Utility 或 STSW-LINK009)。

3. 硬件连接确认

项目 说明
调试接口 ST-Link V2/V3,SWD 2线(SWDIO + SWCLK)+ GND + 3V3
串口调试 USART3: PC10(TX) → USB-TTL RX, PC11(RX) → USB-TTL TX
供电 3V3 稳压供电,或通过 ST-Link 3V3 输出
晶振 外部 HSE 8MHz 晶振(板载,无需额外连接)

步骤1:新建项目 & 芯片选型

1.1 打开 CubeMX 新建项目

启动 STM32CubeMX,点击主界面 File → New Project 或直接点击首页的 ACCESS TO MCU SELECTOR 按钮,进入 MCU 芯片选择界面。
在这里插入图片描述

1.2 芯片选择

在 MCU Selector 页面,左侧过滤器中进行筛选:

过滤项 选择值 说明
Series STM32F1 F1系列
Line STM32F103 中等性能线
Package LQFP64 64引脚封装
Core Cortex-M3 内核类型

筛选后在右侧芯片列表中找到 STM32F103RCT6,双击选中或右键选择 Start Project
在这里插入图片描述
选中芯片后,CubeMX 会自动打开 Pinout & Configuration 视图,显示芯片引脚分配图。


步骤2:SYS 系统配置 & Debug 接口

2.1 进入 SYS 配置

在左侧 System Core 下拉菜单中,点击 SYS,进入系统配置面板。
在这里插入图片描述

2.2 Debug 模式设置

在 SYS 配置面板的 Debug 下拉框中,选择 Serial Wire(SWD 2线调试模式)。

为什么要选 Serial Wire?

  • 默认 Debug = No Debug 时,芯片上电后 PA13(SWDIO) 和 PA14(SWCLK) 可被释放为普通 GPIO,但 一旦程序运行后将无法再通过 ST-Link 连接调试(俗称"锁芯片")。
  • 选择 Serial Wire 后,PA13 和 PA14 被固定为调试功能引脚,始终可通过 ST-Link 进行下载和在线调试。
  • JTAG (4 pins) 模式占用 PA13/PA14/PA15/PB3/PB4 五个引脚,资源浪费较大,本项目不用。
  • 务必每次新建工程都设置 Debug = Serial Wire,否则烧录一次后可能无法再连接调试器!

2.3 Timebase Source 设置

SYS 面面中的 Timebase Source 默认为 SystemTick(SysTick 定时器)。如果操作系统(如 FreeRTOS)需要使用 SysTick,则应将 Timebase Source 改为其他硬件定时器(如 TIM1),避免与 OS 内核定时器冲突。本项目暂不使用 RTOS,保持默认 SystemTick 即可。


步骤3:RCC 时钟源配置

3.1 进入 RCC 配置

在左侧 System Core 下拉菜单中,点击 RCC,进入时钟源配置面板。
在这里插入图片描述

3.2 配置 HSE (外部高速时钟)

配置项 选择值 说明
High Speed Clock (HSE) Crystal/Ceramic Resonator 使用板载 8MHz 外部晶振
Low Speed Clock (LSE) Disable 本项目不使用外部低速晶振(32.768kHz RTC晶振)

HSE 模式说明:

  • Crystal/Ceramic Resonator:使用外部晶振,需配置 OSC_IN/OSC_OUT 引脚(PD0/PD1),适用于板载8MHz晶振的场景。
  • Bypass:外部时钟信号直接输入 OSC_IN(OSC_OUT 不用),适用于有源晶振或外部时钟源。
  • Disable:不使用外部时钟,完全依赖 HSI 内部 8MHz RC 振荡器(精度较低 ±1%,不推荐用于需要精确时序的场景)。

选择 Crystal/Ceramic Resonator 后,PD0(OSC_IN) 和 PD1(OSC_OUT) 引脚会被自动标记为 RCC 功能。


步骤4:72MHz 时钟树配置

4.1 进入 Clock Configuration 视图

点击 CubeMX 顶部菜单栏的 Clock Configuration 标签页,进入时钟树图形化配置界面。

在这里插入图片描述

4.2 72MHz 时钟树详细配置

在时钟树界面上,按以下步骤逐级配置:

第1级:HSE 输入

节点 配置
Input frequency HSE 8 MHz
PLL Source Mux 选择 HSE (不选 HSI)

注意:PLL Source Mux 有两个选项:HSI 和 HSE。务必选择 HSE(8MHz外部晶振),因为 HSI 的 ±1% 精度不够,且通过 HSI 做PLL倍频时配置较为复杂。

第2级:PLL 倍频

节点 配置项 说明
PLLM Division factor /1 HSE 8MHz → PLL输入 8MHz
PLLN Multiplication factor ×9 8MHz × 9 = 72MHz
PLLP Division factor /2 (默认) 但由于 PLLM=1, 实际 VCO 输出 = 8×9 = 72MHz, 经 /2 后为 36MHz,需要调整

⚠ 重要修正:STM32F103 PLL 倍频计算公式为:
PLLCLK = (HSE / PLLM) × PLLN
(注意:F1系列 PLLM 称为 PLLXTPRE,只有 /1 和 /2 两个选项;PLLN 就是倍频系数)

正确配置方式

  • HSE = 8MHz
  • PLLXTPRE (HSE divider for PLL entry) = Not divided (即 /1)
  • PLL Mul (PLLN) = ×9
  • PLLCLK = 8 × 9 = 72 MHz

如果 PLLXTPRE 选了 /2,则 PLLCLK = (8/2) × 9 = 36MHz,不是72MHz!

CubeMX中的操作:在时钟树上,点击 HSE 分支到 PLL 之间的分频器图标,确保选择 Not divided(即不分频,÷1)。

第3级:系统时钟选择与分频

节点 配置项
System Clock Mux 选择 PLLCLK
AHB Prescaler /1 HCLK = SYSCLK = 72 MHz
APB1 Prescaler /2 PCLK1 = 36 MHz (必须!APB1最高36MHz)
APB2 Prescaler /1 PCLK2 = 72 MHz

为什么 APB1 必须 /2?
STM32F103 的 APB1 总线最高时钟为 36MHz,超过此频率将导致外设工作异常。当 APB1 分频系数 ≠ 1 时,挂载在 APB1 上的定时器(TIM2-TIM7)的时钟会自动×2,即 36×2 = 72MHz,这是定时器输入时钟的来源。
APB2 最高 72MHz,可不分频直接运行。

时钟树完整数据流

HSE 8MHz → PLLXTPRE(/1) → PLL×9 → PLLCLK 72MHz → SYSCLK 72MHz
  → AHB(/1) → HCLK 72MHz (Cortex-M3内核时钟)
    → APB1(/2) → PCLK1 36MHz (USART2/3, SPI2/I2C1/2, TIM2-7)
    → APB2(/1) → PCLK2 72MHz (USART1, SPI1, ADC1, TIM1)

配置完成后,时钟树上的数值应全部显示为绿色(无红色报错),表示配置合法。

4.3 时钟安全系统 (CSS)

可选配置:在时钟树界面可勾选 Clock Security System (CSS),当 HSE 晶振失效时自动切换到 HSI 并产生 NMI 中断。本项目暂不启用。


步骤5:GPIO 引脚配置

5.1 项目 IO 分配表

根据报警项目硬件设计,STM32F103RCT6 的 GPIO 引脚分配如下:

功能分类 引脚 GPIO 模式 说明
4bit 硬件ID PB0 GPIO_Input 上拉输入 ID_bit0
PB1 GPIO_Input 上拉输入 ID_bit1
PB10 GPIO_Input 上拉输入 ID_bit2
PB11 GPIO_Input 上拉输入 ID_bit3
3路按键 PB3 GPIO_Input 上拉输入 Key1
PB4 GPIO_Input 上拉输入 Key2
PB5 GPIO_Input 上拉输入 Key3
报警输出 PB6 GPIO_Output 推挽输出 Alarm_Out1
PB7 GPIO_Output 推挽输出 Alarm_Out2
流水灯 PC6 GPIO_Output 推挽输出 LED1
PC7 GPIO_Output 推挽输出 LED2
PC8 GPIO_Output 推挽输出 LED3
PC9 GPIO_Output 推挽输出 LED4
继电器 PC13 GPIO_Output 推挽输出 Relay1
PD2 GPIO_Output 推挽输出 Relay2
PC12 GPIO_Output 推挽输出 Relay3
PA15 GPIO_Output 推挽输出 Relay4
USART3 PC10 USART3_TX 复用推挽 串口发送
PC11 USART3_RX 复用输入 串口接收
调试 PA13 SWDIO SWD调试 Serial Wire
PA14 SWCLK SWD调试 Serial Wire
ADC1 PA0 ADC1_IN0 模拟输入 ADC通道0
PA1 ADC1_IN1 模拟输入 ADC通道1
内部 ADC1_IN16 片内温度 温度传感器
内部 ADC1_IN17 内部参考电压 Vrefint 1.2V

5.2 在 CubeMX 中配置 GPIO

5.2.1 输入引脚配置(按键 + 硬件ID)

在芯片引脚图上,依次点击 PB0、PB1、PB3、PB4、PB5、PB10、PB11,选择 GPIO_Input

配置完成后,在左侧 System Core → GPIO 中设置每个输入引脚的详细参数:

参数 说明
GPIO mode Input mode 浮空/上拉/下拉可选
GPIO Pull-up/Pull-down Pull-up 上拉,未按下时为高电平
Maximum output speed Low (默认) 输入引脚不需要速度设置
5.2.2 输出引脚配置(LED + 继电器 + 报警)

在芯片引脚图上,依次点击以下引脚并选择 GPIO_Output

  • 流水灯: PC6, PC7, PC8, PC9
  • 继电器: PC13, PD2, PC12, PA15
  • 报警输出: PB6, PB7

System Core → GPIO 中设置输出引脚参数:

参数 说明
GPIO mode Output Push Pull 推挽输出
GPIO Pull-up/Pull-down No pull-up and no pull-down 输出无需上下拉
Maximum output speed Low LED/继电器控制无需高速
Initial state Low (或根据硬件需求) 初始低电平,LED熄灭/继电器断开

PA15 注意事项: PA15 是 JTDI 引脚,当 Debug 设置为 Serial Wire 时,PA15 已从 JTAG 释放,可作为 GPIO 使用。但需确保 SYS → Debug = Serial Wire(不是 JTAG),否则 PA15 被占用。
PB3/PB4 注意事项: PB3(JTDO) 和 PB4(JNTRST) 同理,Serial Wire 模式下这两个引脚也可作为 GPIO 使用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


步骤6:USART3 异步串口 + 全局中断配置

6.1 启用 USART3

在左侧 Connectivity 下拉菜单中,点击 USART3,进入串口配置面板。

Mode 下拉框中选择 Asynchronous(异步模式),即可启用 USART3 的 TX/RX 功能。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.2 USART3 参数配置

Parameter Settings 标签页中,按以下参数配置:

参数 说明
Baud Rate 115200 常用波特率
Word Length 8 Bits 8位数据位(含或不含校验)
Parity None 无校验位
Stop Bits 1 1位停止位
Data Direction Receive and Transmit 全双工收发
Over Sampling 16 Samples 16倍过采样(更稳定)

8N1 说明: 8Bits + None Parity + 1 Stop Bit = 8N1,是最常用的串口通信格式。
当 Parity = None 时,Word Length = 8 Bits 实际数据宽度为 8bit;
当 Parity = Even/Odd 时,Word Length 需设为 9 Bits(8bit数据 + 1bit校验)。

配置完成后,PC10 和 PC11 引脚会自动被标记为 USART3_TX 和 USART3_RX。

6.3 使能 USART3 全局中断

在 USART3 配置面板中,切换到 NVIC Settings 标签页,勾选 USART3 global interruptEnabled 复选框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么要使能全局中断?
HAL 库的串口中断接收模式 (HAL_UART_Receive_IT) 依赖于 USART 全局中断。如果不使能此中断,调用 HAL_UART_Receive_IT() 后数据到达时不会触发中断回调,接收功能完全失效。
这是初学者最容易遗漏的配置项!

6.4 USART3 中断接收代码开发

6.4.1 HAL_UART_Receive_IT 的"一次性"特性

⚠ 关键知识: HAL_UART_Receive_IT()一次性触发的,即调用一次只接收指定数量的字节后自动关闭中断接收。要实现持续接收,必须在回调函数中再次调用 HAL_UART_Receive_IT()

/* main.c - 在 main() 函数的 while(1) 之前启动中断接收 */

/* USER CODE BEGIN 2 */
// 启动 USART3 中断接收,每次接收 8 字节
HAL_UART_Receive_IT(&huart3, RxBuffer, 8);
/* USER CODE END 2 */
6.4.2 中断回调函数实现
/* main.c - 在 USER CODE BEGIN 4 区域内实现回调 */

/* USER CODE BEGIN 4 */

// USART3 接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART3)
    {
        // 处理接收到的 8 字节数据
        ProcessReceivedData(RxBuffer, 8);

        // ⚠ 必须再次调用!否则下次数据到达时不会触发中断
        HAL_UART_Receive_IT(&huart3, RxBuffer, 8);
    }
}

/* USER CODE END 4 */
6.4.3 全局变量定义
/* main.c - USER CODE BEGIN PV 区域 */

/* USER CODE BEGIN PV */
uint8_t RxBuffer[8];  // USART3 中断接收缓冲区
/* USER CODE END PV */
6.4.4 串口发送封装
/* 用户自定义函数 - 可放在 USER CODE BEGIN 0 区域 */

/* USER CODE BEGIN 0 */

// USART3 发送字符串
void USART3_SendString(const char *str)
{
    HAL_UART_Transmit(&huart3, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
}

// USART3 发送数据块
void USART3_SendData(uint8_t *data, uint16_t len)
{
    HAL_UART_Transmit(&huart3, data, len, 100);  // 超时100ms
}

/* USER CODE END 0 */

步骤7:ADC1 DMA 循环采集配置

7.1 启用 ADC1

在左侧 Analog 下拉菜单中,点击 ADC1,进入 ADC 配置面板。

IN0IN1 的复选框勾选启用(对应 PA0 和 PA1 的外部模拟输入通道)。

同时,在 IN16(Temperature Sensor)和 IN17(Internal Reference Voltage)的复选框也需要勾选,以启用片内温度传感器和内部参考电压采集。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.2 ADC1 参数详细配置

Parameter Settings 标签页中配置以下参数:

参数 说明
Clock Prescaler Asynchronous clock mode divided by 4 ADC时钟不超过14MHz
Resolution 12 bits 12位分辨率(0~4095)
Data Alignment Right 数据右对齐
Scan Conversion Mode Enabled 多通道必须开启扫描模式
Continuous Conversion Mode Enabled 连续转换(ADC自动循环扫描所有通道)
Discontinuous Conversion Mode Disabled 不使用间断模式
DMA Continuous Requests Enabled DMA连续请求(ADC每次转换完都触发DMA传输)
End Of Conversion Selection EOC Flag at the end of all channel conversions 所有通道转换完才触发EOC
Number Of Conversion 4 4个通道:IN0, IN1, IN16, IN17

ScanConvMode 必须开启: 多通道采集时,ADC 会按通道排序依次转换(扫描),ScanConvMode=Enabled 是 DMA 多通道采集的前提条件。
ContinuousConvMode 必须开启: ADC完成一轮4通道扫描后自动开始下一轮,实现循环采集,无需软件反复触发。
DMAContinuousRequests 必须开启: 每次 ADC 转换完成都触发 DMA 请求,DMA 自动搬运数据到缓冲区,无需 CPU 参与。

7.2.1 通道排序配置(Rank)

Number Of Conversion = 4 下方,会显示 4 个 Rank 配置项:

Rank 通道 采样时间 说明
Rank 1 Channel 0 (PA0) 55.5 Cycles 外部模拟输入1
Rank 2 Channel 1 (PA1) 55.5 Cycles 外部模拟输入2
Rank 3 Channel 16 (Temperature Sensor) 239.5 Cycles 片内温度(采样时间要长!)
Rank 4 Channel 17 (Internal Reference Voltage) 239.5 Cycles 内部参考电压(采样时间要长!)

⚠ 温度传感器和内部参考电压的采样时间必须 ≥ 17.1μs!
STM32F1 的片内温度传感器和参考电压从使能到稳定需要约 17.1μs 的启动时间,采样周期数必须足够长。
当 ADC 时钟为 12MHz 时,239.5 Cycles ≈ 239.5/12MHz ≈ 19.96μs > 17.1μs,满足要求。
55.5 Cycles ≈ 55.5/12MHz ≈ 4.6μs,用于外部通道足够(外部信号源阻抗较低时)。

7.3 DMA 配置

在 ADC1 配置面板中,切换到 DMA Settings 标签页,点击 Add 按钮添加一条 DMA 通道:

参数 说明
DMA Request ADC1 自动关联
Stream / Channel DMA1_Channel1 ADC1 固定使用 DMA1 Channel1
Direction Peripheral To Memory ADC外设 → 内存缓冲区
Mode Circular 循环模式(缓冲区写满后自动回到起始地址继续写)
Increment Memory: Enabled 内存地址递增(每个通道数据写入不同位置)
Peripheral: Disabled 外设地址不变(ADC数据寄存器地址固定)
Data Width Peripheral: Half Word (16 Bits) ADC 12位数据,半字传输
Memory: Half Word (16 Bits) 与外设宽度一致

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DMA Circular 模式说明:

  • Normal 模式:DMA 传输指定数量的数据后自动停止,需要软件重新启动。
  • Circular 模式:DMA 传输完指定数量后自动回到缓冲区起始位置继续传输,实现"永不停歇"的循环采集。
  • 本项目使用 Circular 模式,配合 ADC 的 ContinuousConvMode 和 DMAContinuousRequests,实现 ADC→DMA→缓冲区的全自动循环采集。
  • 缓冲区大小必须是 通道数 × 2(因为每个通道占一个 Half Word = 2字节),4通道 × 2字节 = 8字节最小缓冲区。

7.4 ADC1 DMA 采集代码开发

7.4.1 缓冲区定义
/* main.c - USER CODE BEGIN PV 区域 */

/* USER CODE BEGIN PV */

#define ADC_DMA_BUF_SIZE  4  // 4个通道

// ADC DMA 双缓冲区(可选:双缓冲防数据撕裂)
uint16_t ADCRes[ADC_DMA_BUF_SIZE];       // DMA 当前写入缓冲区
uint16_t ADCResCopy[ADC_DMA_BUF_SIZE];   // 软件拷贝缓冲区(用于安全读取)

/* USER CODE END PV */
7.4.2 启动 ADC DMA 采集
/* main.c - USER CODE BEGIN 2 区域 */

/* USER CODE BEGIN 2 */

// 启动 ADC1 DMA 循环采集
// 注意:缓冲区大小 = 通道数 × 2字节,传给函数的是字节大小
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADCRes, ADC_DMA_BUF_SIZE);

/* USER CODE END 2 */

⚠ 关键注意:
HAL_ADC_Start_DMA() 的第三个参数 LengthHalf Word 的数量(即数据项数),不是字节总数!
4 通道 = 4 个 Half Word = 4 个 uint16_t = 8 字节内存,但 Length 传 4。

如果误传 8(字节大小),DMA 会传输 8 个 Half Word = 16 字节,超出缓冲区,导致内存溢出!

7.4.3 安全读取 ADC 数据
/* main.c - while(1) 主循环中 */

/* USER CODE BEGIN 3 */

// 方法1:直接读取(适用于单缓冲,可能存在数据撕裂风险)
uint16_t adc_ch0 = ADCRes[0];  // Channel 0 (PA0)
uint16_t adc_ch1 = ADCRes[1];  // Channel 1 (PA1)
uint16_t adc_temp = ADCRes[2]; // Channel 16 (Temperature)
uint16_t adc_vref = ADCRes[3]; // Channel 17 (Vrefint)

// 方法2:拷贝后读取(更安全,防止 DMA 正在写入时读取到半更新数据)
memcpy(ADCResCopy, ADCRes, sizeof(ADCRes));
uint16_t adc_ch0_safe = ADCResCopy[0];

// 计算实际电压值 (参考电压 Vdda = 3.3V)
float voltage_ch0 = (float)adc_ch0 / 4095.0f * 3.3f;

// 计算芯片内部温度 (STM32F1公式)
// V25 = 1.43V (25°C时温度传感器输出), Avg_Slope = 4.3mV/°C
float temperature = ((float)adc_temp / 4095.0f * 3.3f - 1.43f) / 0.0043f + 25.0f;

/* USER CODE END 3 */
7.4.4 ADC DMA 中断回调(可选)
/* main.c - USER CODE BEGIN 4 区域 */

/* USER CODE BEGIN 4 */

// DMA 半传输完成回调(Half Transfer)
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)
{
    // 可在此拷贝前半缓冲区数据
}

// DMA 传输完成回调(Full Transfer)
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    if (hadc->Instance == ADC1)
    {
        // 一轮4通道全部采集完毕,可在此做数据处理标记
        ADCDataReady = 1;  // 数据就绪标志
    }
}

/* USER CODE END 4 */

步骤8:NVIC 中断分组配置

8.1 NVIC 优先级分组理论

STM32F103 的 NVIC 支持 4 位优先级(2^4 = 16 级),这 4 位需要在 抢占优先级(Preemption Priority)次级优先级(Sub Priority / 响应优先级) 之间分配。

CubeMX 中通过 System Core → NVIC → Priority Group 下拉框选择分组方式:

分组 抢占位数 次级位数 抢占级数 次级级数 说明
Group 0 0 bit 4 bit 0 级 16 级 无抢占,全部靠响应优先级排队
Group 1 1 bit 3 bit 2 级 8 级
Group 2 2 bit 2 bit 4 级 4 级 常用,平衡抢占和响应
Group 3 3 bit 1 bit 8 级 2 级
Group 4 4 bit 0 bit 16 级 0 级 本项目使用

8.2 本项目选择 Group 4

本项目配置 NVIC 优先级分组为 Group 4(4位全给抢占优先级,无次级优先级)。

为什么选 Group 4?

  • 报警项目中断源不多(USART3 + ADC DMA),但需要确保高优先级中断能立即打断低优先级中断(抢占)。
  • Group 4 提供 16 级抢占优先级(0~15),数值越小优先级越高。
  • 无次级优先级意味着:同一抢占优先级的中断不能互相抢占,按硬件默认顺序排队。
  • 对于简单的嵌入式项目,Group 4 足够清晰且配置简单。

8.3 各中断优先级分配

中断源 抢占优先级 说明
USART3 global interrupt 6 串口通信,中等优先级
DMA1 Channel1 interrupt 5 ADC DMA传输完成,略高于串口
TIMx (如使用) 10 定时器,低优先级

优先级分配原则:

  1. DMA > 串口 > 定时器 > 其他: DMA传输需要及时响应避免数据丢失,串口通信其次。
  2. 0 为最高优先级,15 为最低优先级(Group 4 中)。
  3. 不要把所有中断都设为最高优先级(0)!否则失去分组的意义,且可能导致低优先级任务长期得不到执行。
  4. SysTick 中断默认优先级为 15(最低),不应随意提高。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


步骤9:Project Manager 工程导出配置

9.1 Project 设置

点击 CubeMX 顶部菜单栏的 Project Manager 标签页,进入工程管理配置界面。

Project 标签页配置:

配置项 说明
Project Name STM32F103RCT6_Alarm 工程名称(英文,无空格)
Project Location F:/upstudy/stu_STM32103/STM32F103RCT6_Alarm 工程存放路径(自定义)
Toolchain / IDE MDK-ARM V5 Keil uVision5 工程格式

Toolchain 选择说明:

  • MDK-ARM V5:生成 Keil uVision5 工程(.uvprojx),最常用。
  • MDK-ARM:生成 Keil uVision4 工程(.uvproj),旧格式。
  • STM32CubeIDE:生成 STM32CubeIDE 工程(Eclipse IDE)。
  • Makefile:生成 Makefile 工程(适用于 GCC 工具链)。
  • IAR:生成 IAR Embedded Workbench 工程。

9.2 Code Generator 设置

Code Generator 标签页配置:

配置项 说明
Generate peripheral initialization as a pair of .c/.h files ✓ 勾选 每个外设生成独立的 .c/.h 文件,代码结构清晰
Keep User Code when re-generating ✓ 勾选 重新生成代码时保留 USER CODE 区域内的用户代码
Set all free pins as analog ✓ 勾选 未使用的引脚设为模拟模式,降低功耗和干扰

这三个选项非常关键!

  • Generate peripheral initialization as a pair of .c/.h files: 不勾选时所有初始化代码堆在 main.c 里,非常混乱。勾选后每个外设有独立文件(如 usart.c/h, adc.c/h, gpio.c/h),便于管理和维护。
  • Keep User Code when re-generating: CubeMX 重新生成代码时,只更新框架代码,USER CODE BEGIN/END 区域内的用户代码会被完整保留。不勾选则重新生成会覆盖所有修改!
  • Set all free pins as analog: 未使用的 GPIO 设为模拟输入模式,可显著降低功耗(GPIO浮空输入会有漏电流),同时减少外部干扰耦合。

9.3 Advanced Settings(可选)

Advanced Settings 标签页,可以选择每个外设使用的驱动库类型:

外设 选项 建议
GPIO HAL / LL HAL(本项目用HAL)
USART3 HAL / LL HAL
ADC1 HAL / LL HAL

HAL vs LL:

  • HAL (Hardware Abstraction Layer):高层抽象库,API 简单易用,但代码体积较大、执行效率较低。适合初学者和一般项目。
  • LL (Low Layer):底层直接操作寄存器库,代码精简、执行高效,但需要深入理解寄存器含义。适合对性能和代码体积有极致要求的项目。
  • 本项目统一使用 HAL 库。

9.4 生成代码

配置完成后,点击 CubeMX 顶部工具栏的 GENERATE CODE 按钮(或 Project → Generate Code),CubeMX 将自动生成完整的 MDK-ARM V5 工程。

生成成功后会弹出提示框,可直接点击 Open Project 打开 Keil 工程,或点击 Open Folder 查看工程目录。


步骤10:业务代码开发全套流程

10.1 HAL 库 USER CODE 保护机制

CubeMX 生成的代码文件中,所有允许用户添加代码的位置都用 USER CODE BEGIN xxxUSER CODE END xxx 标记包围:

/* USER CODE BEGIN 0 */
// ← 你的代码写在这里,重新生成时不会被覆盖
/* USER CODE END 0 */

void MX_GPIO_Init(void)
{
    // CubeMX 生成的初始化代码
    // ⚠ 不要在这里修改!重新生成会被覆盖!

    /* USER CODE BEGIN 1 */
    // ← 你可以在这里添加代码
    /* USER CODE END 1 */
}

⚠ 铁律:所有业务代码必须写在 USER CODE BEGIN/END 之间!
在这些标记之外写的代码,下次 CubeMX 重新生成时会被无警告地完全覆盖删除
这是初学者最容易犯的错误,导致辛苦写的业务代码一键丢失。

10.2 完整业务代码框架

以下展示一个典型的 main.c 业务代码结构,所有代码均在 USER CODE 区域内:

/* Includes ----------------------------------- */
#include "main.h"
#include "usart.h"
#include "adc.h"
#include "gpio.h"

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

/* Private variables ------------------------- */
/* USER CODE BEGIN PV */

#define ADC_DMA_BUF_SIZE  4
#define UART_RX_BUF_SIZE  8

uint16_t ADCRes[ADC_DMA_BUF_SIZE];       // ADC DMA 缓冲区
uint8_t  RxBuffer[UART_RX_BUF_SIZE];     // USART3 接收缓冲区
uint8_t  ADCDataReady = 0;               // ADC 数据就绪标志

/* USER CODE END PV */

/* Private function prototypes --------------- */
/* USER CODE BEGIN PFP */
void USART3_SendString(const char *str);
void ProcessReceivedData(uint8_t *data, uint16_t len);
/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

// USART3 发送字符串封装
void USART3_SendString(const char *str)
{
    HAL_UART_Transmit(&huart3, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
}

// 处理 USART3 接收数据
void ProcessReceivedData(uint8_t *data, uint16_t len)
{
    // 示例:回显接收到的数据
    HAL_UART_Transmit(&huart3, data, len, 100);
}

/* USER CODE END 0 */

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();    // ⚠ DMA 必须在 ADC 之前初始化!
    MX_ADC1_Init();
    MX_USART3_UART_Init();

    /* USER CODE BEGIN 2 */

    // 启动 ADC1 DMA 循环采集
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADCRes, ADC_DMA_BUF_SIZE);

    // 启动 USART3 中断接收
    HAL_UART_Receive_IT(&huart3, RxBuffer, UART_RX_BUF_SIZE);

    // 开机提示
    USART3_SendString("System Ready\r\n");

    /* USER CODE END 2 */

    while (1)
    {
        /* USER CODE BEGIN 3 */

        // 流水灯演示
        static uint32_t ledTick = 0;
        if (HAL_GetTick() - ledTick >= 500)  // 每500ms切换
        {
            ledTick = HAL_GetTick();
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_6);  // LED1
        }

        // 按键检测(简化示例,无消抖)
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_3) == GPIO_PIN_RESET)
        {
            // Key1 按下
            HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);  // Alarm_Out1
        }

        // ADC 数据处理
        if (ADCDataReady)
        {
            ADCDataReady = 0;
            float voltage = (float)ADCRes[0] / 4095.0f * 3.3f;
            // 可在此发送 ADC 数据到串口
        }

        /* USER CODE END 3 */
    }
}

/* USER CODE BEGIN 4 */

// USART3 接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART3)
    {
        ProcessReceivedData(RxBuffer, UART_RX_BUF_SIZE);
        // ⚠ 必须再次调用!
        HAL_UART_Receive_IT(&huart3, RxBuffer, UART_RX_BUF_SIZE);
    }
}

// ADC DMA 传输完成回调
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    if (hadc->Instance == ADC1)
    {
        ADCDataReady = 1;
    }
}

/* USER CODE END 4 */

10.3 DMA 初始化顺序问题

⚠ 极重要!DMA 必须在 ADC 之前初始化!
CubeMX 生成的 main.c 中,函数调用顺序为:

MX_GPIO_Init();
MX_DMA_Init();     ← 必须在此位置
MX_ADC1_Init();    ← DMA 已就绪,ADC 才能正常使用 DMA
MX_USART3_UART_Init();

如果 MX_DMA_Init() 在 MX_ADC1_Init() 之后,ADC DMA 配置会失败,HAL_ADC_Start_DMA() 返回 HAL_ERROR。
CubeMX V6.x 默认已自动处理此顺序,但手动修改 main.c 时务必注意!

10.4 按键消抖代码示例

/* USER CODE BEGIN 0 */

// 带消抖的按键检测函数
// 返回:1 = 按键按下确认,0 = 未按下或抖动中
uint8_t Key_Scan(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
    if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET)  // 第一次检测:低电平
    {
        HAL_Delay(20);  // 消抖延时20ms
        if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET)  // 第二次确认
        {
            // 等待按键释放
            while (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET);
            HAL_Delay(20);  // 释放消抖
            return 1;  // 确认按下
        }
    }
    return 0;  // 未按下
}

/* USER CODE END 0 */

消抖原理: 机械按键在按下/释放瞬间会产生 5~20ms 的抖动信号(电平快速跳变),如果不消抖,一次按键可能被识别为多次。
消抖方法:检测到低电平后延时 20ms 再次确认,确认后等待释放再延时 20ms。
HAL_Delay() 的局限: 在中断回调中不能使用 HAL_Delay()(它依赖 SysTick 中断,在中断中调用会导致死锁)。中断中的按键处理应使用定时器消抖方案。

10.5 工程编译与烧录

  1. 编译: 在 Keil 中点击 Project → Build Target(F7),确认 0 Error, 0 Warning。
  2. 烧录: 点击 Flash → Download(F8),ST-Link 将程序烧录到芯片 Flash。
  3. 调试: 点击 Debug → Start/Stop Debug Session(Ctrl+F5),进入在线调试模式。

附录:常见问题与避坑指南

Q1: 烧录一次后 ST-Link 无法连接?

原因: SYS → Debug 设置为 No Debug,程序运行后 PA13/PA14 被释放为 GPIO,调试器无法连接。

解决:

  • 方法1:按芯片复位按钮(RESET)的同时点击 Keil 烧录,可能可以抢在程序初始化 GPIO 之前连接。
  • 方法2:使用 STM32 ST-LINK Utility,按住复位 → Connect → Erase Full Chip → 烧录空程序。
  • 方法3:从源头预防:每次新建 CubeMX 工程,SYS → Debug = Serial Wire

Q2: USART3 中断接收不工作?

常见原因:

  1. NVIC 中 USART3 global interrupt 未勾选 Enabled → 使能中断。
  2. HAL_UART_Receive_IT() 未在 main() 中调用 → 启动中断接收。
  3. 回调函数中未再次调用 HAL_UART_Receive_IT() → 一次性特性,只触发一次就停止。

Q3: ADC DMA 采集数据全为 0?

常见原因:

  1. MX_DMA_Init() 在 MX_ADC1_Init() 之后调用 → DMA 未就绪,ADC DMA 配置失败。
  2. 温度传感器通道未使能:需要先调用 HAL_ADCEx_Calibration_Start() 校准,并确保 TSVREFE 位已使能(CubeMX 勾选 IN16/IN17 后自动处理)。
  3. ADC 采样时间不足:温度传感器采样时间必须 ≥ 17.1μs。
  4. HAL_ADC_Start_DMA() 的 Length 参数传了字节大小而非 Half Word 数量 → 缓冲区溢出。

Q4: CubeMX 重新生成代码后业务代码丢失?

原因: 业务代码写在了 USER CODE BEGIN/END 标记之外。

解决: 严格遵守规则,所有用户代码只写在 USER CODE BEGIN xxx ~ USER CODE END xxx 之间。CubeMX 生成时只更新标记之外的框架代码。

Q5: PA15 / PB3 / PB4 作为 GPIO 不工作?

原因: 这三个引脚默认为 JTAG 功能(JTDO/JNTRST/JTDI),需要设置 SYS → Debug = Serial Wire 才能释放 PA15 和 PB3/PB4(PB4 在 SWD 模式下仍需注意)。
解决: 确保 SYS → Debug = Serial Wire,不要选 JTAG (4 pins)


附录:CubeMX 配置速查表

配置项 菜单路径
芯片 STM32F103RCT6 MCU Selector
Debug Serial Wire System Core → SYS
HSE Crystal/Ceramic Resonator System Core → RCC
PLL Mul ×9 Clock Configuration
SYSCLK 72 MHz (PLLCLK) Clock Configuration
AHB /1 → 72MHz Clock Configuration
APB1 /2 → 36MHz Clock Configuration
APB2 /1 → 72MHz Clock Configuration
USART3 Mode Asynchronous Connectivity → USART3
USART3 Baud 115200 8N1 Connectivity → USART3 → Parameter Settings
USART3 IRQ Enabled Connectivity → USART3 → NVIC Settings
ADC1 Channels IN0, IN1, IN16, IN17 Analog → ADC1
ADC1 ScanConv Enabled Analog → ADC1 → Parameter Settings
ADC1 ContinuousConv Enabled Analog → ADC1 → Parameter Settings
ADC1 DMAContinuous Enabled Analog → ADC1 → Parameter Settings
ADC1 DMA Mode Circular Analog → ADC1 → DMA Settings
NVIC Group Group 4 (4bit preempt) System Core → NVIC
Toolchain MDK-ARM V5 Project Manager → Project
Periph .c/.h ✓ Generate pairs Project Manager → Code Generator
Keep User Code ✓ Keep Project Manager → Code Generator
Free pins analog ✓ Set analog Project Manager → Code Generator

Logo

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

更多推荐