STM32 开发入门 · 完整教程

从选型到点灯到 CAN:时钟树、GPIO、中断、定时器、串口、CAN 外设全梳理。工控嵌入式新手系统学习路线。

STM32 F103 HAL CubeMX Keil
输入关键词实时过滤下方章节。
一句话总览
先建立整体框架。
  • STM32 是意法半导体(ST)的 32 位 ARM Cortex-M 系列单片机,主流型号 F103(M3 内核)跑 72MHz,性价比高、资料多,是入门首选。
  • 开发三板斧:CubeMX 图形化配置 → HAL 库函数编程 → Keil/STM32CubeIDE 编译下载;也可以纯寄存器(快但门槛高)。
  • 外设套路统一:开时钟 → 配引脚(GPIO)→ 配外设参数 → 开中断/收发。学会一个外设,其他的都是这个流程。
选型与开发方式
买哪颗芯片、用什么工具链。
常见系列速览
系列内核主频定位工控常用
F0M048MHz低成本入门简单 IO/串口
F1M372MHz经典全能CAN/串口/定时器,性价比王
F4M4F168~180MHz高性能带 FPU/DSP屏显、电机、算法
F7 / H7M7216~480MHz旗舰工业视觉、复杂控制
G0/G4M0+/M464~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)72MHzDMA、SRAM、Flash、内核--
APB1(低速)36MHzUSART2/3、TIM2~7、I2C、CAN、DAC定时器时钟 = 2×PCLK1 = 72MHz
APB2(高速)72MHzGPIO、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 默认)
串口总线时钟默认引脚备注
USART172MHz (APB2)PA9=TX, PA10=RX最常用,接调试/电脑
USART236MHz (APB1)PA2=TX, PA3=RX接模块(GPS/4G/蓝牙)
USART336MHz (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 处理,建议入队列
}
调试顺序(经典流程)
  1. Loopback 模式:MCU 自发自收,验证控制器/时钟/滤波配置。
  2. 接上 CTM1051A + 双绞线对连两板,Normal 模式互发。
  3. 总线两端各接 120Ω 终端电阻(必须)。
  4. 示波器/逻辑分析仪看 CANH/CANL 差分波形。
⚠️
CAN 报文只发不收的常见原因:波特率不一致(总线会报错)、终端电阻缺失、过滤器把帧滤掉了、CANH/CANL 接反、收发器型号电平不匹配。
调试与排查
新人最需要的一套排查思路。
工具
  • ST-Link + Keil/CubeIDE 调试器:断点、单步、Watch 窗口看变量(首选,别靠猜)。
  • 串口 printf:打日志看程序走到哪、值是多少。
  • 示波器/逻辑分析仪:看波形、抓时序。
外设不工作的标准排查顺序
  1. 时钟开了吗?(__HAL_RCC_xxx_CLK_ENABLE() / CubeMX 是否勾选)
  2. 引脚配置对了吗?(复用功能选对没、引脚有没有被别的外设占用)
  3. 参数对了吗?(波特率、分频、模式;APB1/APB2 别用错时钟)
  4. 中断开全了吗?(NVIC 使能 + HAL 启动函数如 Receive_IT)
  5. 硬件对了吗?(接线、电平、供电、共地)
经验。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(外设原理)。
📚 知识库 · STM32 开发入门完整教程 生成于 2026-08-13
没有匹配的内容,换个关键词试试。