跳到主要内容

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 画原理图

  1. 新建工程 → 选 Firmware 模式(支持芯片外设仿真)
  2. 元件库搜 STM32F103C8,放到原理图
  3. LED-YELLOW(黄色 LED)+ RES(电阻),画电路:
    PC13 ---[ 330Ω ]--- LED 阳极
    LED 阴极 --- GND

4.2 加载 HEX 文件

  1. 双击 STM32 芯片 → 选 Program File → 选 Keil 生成的 .hex 文件
  2. Crystal Frequency8MHz

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);
}
}
}

这样主循环可以做其他事,不卡死。

八、小结

第一次点灯的流程看起来很长,实际跑通一次后,后面的项目都是类似的套路:

  1. CubeMX 配 IO / 外设
  2. 生成代码
  3. 写业务逻辑(写在 USER CODE 标记之间)
  4. 仿真 / 上板验证
  5. 调试

嵌入式入门没有捷径,多写多调,点灯点个 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);
}
}

有问题评论区交流,有其他嵌入式入门想看的也告诉我~