PUYA GPIO — GPIO 翻转输出(LL 库)

PUYA PY32F0xx_Firmware V1.5.0 GPIO 翻转输出(GPIO_Toggle)示例教学(LL 库版本)

GPIO — GPIO 翻转输出(LL 库)

HAL vs LL:PUYA 固件库提供 HAL 和 LL 两套 API。

  • HAL 库:封装度高,可移植性强,适合快速开发
  • LL 库:接近寄存器操作,代码精简效率高,适合性能敏感场景 本文使用 LL 库版本。

固件库: PY32F0xx_Firmware V1.5.0
芯片: PY32F030(PY32F002A / F003 同库可参考)
源文件: PUYA/PY32F0xx_Firmware_V1.5.0/Projects/PY32F030-STK/Example_LL/GPIO/GPIO_Toggle/Src/main.c


功能简介

本示例与 HAL 版功能完全相同:让一个 GPIO 引脚工作于输出模式,主循环里周期性翻转电平,板载 LED(PA11,丝印 LED3)闪烁。区别只在于底层 API 用的是 LL 库而非 HAL 库。

通过这一最简单示例,可以直观体会 HAL 与 LL 的写法差异:

  • HAL 用 HAL_GPIO_Init() 传一个 GPIO_InitTypeDef 结构体完成配置;
  • LL 直接调用 LL_GPIO_SetPinMode() 一行设置模式,更接近“操作寄存器”,代码更短、依赖更少。

硬件准备

  • PY32F030 开发板(或 PY32F002A / F003 官方 STK 板)
  • 板载 LED(PA11,对应 LED3
  • 一根 USB 数据线用于供电与下载
  • 无需额外外接元件

提示:若你手上板子的 LED 不在 PA11,修改下方 LED3_PIN 相关宏即可。

完整代码

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include "main.h"
#include "py32f030xx_ll_Start_Kit.h"

/* 把板载 LED3 映射成一组好记的宏,方便后续修改 */
#define LED_GPIO_PIN                   LED3_PIN
#define LED_GPIO_PORT                  LED3_GPIO_PORT
#define LED_GPIO_CLK_ENABLE()          LED3_GPIO_CLK_ENABLE()

/* 函数声明 */
static void APP_SystemClockConfig(void);
static void APP_GpioConfig(void);

int main(void)
{
  /* 1. 配置系统时钟(LL 版需要手动配时钟,HAL 在 HAL_Init 里代劳了部分) */
  APP_SystemClockConfig();

  /* 2. 配置 LED 引脚为输出 */
  APP_GpioConfig();

  /* 3. 先熄灭 LED(输出高电平,低电平点亮,取决于硬件) */
  LL_GPIO_SetOutputPin(LED_GPIO_PORT, LED_GPIO_PIN);

  /* 4. 主循环:每 100ms 翻转一次 */
  while (1)
  {
    LL_mDelay(100);                                   /* LL 自带的毫秒延时 */
    LL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN);   /* 翻转引脚电平 */
  }
}

/**
  * @brief  系统时钟配置
  */
static void APP_SystemClockConfig(void)
{
  /* 使能 HSI 并等待就绪 */
  LL_RCC_HSI_Enable();
  while (LL_RCC_HSI_IsReady() != 1) {}

  /* AHB 不分频 */
  LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);

  /* 选 HSISYS 作为系统时钟源 */
  LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSISYS);
  while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSISYS) {}

  /* APB1 不分频,并初始化 1ms 时基与系统时钟全局变量 */
  LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
  LL_Init1msTick(8000000);
  LL_SetSystemCoreClock(8000000);
}

/**
  * @brief  GPIO 引脚配置
  */
static void APP_GpioConfig(void)
{
  /* 必须先开时钟,否则配置无效(LL 不会自动帮你开) */
  LED_GPIO_CLK_ENABLE();

  /* 一行设置引脚为输出模式,复位后默认就是推挽、低速、无上下拉 */
  LL_GPIO_SetPinMode(LED_GPIO_PORT, LED_GPIO_PIN, LL_GPIO_MODE_OUTPUT);

  /* 下面这些都是“复位默认值”,注释掉也能工作,写出来便于理解
  LL_GPIO_SetPinOutputType(LED_GPIO_PORT, LED_GPIO_PIN, LL_GPIO_OUTPUT_PUSHPULL);
  LL_GPIO_SetPinSpeed(LED_GPIO_PORT, LED_GPIO_PIN, LL_GPIO_SPEED_FREQ_LOW);
  LL_GPIO_SetPinPull(LED_GPIO_PORT, LED_GPIO_PIN, LL_GPIO_PULL_NO);
  */
}

