HC32 GPIO 输出 — 点亮并闪烁 LED

HC32F460 GPIO 输出实验:把 PA0/PA1/PA2 配成推挽输出,用 PORT_Toggle 翻转电平做出三颗 LED 一起闪烁的效果。

GPIO 输出:点亮并闪烁 LED

源码路径:example/HC32F460JEUA/1.gpio/gpio_output/source/main.c

1. 功能简介

这是单片机界的「Hello World」。

程序做的事非常朴素:把开发板上的三颗 LED(红、绿、蓝)配置成输出,然后在一个 while(1) 死循环里,每隔 500 毫秒把它们的电平翻转一次。

翻转的效果就是:亮 → 灭 → 亮 → 灭…… 看起来就是三颗灯一起「闪烁」,肉眼看大概每秒闪一次(亮 0.5 秒 + 灭 0.5 秒 = 1 秒一个周期)。

学会这个例子,你就掌握了单片机最基础的能力:让一根引脚按你的意愿输出高低电平。之后的蜂鸣器、继电器、驱动 MOS 管,本质都是这一件事。


2. 硬件准备

最省事的做法:直接用 HC32F460JEUA KIT 开发板,板载三颗 LED 已经焊好,什么都不用接,烧录就能看效果。

板载 LED 接线(低电平点亮):

1
2
3
4
5
6
7
      HC32F460                    LED
   ┌────────────┐
   │        PA0 ├───[ 限流电阻 ]───▶|───┐  红灯
   │        PA1 ├───[ 限流电阻 ]───▶|───┤  绿灯
   │        PA2 ├───[ 限流电阻 ]───▶|───┤  蓝灯
   │            │                       │
   └────────────┘                     3.3V

注意箭头方向:LED 正极(阳极)接 3.3V,负极(阴极)经限流电阻接到引脚。 所以引脚输出低电平时,电流从 3.3V 流过 LED 到引脚,灯亮;输出高电平时两端都是高,没有压差,灯灭。

如果你想外接 LED 自己搭(比如用面包板):

1
   引脚 PA0 ───[ 220Ω~1kΩ 电阻 ]───|◀── LED ── GND

这种接法就是高电平点亮了(引脚给高,电流流向 GND)。极性和板载相反,写代码时要留意。新手建议先用板载的,别自己折腾接线。


3. 完整代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "hc32_ddl.h"                   // HC32 ddl 库总头文件,包含它就够了

/* ---------- LED 引脚定义(改这里就能换引脚,不用动下面的逻辑)---------- */
#define  LED0_PORT        (PortA)       // 红灯:PA0
#define  LED0_PIN         (Pin00)

#define  LED1_PORT        (PortA)       // 绿灯:PA1
#define  LED1_PIN         (Pin01)

#define  LED2_PORT        (PortA)       // 蓝灯:PA2
#define  LED2_PIN         (Pin02)

/* 用宏把「翻转某颗 LED」包一下,主循环里读起来更清爽 */
#define  LED0_TOGGLE()    (PORT_Toggle(LED0_PORT, LED0_PIN))
#define  LED1_TOGGLE()    (PORT_Toggle(LED1_PORT, LED1_PIN))
#define  LED2_TOGGLE()    (PORT_Toggle(LED2_PORT, LED2_PIN))

#define  DLY_MS           (500ul)       // 延时 500 毫秒,ul 表示无符号长整型常量

int32_t main(void)
{
    stc_port_init_t stcPortInit;        // GPIO 配置结构体

    /* 第一步:把结构体清零,避免没赋值的成员是随机垃圾值 */
    MEM_ZERO_STRUCT(stcPortInit);

    /* 第二步:填写配置 */
    stcPortInit.enPinMode = Pin_Mode_Out;   // 输出模式
    stcPortInit.enExInt   = Enable;         // 外部中断输入使能(输出用不到,保持默认)
    stcPortInit.enPullUp  = Enable;         // 内部上拉使能

    /* 第三步:用同一份配置,逐个初始化三颗 LED 的引脚 */
    PORT_Init(LED0_PORT, LED0_PIN, &stcPortInit);
    PORT_Init(LED1_PORT, LED1_PIN, &stcPortInit);
    PORT_Init(LED2_PORT, LED2_PIN, &stcPortInit);

    /* 先把三个引脚都拉高 —— 板载 LED 是低电平点亮,所以此时三颗灯都是灭的 */
    PORT_SetBits(LED0_PORT, LED0_PIN);
    PORT_SetBits(LED1_PORT, LED1_PIN);
    PORT_SetBits(LED2_PORT, LED2_PIN);

    while(1)
    {
        /* 翻转三颗 LED 的电平:原来灭的变亮,原来亮的变灭 */
        LED0_TOGGLE();
        LED1_TOGGLE();
        LED2_TOGGLE();

        Ddl_Delay1ms(DLY_MS);           // 死等 500 毫秒
    };
}

