GPIO

FAQ1 为什么配置了pin脚不生效?

当发现配置的 pin 脚 pinmux 没有生效时,可以检查 HAL_PIN_Set() 的参数是否正确:

int HAL_PIN_Set(int pad, pin_function func, int flags, int hcpu)

例如:PA28 对应的是 GPIO_A28,复制粘贴后如果忘记修改,就会导致配置错误。

../../_images/hal_gpio_config_err.png
  • flags:内部上下拉状态。配置原则可参考 低功耗指南第 3 节:配置上下拉

  • hcpu:用于区分大小核。大核的 PA 为 1,小核的 PB 为 0。实际使用中,常见问题是复制代码后忘记修改该参数,导致配置不生效。


FAQ2 为什么 finsh 命令 pin mode 配置上下拉无效?

现象描述: 计划通过 finsh 命令 pin mode 50 2 将 PA50 配置为 GPIO 输入上拉。 从下表枚举值来看,2 对应输入上拉类型:

#define PIN_MODE_OUTPUT         0x00
#define PIN_MODE_INPUT          0x01
#define PIN_MODE_INPUT_PULLUP   0x02
#define PIN_MODE_INPUT_PULLDOWN 0x03
#define PIN_MODE_OUTPUT_OD      0x04

通过 pin status 50 检查效果时,实际上 PA50 并没有变为上拉。 原因分析: finsh 命令 pin mode 对应的函数原型为 cmd_pincmd_pin 处理 mode 参数的代码如下:

if (strcmp(argv[1], "mode") == 0)
{
    struct rt_device_pin_mode m;
    m.pin = atoi(argv[2]);
    m.mode = atoi(argv[3]);
    rt_device_control(device, 0, &m);
    ......
}

检查调用层级:rt_device_control() -> sifli_pin_mode() -> HAL_GPIO_Init()HAL_GPIO_Init() 只配置 hwp_gpiox 寄存器功能,不具备配置 pinmux 的能力。 上下拉配置是通过修改 hwp_pinmux 寄存器实现的。 解决方案: 正确修改上下拉的方式必须通过 HAL_PIN_Set(int pad, pin_function func, int flags, int hcpu)。 例如:

// 配置 PA50 为上拉
HAL_PIN_Set(PAD_PA50, GPIO_A50, PIN_PULLUP, 1);

FAQ3 为什么在 bsp_pinmux.c 中将 PA43 配置为 GPIO,实际通过 pin status 查看却变成了 LCDC?

现象描述:bsp_pinmux.c 中将 PA43 配置为 GPIO 并输出高电平,但实际上电平却为低。 通过 finsh 命令 pin status 43 检查后,发现 PA43 被配置成了 LCDC。 原因分析: 通过检查原理图可知,PA43 被用于 LCDC 控制,如下图所示:

../../_images/gpio_faq3_sch_PA43.png

LCDC 驱动会在开机和唤醒时调用相关代码配置 pinmux。

../../_images/gpio_faq3_lcd_pinmux.png

解决方案: 建议根据原理图分配合适的 GPIO。对于已经被其他功能模块占用的 pin,尽量不要复用。


FAQ4 PBR0 作为电源控制 pin 时,为什么必须被配置为 force1

原因分析: PBR 作为低功耗 IO,常用于低功耗场景,例如睡眠和关机后继续输出 PWM 波形等。 PBR0 相比其他 PBR,多了一个特殊功能 force1,即硬件强制 PBR0 输出高电平。 force1 一般用于电源控制。 特别是当 PBR0 作为 PSRAM 电源控制 pin 时,如果没有配置为 force1,在睡眠唤醒过程中可能会出现瞬间低电平,导致 PSRAM 存储数据异常。对于 NAND 方案,由于代码也放在 PSRAM 中,因此可能出现代码跑飞的问题。 56X 一般会使用 PBR0 控制 PSRAM 电源,如下图所示:

../../_images/gpio_faq4_pbr0_psram.png

解决方案: 一般在 PBR0 作为电源控制 pin 使用时,都会在 bsp_pinmux.c 中让 PBR0 进入 force1 状态,方法如下:

MODIFY_REG(hwp_rtc->PBR0R, RTC_PBR0R_SEL_Msk, MAKE_REG_VAL(0, RTC_PBR0R_SEL_Msk, RTC_PBR0R_SEL_Pos));       // 恢复PBR0 功能为 0
HAL_PBR0_FORCE1_ENABLE();   // 使能 PBR0 force1

如果想让 PBR0 退出 force1,可以参考如下方法:

HAL_PBR0_FORCE1_DISABLE();      // 禁止 PBR0 force1
MODIFY_REG(hwp_rtc->PBR0R, RTC_PBR0R_SEL_Msk, MAKE_REG_VAL(1, RTC_PBR0R_SEL_Msk, RTC_PBR0R_SEL_Pos));            // 切换PBR0 功能为 GPIO
HAL_PBR_ConfigMode(0, 1);       // 配置 PBR0 为输出模式
HAL_PBR_WritePin(0, 0);         // 配置 PBR0 输出0

FAQ5 如何睡眠后PBR1继续输出PWM波形?

PBR支持PBR_CLK_LP的功能,支持睡眠后持续输出LP的波形。
LP的波形和LXT_DISABLE有关:
LXT_DISABLE 使能, 去掉外部32k晶振,使用内部rc10k, lp 输出的是rc10k波形;
LXT_DISABLE 禁止, 使用外部32k晶振, lp 输出的是32k波形;

