STM32 开发入门 · 完整教程
从选型到点灯到 CAN:时钟树、GPIO、中断、定时器、串口、CAN 外设全梳理。工控嵌入式新手系统学习路线。
⌕
输入关键词实时过滤下方章节。
★一句话总览
先建立整体框架。
- STM32 是意法半导体(ST)的 32 位 ARM Cortex-M 系列单片机,主流型号 F103(M3 内核)跑 72MHz,性价比高、资料多,是入门首选。
- 开发三板斧:CubeMX 图形化配置 → HAL 库函数编程 → Keil/STM32CubeIDE 编译下载;也可以纯寄存器(快但门槛高)。
- 外设套路统一:开时钟 → 配引脚(GPIO)→ 配外设参数 → 开中断/收发。学会一个外设,其他的都是这个流程。
一选型与开发方式
买哪颗芯片、用什么工具链。
常见系列速览
| 系列 | 内核 | 主频 | 定位 | 工控常用 |
|---|---|---|---|---|
| F0 | M0 | 48MHz | 低成本入门 | 简单 IO/串口 |
| F1 | M3 | 72MHz | 经典全能 | CAN/串口/定时器,性价比王 |
| F4 | M4F | 168~180MHz | 高性能带 FPU/DSP | 屏显、电机、算法 |
| F7 / H7 | M7 | 216~480MHz | 旗舰 | 工业视觉、复杂控制 |
| G0/G4 | M0+/M4 | 64~170MHz | 新系列 | G4 带 FDCAN,CAN FD 首选 |
入门买 STM32F103C8T6(蓝丸/最小系统板,几块钱)或 F103RCT6(资源更多)即可。
工具链(推荐 HAL 路线)
- STM32CubeMX:图形化配置时钟/引脚/外设,自动生成初始化代码(推荐 6.x 以上版本)。
- Keil MDK(Windows 主流)或 STM32CubeIDE(ST 官方免费,跨平台):编译下载调试。
- 烧录:ST-Link(推荐,可调试)或串口 ISP(Boot0 拉高)。
- 库:HAL 库(官方现代标准,教程多);老项目可能用标准外设库(SPL,已停更但存量多)。
✅
新手路线建议。CubeMX + HAL 上手最快:配置可视化、出错少;掌握后再看寄存器加深理解(面试/调试时有用)。
二时钟树(F103 为例)
STM32 和 51 最大的区别:外设必须先开时钟才能用。
时钟来源
- HSI:内部 8MHz RC,上电默认,精度一般(±1%~2%),适合要求不高的场合。
- HSE:外部晶振 4~16MHz(板载常为 8MHz),精度高,串口/CAN 必须用。
- 另有 LSI/LSE(低速内部/外部 32.768kHz)给 RTC 和看门狗。
F103 典型时钟路径(72MHz 配置)
HSE 8MHz ──(PLL ×9)── SYSCLK 72MHz
│
AHB 分频 (÷1)
│
┌───────────┴───────────┐
APB1 分频 ÷2 APB2 分频 ÷1
PCLK1 = 36MHz PCLK2 = 72MHz
│ │
USART2/3、TIM2~7、 GPIO、USART1、SPI1、
I2C、CAN、DAC、PWR ADC、TIM1、TIM8、EXTI
| 总线 | 最大频率 | 典型外设 | 定时器时钟注意 |
|---|---|---|---|
| AHB (HCLK) | 72MHz | DMA、SRAM、Flash、内核 | -- |
| APB1(低速) | 36MHz | USART2/3、TIM2~7、I2C、CAN、DAC | 定时器时钟 = 2×PCLK1 = 72MHz |
| APB2(高速) | 72MHz | GPIO、USART1、SPI1、ADC、TIM1/8 | 定时器时钟 = PCLK2 = 72MHz |
常见坑
- 用外设前必须开对应总线时钟:__HAL_RCC_GPIOA_CLK_ENABLE()、__HAL_RCC_CAN1_CLK_ENABLE() 等。
- 串口波特率计算用的是 PCLK1/PCLK2(不是 72MHz 想当然):USART1 用 72MHz,USART2/3 用 36MHz。
- APB1 定时器是 2 倍频:APB1=36MHz 但 TIM2~7 内部×2 得 72MHz 计数时钟(CubeMX 自动处理,但要理解)。
- CubeMX 里改时钟后要 Re-generate,改的是 SystemClock_Config(),别手改生成文件。
三GPIO 通用输入输出
最基础的外设,所有操作的第一步。
端口与引脚
- F103 有 GPIOA~G,每端口 16 脚(PA0~PA15 等)。
- 每个引脚可配置输入/输出/复用/模拟四种模式 + 速度 + 上下拉。
- 复用(AF):引脚被外设占用,如 PA9=USART1_TX、PA11=CAN1_RX、PA12=CAN1_TX。
CubeMX 配置
- Pinout 视图点引脚 → 选 GPIO_Output / GPIO_Input / 外设复用(如 USART1_TX)。
- GPIO 参数:Output level(初值)、Mode(推挽/开漏)、Pull-up/down、Speed(Low/High)。
HAL 常用函数
// 写输出
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 置1
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 置0
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转
// 读输入
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_SET) { ... }
点灯实例(PA5 接 LED)
while (1) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(500); // 500ms 翻转一次
}
⚠️
推挽 vs 开漏。推挽(Push-Pull)直接输出高/低,驱动 LED/继电器用;开漏(Open-Drain)只拉低或悬空,需外接上拉,用于 I2C/电平转换。开漏当推挽用输出不了高电平。
⚠️
5V 容忍。F103 大部分引脚标注 5V 容忍(FT),可接 5V 输入;但模拟引脚(ADC)和部分特殊脚不能超 VDD,查看数据手册引脚表确认。
四中断与 NVIC
事件驱动编程的核心:不要让 CPU 轮询等按键。
体系
- NVIC:嵌套向量中断控制器,管理所有中断的优先级和使能。
- EXTI:外部中断线,GPIO 上升/下降沿触发进中断。
- 外设中断:串口接收、定时器溢出、CAN 报文到达等,都走 NVIC。
- 优先级分组:抢占优先级(可嵌套打断)+ 子优先级(同抢占时排队)。
CubeMX 开外部中断
- GPIO 模式选 External Interrupt Mode with Rising/Falling edge trigger。
- NVIC 设置页勾选 EXTI line 中断,配优先级。
HAL 中断回调
// GPIO 外部中断回调(HAL 自动调用,不用自己写 ISR)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_0) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 按键按下翻转 LED
}
}
// 定时器溢出回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim == &htim2) { flag_1ms = 1; } // 1ms 时基标志
}
⚠️
回调里别干重活。中断回调里只做置标志/收数据入队列,处理放主循环;回调里调用 HAL_Delay 或长耗时函数会阻塞其他中断。
⚠️
按键抖动。机械按键直接接 EXTI 会抖动多次触发,需要消抖(软件延时判断 10~20ms 或 RC 硬件消抖)。
五定时器 TIM
延时、时基、PWM、输入捕获全靠它。
分类
| 类型 | 实例 | 特性 |
|---|---|---|
| 高级定时器 | TIM1、TIM8(APB2) | 带互补输出/死区,电机控制用 |
| 通用定时器 | TIM2~TIM5(APB1,内部×2=72MHz) | 时基/PWM/捕获,最常用 |
| 基本定时器 | TIM6、TIM7 | 仅时基,内部给 DAC 用 |
时基配置(CubeMX)
- Prescaler(预分频):把 72MHz 分到 1MHz → PSC=71(72MHz/(71+1)=1MHz,即 1µs 计数一次)。
- Counter Period(自动重载):ARR=999 → 1000×1µs = 1ms 溢出中断一次。
- 公式:溢出周期 = (PSC+1)×(ARR+1) / TIM_CLK。
PWM 输出(呼吸灯/舵机/调光)
// 开启 PWM 通道 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 改占空比:0~ARR 之间 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 500); // 50%
✅
注意 APB1×2。APB1=36MHz 时 TIM2~7 内部×2 到 72MHz,CubeMX 生成代码会算好;手动算寄存器时别漏了 2 倍。
六串口 USART
调试输出、与上位机/模块通信的第一选择。
挂载与引脚(F103 默认)
| 串口 | 总线时钟 | 默认引脚 | 备注 |
|---|---|---|---|
| USART1 | 72MHz (APB2) | PA9=TX, PA10=RX | 最常用,接调试/电脑 |
| USART2 | 36MHz (APB1) | PA2=TX, PA3=RX | 接模块(GPS/4G/蓝牙) |
| USART3 | 36MHz (APB1) | PB10=TX, PB11=RX | 可重映射到 PB10/11 |
CubeMX 配置
- 选 USART1 异步模式(Asynchronous),配波特率 115200、8N1。
- NVIC 勾选 USART1 global interrupt(接收中断用)。
HAL 收发
// 发送(阻塞)
HAL_UART_Transmit(&huart1, (uint8_t*)"Hello\r\n", 7, 100);
// 接收(中断,收到 1 字节进回调)
HAL_UART_Receive_IT(&huart1, &rxByte, 1);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart == &huart1) {
rxBuf[rxLen++] = rxByte;
if (rxLen >= sizeof(rxBuf) || rxByte == '\n') {
rxComplete = 1; // 主循环处理整包
}
HAL_UART_Receive_IT(&huart1, &rxByte, 1); // 重新开启
}
}
✅
printf 重定向。串口调试最爽的是 printf:重写 fputc 调用 HAL_UART_Transmit(&huart1, ...),勾选 MicroLIB,就能直接 printf 打印变量。
⚠️
中断接收要"续杯"。HAL_UART_Receive_IT 是一次性的,每收完一次必须重新调用,否则不再触发回调。
七CAN 总线(bxCAN)
工控组网核心外设;物理层收发器见《ZLG CTM1051A 教程》。
硬件连接
STM32F103 CTM1051A CAN 总线
PA11 (CAN1_RX) ── RXD ──┤4 RXD │
PA12 (CAN1_TX) ── TXD ──┤3 TXD │── CANH ──┼── 总线 CANH
3.3V ── VCC ──┤1 VCC │ │
GND ── GND ──┤2 GND │── CANL ──┼── 总线 CANL
└──────┘
CubeMX 配置(CAN1)
- 使能 CAN1,引脚自动 PA11/PA12。
- Bit Timings:Prescaler + BS1 + BS2 组合出目标波特率。APB1=36MHz、目标 500kbps 示例:PSC=3, BS1=13, BS2=4 → 位时间 = 1+13+4 = 18 Tq → 36MHz / (3+1) / 18 = 500k,采样点 = (1+13)/18 ≈ 78%。CubeMX 里直接选 500k 并看采样点提示最稳。
- NVIC 勾选 CAN1 RX0 interrupt。
关键概念:波特率与采样点
- 位时间 = 同步段(1Tq) + 传播段 + BS1 + BS2;采样点 = (1+BS1)/(1+BS1+BS2),推荐 75%~87.5%(500k 常用 80% 左右)。
- 总线速率 = APB1时钟 / (Prescaler × 位时间Tq数)。
过滤器(重要!)
- bxCAN 有14 个(F103)滤波器组,决定哪些报文能进 FIFO。默认全放行,但正式项目必须配过滤,否则 CPU 被无关帧刷屏。
- 两种模式:Mask(掩码,匹配一段 ID 范围)和 List(列表,精确匹配几个 ID)。
- 最简单用法:只收指定 ID,CAN_FilterIdHigh/Low 设成目标 ID(标准帧左移 5 位填入)。
HAL 收发(标准帧)
// 发送
CAN_TxHeaderTypeDef tx;
tx.StdId = 0x123; tx.IDE = CAN_ID_STD;
tx.RTR = CAN_RTS_DATA; tx.DLC = 8;
uint8_t data[8] = {1,2,3,4,5,6,7,8};
uint32_t mb;
HAL_CAN_AddTxMessage(&hcan1, &tx, data, &mb);
// 接收(FIFO0 中断)
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING, 0);
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CAN_RxHeaderTypeDef rx;
uint8_t rxData[8];
HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &rx, rxData);
// rx.StdId / rxData / rx.DLC 处理,建议入队列
}
调试顺序(经典流程)
- Loopback 模式:MCU 自发自收,验证控制器/时钟/滤波配置。
- 接上 CTM1051A + 双绞线对连两板,Normal 模式互发。
- 总线两端各接 120Ω 终端电阻(必须)。
- 示波器/逻辑分析仪看 CANH/CANL 差分波形。
⚠️
CAN 报文只发不收的常见原因:波特率不一致(总线会报错)、终端电阻缺失、过滤器把帧滤掉了、CANH/CANL 接反、收发器型号电平不匹配。
八调试与排查
新人最需要的一套排查思路。
工具
- ST-Link + Keil/CubeIDE 调试器:断点、单步、Watch 窗口看变量(首选,别靠猜)。
- 串口 printf:打日志看程序走到哪、值是多少。
- 示波器/逻辑分析仪:看波形、抓时序。
外设不工作的标准排查顺序
- 时钟开了吗?(__HAL_RCC_xxx_CLK_ENABLE() / CubeMX 是否勾选)
- 引脚配置对了吗?(复用功能选对没、引脚有没有被别的外设占用)
- 参数对了吗?(波特率、分频、模式;APB1/APB2 别用错时钟)
- 中断开全了吗?(NVIC 使能 + HAL 启动函数如 Receive_IT)
- 硬件对了吗?(接线、电平、供电、共地)
✅
经验。90% 的新手问题出在前两步:时钟忘开、引脚复用没选。CubeMX 生成代码后先看 MX_xxx_Init() 和 HAL_GPIO_Init 是否如预期。
九注意事项
实战经验汇总。
✅
学习顺序。点灯(GPIO)→ 按键中断(EXTI)→ 定时器(时基/PWM)→ 串口(调试打印)→ 外设通信(I2C/SPI/CAN)→ 项目。别一上来就啃 CAN。
✅
多看书多动手。正点原子/野火的开发板例程是中文圈最全的参考;但例程用标准库多,HAL 项目对照着看,重点学思路。
⚠️
工程别乱删生成文件。CubeMX 生成的核心文件(stm32f1xx_hal_msp.c、main.c 的初始化段)别手动大改,配置用 CubeMX 重新生成,业务代码放 USER CODE 区段。
⚠️
电源。F103 一般 3.3V 供电,5V 引脚输入要看"5V 容忍";VDD 接反/超压会烧芯片,上电前先量。
💡
延伸阅读。本库相关文章:STM32F103C8T6 点亮 LED 新手教程(Keil 建工程细节)、ZLG CTM1051A 隔离 CAN 收发器教程(CAN 物理层)、串行通信三兄弟 UART/I2C/SPI(外设原理)。
没有匹配的内容,换个关键词试试。