4. 代码分段讲解

4.1 三步配置法

HC32 配任何 GPIO 都是这个固定套路,闭着眼睛都要背下来:

1
2
3
MEM_ZERO_STRUCT(stcPortInit);          // ① 清零
stcPortInit.enPinMode = Pin_Mode_Out;  // ② 填参数
PORT_Init(LED0_PORT, LED0_PIN, &stcPortInit);  // ③ 初始化

为什么第一步要清零? 因为 stcPortInit 是定义在栈上的局部变量,C 语言不会帮你自动清零。如果你只填了 enPinMode,剩下的成员(开漏、驱动能力……)就是内存里的随机值。运气好能跑,运气差就出诡异 bug。MEM_ZERO_STRUCT 一句解决。

4.2 三颗灯共用一份配置

注意 PORT_Init() 被调了三次,但用的都是同一个 &stcPortInit。因为三颗 LED 的配置完全一样(都是输出),没必要写三份结构体。这是个很实用的偷懒技巧。

4.3 为什么先 SetBits 拉高

1
PORT_SetBits(LED0_PORT, LED0_PIN);   // 拉高 = 灭灯

板载 LED 是低电平点亮。上电后先把引脚拉高,保证三颗灯的初始状态是「灭」,这样后面第一次 Toggle 就是「点亮」,视觉上有个干净的起点。

4.4 主循环:翻转 + 延时

1
2
3
4
5
6
7
while(1)
{
    LED0_TOGGLE();               // 翻转
    LED1_TOGGLE();
    LED2_TOGGLE();
    Ddl_Delay1ms(DLY_MS);        // 延时 500ms
}

PORT_Toggle() 是关键。它不用管当前是高还是低,直接反过来。配上 500ms 延时,就是「亮 0.5 秒、灭 0.5 秒」的循环。

Ddl_Delay1ms() 是 ddl 库自带的毫秒级阻塞延时——CPU 在这段时间里啥也不干,纯粹空转数圈。简单例子够用,但真正的项目里不建议长时间用它(CPU 被白白浪费),后面学了定时器就该用定时器来做时间控制。


5. 实验现象

烧录后,开发板上的红、绿、蓝三颗 LED 同时闪烁,节奏是每秒一次(亮半秒、灭半秒)。

因为三颗灯完全同步翻转,看起来会是「三色一起亮、一起灭」。


6. 常见问题

Q1:烧录后灯不亮,或者一直常亮不闪?

  • 先确认板子是不是 LQFP100_v2 版本,LED 引脚是不是 PA0/PA1/PA2。用错板子可能引脚对不上。
  • 检查是不是烧录失败(程序没进去)。多数下载器会有下载成功提示。

Q2:我改成 PORT_ResetBits 想让灯更亮,结果灯灭了?

  • 记住板载 LED 是低电平点亮ResetBits(拉低)才是点亮,SetBits(拉高)是熄灭。别被函数名带偏了。

Q3:Ddl_Delay1ms 延时不准,比预期慢/快?

  • 这个延时依赖系统时钟频率。如果你没调用时钟初始化、系统跑在默认的内部 RC 时钟上,延时就会偏。本例没调 BSP_CLK_Init(),跑在上电默认时钟,节奏大致对但不精确,做闪灯完全够用。

Q4:三颗灯我只想让一颗闪,怎么改?

  • 把主循环里另外两句 LEDx_TOGGLE() 注释掉即可。

7. 小结

  • GPIO 输出三步走:清零结构体 → enPinMode = Pin_Mode_OutPORT_Init
  • PORT_SetBits 拉高、PORT_ResetBits 拉低、PORT_Toggle 翻转。
  • 开发板 LED 低电平点亮,拉低才亮。
  • Ddl_Delay1ms 是简单的阻塞延时,入门够用,项目里少用。

8. 拓展应用

掌握了「控制一根引脚输出」,你已经能做出一堆小东西:

  • 呼吸灯:把 Toggle + 固定延时改成「快速开关 + 调占空比」,就是软件 PWM,能让 LED 由暗到亮渐变。(更专业的做法是用定时器硬件 PWM,见 Timer0 章节)
  • 交通灯:三颗灯按红→绿→黄的顺序和不同时长点亮,就是一个红绿灯模型。
  • 蜂鸣器报警:把 LED 换成有源蜂鸣器,Toggle 就能让它「滴滴」响。
  • 继电器控制:引脚接三极管驱动继电器,就能用单片机开关 220V 的电器(注意强电安全)。
  • 状态指示灯:项目里最实用——用不同的闪烁频率表示「正常运行 / 出错 / 联网中」等状态。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计