GPIO配置问题学习
GPIO知识
问题描述
在SPI通信信号测试中,发现CS片选信号在上电短时间后置高,并迅速异常拉低260ms左右后,进入正常片选状态。
根因分析
由于在boot初始化配置GPIO引脚为上拉推挽输出模式。由于STM32G0芯片会初始化寄存器ODR为0x0000 0000。在推挽输出模式下:ODR=0时:N-MOS导通,P-MOS截止。引脚通过N-MOS强下拉到地,上拉电阻的电流被短路到地,引脚电压≈0V。

解决方法
在初始化配置PE12引脚为上拉推挽输出前,提前写入该位ODR值为1,避免因配置后导致的异常拉低问题。
知识点
1. 上拉电阻的物理位置和作用
物理位置:
外部引脚──┬──内部上拉电阻(约40kΩ)──VDD
│
├──P-MOS(推挽上拉)
│
├──N-MOS(推挽下拉)
│
└──到输入缓冲器
不同模式下的作用:
| GPIO模式 | 上拉电阻的作用 |
|---|---|
| 输入模式 | 在没有外部驱动时,将引脚拉到默认高电平 |
| 开漏输出模式 | 当ODR=1(MOS管截止)时,将引脚拉到高电平 |
| 推挽输出模式 | 几乎不起作用(被MOS管短路) |
2. 推挽输出模式的工作原理
当配置为推挽输出时:
-
ODR=0:N-MOS导通,P-MOS截止
引脚→低阻抗N-MOS→GND 上拉电阻电流:VDD→上拉电阻→N-MOS→GND(浪费电能) -
ODR=1:P-MOS导通,N-MOS截止
引脚→低阻抗P-MOS→VDD 上拉电阻并联,但P-MOS阻抗更低
3. 为什么上拉感觉没效果?
根本原因:驱动强度差异
假设:
- 内部上拉电阻:Rpullup = 40kΩ
- N-MOS导通电阻:Rds_on = 50Ω
- VDD = 3.3V
当ODR=0时:
上拉试图建立电压:Vpull = VDD × [Rds_on/(Rpullup+Rds_on)]
≈ 3.3V × [50Ω/(40000Ω+50Ω)]
≈ 0.0041V ≈ 4.1mV(远低于逻辑低电平阈值)
数值对比:
| 参数 | 内部上拉 | N-MOS下拉 |
|---|---|---|
| 电阻值 | 40kΩ | 50Ω |
| 驱动能力 | 弱(~0.08mA) | 强(可驱动数十mA) |
| 电压建立 | 几乎被完全压制 | 主导引脚电压 |
正确的配置思维:
// 对于需要初始高电平的SPI CS引脚:
// 1. 配置上拉(作为安全备份)
// 2. 必须显式设置ODR=1
void Correct_SPI_CS_Init(void)
{
// 步骤1:先写ODR(确保输出为高)
GPIOE->ODR |= GPIO_PIN_12;
// 步骤2:配置为推挽输出+上拉
GPIOE->MODER = (GPIOE->MODER & ~GPIO_MODER_MODE12_Msk)
| (0x01 << GPIO_MODER_MODE12_Pos);
GPIOE->OTYPER &= ~GPIO_OTYPER_OT12; // 推挽
GPIOE->PUPDR = (GPIOE->PUPDR & ~GPIO_PUPDR_PUPD12_Msk)
| (0x01 << GPIO_PUPDR_PUPD12_Pos); // 上拉
// 这样,从开始到结束,引脚始终保持高电平
}
7. 为什么设计上拉在这种模式下仍然存在?
STM32这样设计有几个原因:
原因1:硬件设计一致性
- 每个GPIO引脚都有相同的物理结构
- 上拉/下拉是物理电阻,始终连接到引脚
- 软件只是控制是否启用它们
原因2:模式切换时的连续性
当GPIO从输出模式切换到输入模式时:
// 从输出模式切换为输入模式
GPIOE->MODER &= ~(GPIO_MODER_MODE12_Msk); // 输入模式
// 如果之前配置了上拉,切换后立即生效
// 引脚不会浮空,避免不确定状态
原因3:开漏输出模式的需要
// 开漏输出模式需要上拉
GPIOE->OTYPER |= GPIO_OTYPER_OT12; // 开漏输出
GPIOE->ODR = 1; // MOS管截止,由上拉拉高引脚
8. 实际应用建议
对于SPI CS引脚的最佳实践:
typedef enum {
CS_STATE_SELECTED = 0, // 低电平选中
CS_STATE_DESELECTED = 1 // 高电平不选中
} CS_State_t;
void SPI_CS_GPIO_Init(void)
{
// 1. 启用时钟
__HAL_RCC_GPIOE_CLK_ENABLE();
// 2. 先设置为输入上拉(安全状态)
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
// 3. 然后配置为推挽输出上拉,初始不选中
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 保持上拉配置
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
// 4. 明确设置为高电平
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, CS_STATE_DESELECTED);
}
void SPI_CS_Set(CS_State_t state)
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_12, state);
}
9. 总结
关键理解:
- 上拉电阻在推挽输出模式下存在,但被低阻抗的MOS管"淹没"
- 输出电平由ODR寄存器直接控制,不受上拉影响
- 上拉的主要作用是在输入模式或开漏输出模式下提供默认电平
- 对于推挽输出的SPI CS引脚,必须显式设置ODR=1才能获得高电平
上拉配置 ≠ 输出高电平。上拉是"弱"配置,ODR是"强"控制。在推挽输出模式下,ODR的优先级远高于上拉配置。
浙公网安备 33010602011771号