/**
  * @brief  错误处理:卡死在此处便于调试
  */
void APP_ErrorHandler(void)
{
  while (1) {}
}

代码分段讲解

1. LL 没有“统一初始化入口”

HAL 版第一行是 HAL_Init(),它顺手初始化了 HAL 库、SysTick、中断优先级等。LL 版没有这个总入口,所以你需要自己调用 APP_SystemClockConfig() 把系统时钟配好,否则 LL_mDelay() 的时基是错的。

2. 时钟使能:LL 更“裸”

  • HAL:__HAL_RCC_GPIOA_CLK_ENABLE()(由 LED_GPIO_CLK_ENABLE() 宏展开)。
  • LL:LED_GPIO_CLK_ENABLE() 这个宏本身两版都用,但它的底层就是直接写 RCC 寄存器。关键点:LL 不会在初始化时自动开时钟,忘记开时钟是 LL 初学者最常见的 bug。

3. 引脚配置:结构体 vs 直接函数

  • HAL:HAL_GPIO_Init() 接收 GPIO_InitTypeDef,一次设置 Pin/Mode/Pull/Speed。
  • LL:LL_GPIO_SetPinMode() 单功能函数,一行只做一件事。优点是不需要构造结构体、代码更短;缺点是多个属性要分多行写(不过多数属性复位值就是你要的)。

4. 翻转与延时

  • LL_GPIO_TogglePin()HAL_GPIO_TogglePin() 语义一致,都是读-反-写。
  • LL_mDelay(100) 对应 HAL 的 HAL_Delay(100),都是基于 SysTick 的阻塞毫秒延时。注意 LL 版延时前必须先调 LL_Init1msTick() 设定正确的 tick 频率(在 APP_SystemClockConfig 里已做)。

实验现象

  • 板载 LED(PA11)以约 5Hz(每 100ms 一次)的频率闪烁,比 HAL 版(250ms)更快。
  • 用示波器/逻辑分析仪测量 PA11,可见周期约 200ms 的方波。

常见问题

  1. LED 不闪 / 闪得很怪?

    • 先确认 APP_SystemClockConfig()LL_Init1msTick() 的参数和 LL_SetSystemCoreClock() 与实际 HSI 频率一致(本例 8MHz)。参数不对,LL_mDelay 时序会整体偏移。
    • 确认 LED 实际引脚与 LED3_PIN 一致。
  2. 编译报 LL_GPIO_xxx 未定义?

    • LL 函数分散在 stm32py32..._ll_gpio.h 等头文件中,确认工程已包含 py32f030xx_ll_Start_Kit.h 以及对应的 LL 驱动头。
  3. 想改成非阻塞闪烁?

    • 同 HAL 版思路,用 TIM 更新中断翻转引脚;LL 里对应 LL_TIM_xxx 系列函数(见 TIM1_PWM_Init 示例)。

小结

  • LL 版 GPIO 输出三步依然是:开时钟 → 设模式 → 翻转/写电平,但全部由 LL_GPIO_* / LL_RCC_* 直接操作完成。
  • 与 HAL 的最大区别:LL 没有 HAL_Init() 这种“一站式”初始化,系统时钟、LL_mDelay 时基都要自己配。
  • 代码更短、编译体积更小、对寄存器行为更透明——这是 LL 的核心价值。

拓展应用

  • LL_mDelay 换成 TIM 中断,做多 LED 不同频率非阻塞闪烁。
  • 配合 LL_GPIO_SetPinOutputType() 把引脚设为开漏(LL_GPIO_OUTPUT_OPENDRAIN),驱动需要线与的总线设备。
  • LL_GPIO_ReadInputPort() 读取按键,实现按键控制 LED(LL 版 GPIO_INPUT 思路同 HAL)。
  • 结合 TIM1_PWM_Init 示例,用 LL_TIM_OC_SetCompareCHx() 做呼吸灯。
世界是你们
使用 Hugo 构建
主题 StackJimmy 设计