HAL 库点灯 —— 从仿真到上板全流程
写在前面
嵌入式入门的"Hello World"是什么?对,我点灯。这篇文章不是教你怎么写 HAL 库,而是把"从建工程到仿真再到上板"这条完整链路走一遍。
主要面向:
- 大一大二刚开始学嵌入式的同学
- 接触过 51 单片机,想学 STM32 的
- 不想直接买开发板,先用仿真试水的
一、所需工具
| 工具 | 用途 |
|---|---|
| STM32CubeMX | 图形化配置生成代码 |
| Keil MDK / IAR / VSCode | 写代码 + 编译 |
| Proteus | 仿真(可以不下到板子) |
| STM32F103 开发板(可选) | 实物验证 |
Proteus 8.9 + SP3 之后的版本支持 STM32 仿真,推荐用最新。
二、CubeMX 配置
2.1 新建工程
打开 CubeMX → File → New Project → 选芯片 STM32F103C8T6(经典型号,资料多)。
2.2 配置时钟
RCC → HSE 选 Crystal/Ceramic Resonator(外部晶振)。
Clock Configuration 标签页:
- 输入频率 8 MHz(板子上的晶振)
- PLL 倍频到 72 MHz(STM32F103 最高主频)
- 系统时钟源选 PLL
2.3 配置 GPIO
假设要点亮 PC13(很多板子的板载 LED 接这个引脚)。
Pinout & Configuration → 找到 PC13 → 左键 → 选 GPIO_Output。
2.4 工程设置
Project Manager:
- Project Name:
led_blink - Toolchain: MDK-ARM(Keil)或你常用的 IDE
- 勾选 Generate peripheral initialization as a pair of '.c/.h' files per peripheral
Code Generator:
- 勾选 Generate peripheral initialization...
- 勾选 Keep User Code when re-generating(保留用户代码)
2.5 生成代码
点 GENERATE CODE,CubeMX 自动生成工程。
三、写代码
打开 main.c,找到 /* USER CODE BEGIN 3 */(主循环里的标记),加上:
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 点亮(低电平点亮)
HAL_Delay(500); // 延时 500ms
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // 熄灭
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
⚠️ STM32F103 的板载 LED 一般是低电平点亮(LED 阴极接 IO),所以
RESET= 亮,SET= 灭。
编译
Keil 里按 F7 编译,没有报错就 OK。
四、Proteus 仿真
4.1 画原理图
- 新建工程 → 选
Firmware模式(支持芯片外设仿真) - 元件库搜
STM32F103C8,放到原理图 - 搜
LED-YELLOW(黄色 LED)+RES(电阻),画电路:PC13 ---[ 330Ω ]--- LED 阳极
LED 阴极 --- GND
4.2 加载 HEX 文件
- 双击 STM32 芯片 → 选
Program File→ 选 Keil 生成的.hex文件 Crystal Frequency填 8MHz
4.3 跑仿真
点左下角 ▶ 按钮,LED 应该以 1Hz 频率闪烁。
如果不闪,检查:
- HEX 文件路径对不对
- 引脚对应对不对(PC13 vs PB12 之类的)
- 仿真器跑得太快/太慢,调整
Animation Frequency
五、上板调试(可选)
如果你有实物板子,直接烧录:
烧录方式:
- ST-Link V2(推荐,稳定)
- J-Link
- 串口 ISP(boot0 跳线,稍微麻烦)
我用的是 ST-Link V2,Keil 里 Debug 按钮直接烧录 + 进入调试模式。
六、可能踩的坑
| 坑 | 解决 |
|---|---|
| Proteus 找不到 STM32F103 | 装 Proteus 8.9 SP3 或更新 |
| Proteus 仿真不运行 | 检查 Firmware 模式 + HEX 路径 |
| LED 不亮(上板) | 检查硬件,可能是 LED 极性反了 |
| HAL_Delay 不准 | 默认 SysTick 是 1ms,精度还行,大延时可改用定时器 |
| 重新生成 CubeMX 代码后自己写的代码丢了 | 必须写在 USER CODE BEGIN/END 标记之间 |
七、进阶:用定时器点灯
HAL_Delay 是阻塞的,实际项目里建议用定时器中断:
// 1. CubeMX 配 TIM2,1ms 中断
// 2. main.c 启动 HAL_TIM_Base_Start_IT(&htim2);
// 3. stm32f1xx_it.c 的 TIM2_IRQHandler 里:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
static uint32_t tick = 0;
if (++tick >= 500) // 500ms
{
tick = 0;
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
}
这样主循环可以做其他事,不卡死。
八、小结
第一次点灯的流程看起来很长,实际跑通一次后,后面的项目都是类似的套路:
- CubeMX 配 IO / 外设
- 生成代码
- 写业务逻辑(写在 USER CODE 标记之间)
- 仿真 / 上板验证
- 调试
嵌入式入门没有捷径,多写多调,点灯点个 5、6 次,后面就熟了。
附:完整 main.c 示例
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : LED Blink with HAL
******************************************************************************
*/
/* USER CODE END Header */
#include "main.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(500);
}
}
有问题评论区交流,有其他嵌入式入门想看的也告诉我~