../../_images/gpio_faq5_32k_rc10k.png

52x/56x/58x都支持PBR睡眠继续输出LP波形, 但配置方法有些许差异:
52x 配置PBR1 睡眠输出lp
52x的PBR0~PBR3 和 PA24 ~PA27复用pin。
所以,配置PBR1 的同时需要配置PA25.
步骤1: 配置PA25
PA25 配置上下拉为NOPULL,功能配置为模拟;

    HAL_PIN_Set(PAD_PA25, GPIO_A25, PIN_NOPULL, 1);
    HAL_PIN_Set_Analog(PAD_PA25, 1);

步骤2: 配置PBR1
PBR1 配置为功能1,即:PBR_CLK_LP, 输出

    uint32_t val = hwp_rtc->PBR1R;
    val &= ~(RTC_PBR1R_OE_Msk | RTC_PBR1R_SEL_Msk);
    val |= (RTC_PBR1R_OE_Msk | MAKE_REG_VAL(1, RTC_PBR1R_SEL_Msk, RTC_PBR1R_SEL_Pos));
    hwp_rtc->PBR1R = val;

由于验证时,LXT_DISABLE 使能,查看效果输出的频率在9k左右;

../../_images/gpio_faq5_pbr1_output_rc10k.png

56x/58x 配置PBR1 睡眠输出lp
56x/58x的PBR都是单独的pin,可以直接配置PBR1。

    uint32_t val = hwp_rtc->PBR1R;
    val &= ~(RTC_PBR1R_OE_Msk | RTC_PBR1R_SEL_Msk);
    val |= (RTC_PBR1R_OE_Msk | MAKE_REG_VAL(1, RTC_PBR1R_SEL_Msk, RTC_PBR1R_SEL_Pos));
    hwp_rtc->PBR1R = val;

FAQ6 睡眠怎么用pin脚唤醒系统?

52x: solution方案上52x 待机用的是deep模式,所有GPIO都支持中断唤醒。 配置示例:

    rt_pin_mode(38, PIN_MODE_INPUT);//配置PA38为输入
    rt_pin_attach_irq(38, PIN_IRQ_MODE_FALLING, (void *) wifi_wakeup_hcpu_irq_pin_handle,
                      (void *)(rt_uint32_t) 38);   ////配置PA38,下降沿中断
    rt_pin_irq_enable(38, 1);//使能PA38中断

55x, 56x, 58x系列: 这些系列方案的默认待机模式是Standby模式,普通GPIO无法通过中断唤醒系统。 只有带有 #WKUP 功能的pin脚,使用pm_enable_pin_wakeup函数配置后才能唤醒系统。配置的唤醒中断触发方式必须与GPIO中断的触发方式一致,否则唤醒后无法触发普通的GPIO中断处理函数。 配置示例:

// WIFI_WAKEUP_HCPU_PIN 为38,对应PA38脚
rt_pin_mode(WIFI_WAKEUP_HCPU_PIN, PIN_MODE_INPUT);  

// 查询该GPIO对应的唤醒源引脚号
GPIO_TypeDef *gpio = GET_GPIO_INSTANCE(WIFI_WAKEUP_HCPU_PIN);
uint16_t gpio_pin = GET_GPIOx_PIN(WIFI_WAKEUP_HCPU_PIN);
int8_t wakeup_pin;
if (WIFI_WAKEUP_HCPU_PIN > 96)   // 大于96的是PB脚
    wakeup_pin = HAL_LPAON_QueryWakeupPin(gpio, gpio_pin);
else
    wakeup_pin = HAL_HPAON_QueryWakeupPin(gpio, gpio_pin);
RT_ASSERT(wakeup_pin >= 0);

// 使能该引脚的唤醒功能,触发方式为下降沿
pm_enable_pin_wakeup(wakeup_pin, AON_PIN_MODE_NEG_EDGE); 

// 配置并使能普通GPIO中断(触发方式需与唤醒方式一致)
rt_pin_attach_irq(WIFI_WAKEUP_HCPU_PIN, PIN_IRQ_MODE_FALLING, (void *) wifi_wakeup_hcpu_irq_pin_handle,
                  (void *)(rt_uint32_t) WIFI_WAKEUP_HCPU_PIN);
rt_pin_irq_enable(WIFI_WAKEUP_HCPU_PIN, 1);


FAQ7 唤醒系统后怎么确认是不是我配置的pin脚?

开启pm_debug宏后,UART日志中会出现[pm]相关的打印信息。

  • [pm]S: // 系统进入睡眠时会打印此信息。

  • [pm]W: // 系统唤醒时会打印此信息。

  • [pm]WSR: // 系统唤醒原因,其后的16进制值需要参考hpsys_aon.hlpsys_aon.h中定义的WSR寄存器位。

例如:

[pm]WSR: 0x4

在52x系列芯片上,0x4对应定时器唤醒,对应WSR寄存器的bit位为100(即bit 2)。

// 在 hpsys_aon.h 中的定义
#define HPSYS_AON_WSR_LPTIM1_Pos        (2U)
#define HPSYS_AON_WSR_LPTIM1_Msk        (0x1UL << HPSYS_AON_WSR_LPTIM1_Pos)
#define HPSYS_AON_WSR_LPTIM1            HPSYS_AON_WSR_LPTIM1_Msk