星图 C8T6 01|从一颗 LED 开始:验证整条链路
星图计划 · C8T6 卷第 1 颗星。FindStar 寻星工作室 · STM32F103C8T6 · 65 个项目,从这颗灯开始。
为什么第一课永远是点灯
每个学单片机的人都被同一句话送进门:先让灯闪起来。
这话听起来像敷衍,其实是嵌入式开发中一次完整的最小闭环:你写下的每一行 C 代码,要经过编译、链接、下载到 Flash,再由内核在正确的时钟节拍下,把一个寄存器的某个 bit 翻转,最后变成 PCB 上一颗灯珠的明灭。这中间有一整条链路上任何一个环节断了,灯都不会亮——而灯不亮的时候,你手里没有任何其他信号可以依赖,只能靠猜。所以点灯真正的价值不是"点亮",而是把这条链路一次性打通、验证到底。
这篇博客记录的是我在 STM32F103C8T6 最小系统板(也就是大家常说的"蓝色药丸"Blue Pill)上做的第一个工程:让 PC13 上的板载 LED 以 500 ms 的间隔闪烁,并且用逻辑分析仪回头验证一遍时序是不是真的如我所愿。
硬件上的第一个认知:PC13 是低电平点亮的
这是新手最容易卡住的地方,值得单独说。
蓝色药丸板上的板载 LED 接法是"阳极接 3.3 V,阴极接 PC13"。也就是说,PC13 输出低电平时,电流从 3.3 V 经 LED 灌进 PC13,灯才亮;输出高电平时两端没有压差,灯灭。
所以代码里那个看起来有点反直觉的写法其实是正确的:
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);// 输出低电平 → 灯亮HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);// 输出高电平 → 灯灭这也是为什么 CubeMX 里 PC13 的初始电平要设成HIGH——上电瞬间保持高电平,灯是灭的,不会出现一上电就常亮、让人以为程序跑飞了的错觉。
顺带一个容易被忽略的限制:PC13、PC14、PC15 位于低功耗备份域,驱动能力很弱(数据手册标注约 3 mA),只能灌电流带一颗小 LED,别指望它推继电器或者驱动大电流负载。要带负载,请换到 PA、PB 上的普通 IO 口。
CubeMX 里的配置
这次用的工具链是 STM32CubeMX + HAL 库。
| 配置项 | 值 | 说明 |
|---|---|---|
| SYS → Debug | Serial Wire | 保留 SWD 调试口,否则第二次下载会连不上 |
| PC13 → GPIO output level | HIGH | 上电默认灭灯 |
| PC13 → GPIO mode | GPIO_Output | 推挽输出 |
| PC13 → 输出速度 | Low | 点灯不需要高速翻转,低速更干净 |
提示:
SYS → Debug这一项必须显式设置成 Serial Wire。CubeMX 默认会把 PA13/PA14 当成普通 IO,一旦生成代码,SWD 引脚就被复用掉了,下一轮想再下载程序,芯片会"失联"——这是每个 STM32 新手迟早要经历一次的事故。真碰上了也不用慌:把 BOOT0 拉高进系统存储器启动模式,或者按住复位键点下载、在松开的一瞬间完成握手,都能救回来。
主循环里的四行代码
HAL 生成的骨架里,用户代码只写进while (1)里:
while(1){HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);// PC13 输出低电平,灯亮HAL_Delay(500);// 500 ms 延时HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);// PC13 输出高电平,灯灭HAL_Delay(500);// 500 ms 延时}四行,两个动作,两个等待。任何一块最小系统板上连 PC13 的 LED,都会以 500 ms 的间隔闪烁——亮半秒,灭半秒,一个完整周期 1 秒。
这里有两个值得说清楚的细节。
第一,HAL_Delay()并不精确,但足够用。它依赖 SysTick 中断,内部是一个毫秒计数器,被HAL_Init()按 1 kHz 配好。在主循环里裸调它,实际延时 = 500 ms + 两次函数调用与 GPIO 翻转的开销,误差在微秒量级,人眼看不出、点灯也完全不需要在意。但如果你以后要拿它做串口超时、做多任务节拍,就要小心了:HAL_Delay()是阻塞的,它把 CPU 整个占住,谁也别想干活。
第二,别小看"忘记while(1)"这个错误。很多人第一次写完发现灯是常亮的,以为代码没生效,其实是程序从main()返回后跑飞了,PC13 停在了最后一次写入的状态。灯常亮 ≠ 灯没坏,先看程序停在哪。
用逻辑分析仪验证一遍
"灯在闪"只是肉眼结论。想知道时序到底准不准,得上仪器。
我把逻辑分析仪的通道夹在 PC13 上,抓了一段波形。测量结果:
- 低电平脉宽0.499 s(灯亮 500 ms)
- 高电平脉宽0.499 s(灯灭 500 ms)
- 完整周期0.998 s,换算频率1.002 Hz
这和HAL_Delay(500)的理论预期完全对上,那 1~2 ms 的偏差来自 SysTick 中断响应延迟和采样分辨率,属于正常范围。占空比 50%,方波干净,没有毛刺——说明推挽输出配低速档是对的,如果输出速度设成 High,边沿会更陡,这时候长线走线反而容易引入过冲和振铃。
至此,“代码 → 内核 → 寄存器 → 引脚电平"这条链路被完整验证了一遍。这一课真正的收获不是那四行代码,而是你从此有了一个可信的锚点:后面学按键、串口、定时器、PWM、I2C,只要哪一步不对,你都可以退回到这颗灯上,确认"最基础的执行流还是好的”。
踩坑清单
留给未来的自己:
- 没配 Serial Wire→ 第二次下载连不上。救援方法:BOOT0 拉高 / 复位键配合下载。
- 以为高电平才亮灯→ 蓝色药丸是低电平点亮,逻辑反了。看原理图,别猜。
HAL_Delay阻塞整个主循环→ 这个阶段无所谓,等做到多任务或者需要精确节拍时,必须换成定时器中断或 SysTick 回调。- 忘了开 GPIO 时钟→ 手写寄存器版本才会遇到;CubeMX 生成的
MX_GPIO_Init()里已经带了__HAL_RCC_GPIOC_CLK_ENABLE(),用 HAL 反而帮你挡了一刀。 - 用 PC13 驱动大电流负载→ 低功耗域只有 3 mA 左右的驱动能力,带不动。
下一步
点亮一个灯是"能控制一个引脚",下一步是"能同时控制多个灯"——02 多路 LED 流水灯,会讲到HAL_GPIO_WritePin逐个写和直接写ODR寄存器的区别,以及位操作怎么让多个灯跑出花样。那一步会第一次让你觉得"寄存器"这个词没那么吓人。
再往后(按键、消抖、串口)才算真正开始。特别提醒:串口排在后面——不是因为它简单,而是因为它重要。从那里,你手上就有了"让程序说话"的能力,后面所有项目的排错都不再靠猜。前四个项目没有串口,用眼睛和灯看结果,是这四篇唯一难受的地方,忍一下。
一个灯,四行代码,一个 1 Hz 的方波。听着简单,但这是后面的地基。
*星图计划 · C8T6 卷 · 基于 STM32F103C8T6 最小系统板,工具链为 STM32CubeMX + HAL 库,所有代码均在实机上验证。
FindStar *