GPIO与LED模块
目录
1、GPIO基础知识
GPIO(通用输入/输出口)是微控制器上可以由用户自定义控制的引脚。对于嵌入式开发初学者来说,理解GPIO的几个关键概念至关重要:
1.GPIO模式配置
模式配置 | 功能 | 适用场景 |
---|---|---|
输入模式 | 将引脚配置为输入模式,用于读取外部信号 | 读取按钮状态、传感器信号等 |
输出模式 | 将引脚配置为输出模式,用于驱动外部设备或信号 | LED控制、电机驱动等 |
模拟模式 | 将引脚配置为模拟模式,用于模拟信号输入或输出 | ADC读取、DAC输出等 |
备用功能/复用功能 | 将引脚配置为某个外设的备用功能,如UART、SPI或I2C等 | 通信接口、定时器输出等 |
关于复用功能:
在HAl库中,复用功能可以直接用cubeMAX查看
而在标准库中,则需要在用户手册或者宏定义中查看。在代码我们配置哪一个宏定义,就使用哪一个功能,这是标准库开发中问我们需要做的.
那么在cubeMAX中我们应该怎么做呢?
2.输出类型
推挽输出模式 (Push-Pull) | 开漏输出模式 (Open-Drain) | |
---|---|---|
功能 | 可以驱动高电平(逻辑1)和低电平(逻辑0) | 只能拉低电平(逻辑0),高电平由外部上拉电阻提供 |
工作原理 | 有两个晶体管配置,一个用于拉高电平,一个用于拉低电平 | 只有一个下拉晶体管,高电平依赖外部上拉电阻 |
优点 | 驱动能力强,适用于大多数数字输出应用 | 支持多路信号线"线与"逻辑,适合共享总线 |
缺点 | 高频切换时可能导致较大功耗和信号干扰 | 无法直接驱动高电平,需要外部电阻 |
应用示例 | LED控制、开关控制、信号输出 | I2C总线、外部信号线共享 |
推挽输出和开漏输出的本质区别就在于电路图上的P-MOS(开漏输出的P-MOS永远截止)
推挽输出电路原理图
开漏输出原理图
另外开漏输出具有”线与“特性,但凡有一个引脚输出低电平,所有的引脚电压都会被拉低
既然stm32能够配置输出模式,那么传统的 51 单片机可以这样配置吗?为什么?
不完全可以,因为结构不同:传统的 8051 单片机的IO口结构与 STM32 不同,通常被称为准双向口(Quasi-bidirectional Port)。
51 的IO口结构(以 P1,P2,P3 为例,P0 有些特殊):
- 内部有一个上拉电阻: 每个引脚内部连接了一个弱的上拉电阻到 VDD。
- 一个下拉晶体管: 有一个连接到 GND 的 N-MOS 晶体管,用于将引脚拉低
- 工作方式:
- 输出低电平(0):下拉 N-MOS 导通,将引脚拉低到 GND。此时,内部的上拉电阻也被这个 N-MOS 短路了。
- 输出高电平(1): 下拉 N-MOS 关闭。引脚通过 内部的上拉电阻 被拉高到 VDD。
所以我们只能简单地控制输入输出。
3.输出速度
分别有:低速、中速、高速、超高速
速度设置的影响
更高的速度会导致:
- 更多的功耗
- 更大的电磁干扰(EMI)
- 更快的信号上升和下降时间
对于LED控制这类应用,通常选择低速即可,这能降低功耗并减少EMI。
4. STM32 HAL库GPIO配置
STM32 HAL(Hardware Abstraction Layer,硬件抽象层)库提供了一套完整的API来配置和控制GPIO引脚,通过我们的cubeMAX来控制。相比直接操作寄存器,HAL库更易用且可移植性更好。
首先我们先打开一个工程中的cubeMAX,我们会发现其中的芯片引脚是可以配置输出模式的。(以PCB8为例)
对于初学者来说不知道这些引脚在哪里怎么办呢,事实上这些引脚就在原理图中,我们并不需要知道原理图中的电路是怎么构成的,我们只需要知道这些引脚在哪里,我们需要用那个IO口来控制它就够了。
那么我们以今天要学习的LED模块为例,打开我们的西门子原理图可以看到LED是共阴极的,而在主控芯片上,我们会发现找不到LED的引脚,这是因为官方的板子对于LED模块是需要我们用杜邦线来进行飞线的,而不是固定在某个端口上。
板子实物图如下,我们需要将17的LED部分连到8的MCU I/O口引出部分,而具体连到哪个引脚就看我们自己连接自己去配置
那么接下来以连接到PG2-PG7为例
上面6个选项分别为:初始电平、GPIO模式、上下拉、输出速度、用户标签
对于我们配置LED模块来说,初始电平默认为低电平,GPIO模式为输出模式,上下拉不启用,用户标签我们可以分别定义为LED1-6
配置完了之后创建工程,打开我们的工程
打开我们GPIO初始化函数,可以看到定义了一个结构体和初始化。
再打开结构体的定义,就可以看到我们刚才在cubeMAX里面配置的那5项,由cubeMAX帮我们生成了相关代码
接着我们介绍一下GPIO初始化函数中的内容。
除了我们刚才讲过的结构体定义和初始化,接下来的几句代码分别是:
-
/* GPIO Ports Clock Enable */使能所使用的GPIO引脚的时钟
-
/*Configure GPlo pin output Level */配置 GPIO 引脚输出电平 其中写入GPIO_PIN_RESET(为0)默认LED初始熄灭
-
/*Configure GPIO pins : PGPin PGPin PGPin PGPinPGPin PGPin */配置 GPIO 引脚
使用结构体访问其中成员,进行配置。配置完后写入GPIO的结构体。
这就是我们GPIO初始化的内容,这也是我们需要关注的,而不是掌握具体函数是如何实现的,是如何连接到硬件上的。
STM32 HAL GPIO配置实际应用
为LED控制配置GPIO的基本流程:
1首先定义GPIO配置结构体并初始化为默认值
2使能对应GPIO端口的时钟
3配置引脚模式为推挽输出(对LED最适合)
4设置上拉/下拉配置(LED控制通常不需要)
5设置输出速度(低速即可)
6应用配置到指定的GPIO端口和引脚
2、LED控制原理
在介绍LED控制原理之前,我们先讲一个小插曲,在GPIO配置选项中,有一个上下拉电阻的选项,而在西门子原理图我们也可以看见LED模块中有着上拉电阻。
那么什么是上下拉电阻呢?
上拉电阻和下拉电阻是GPIO配置中非常重要的概念。它们用于确保GPIO引脚在未连接外部设备时的状态,以及在连接外部设备时的状态。
无上下拉电阻 | 上拉电阻 | 下拉电阻 | |
---|---|---|---|
定义 | 引脚不连接内部上下拉电阻,适用于浮空或外部电路自带上下拉电阻的情况。 | 内部连接一个电阻到VCC,当引脚未被驱动时拉高到逻辑高电平。 | 内部连接一个电阻到地(GND),当引脚未被驱动时拉低到逻辑低电平。 |
适应场景 | 确保引脚默认为高电平 常用于按键输入(按键按下接地) 适用于需要默认高电平的场合 | 确保引脚默认为低电平 常用于需要默认低状态的场合 适用于低功耗设计(低电平通常功耗更低) | |
注意:引脚可能处于浮空状态,容易受到外部干扰。 |
那么为什么需要在西门子原理图中有了上拉电阻,可以去掉它们吗?
答案是否定的。GD32中的LED是共阴极,高电平点亮。那么就表明我们需要输出一定的电压来保证其点亮,而上拉电阻就确保GPIO能提供足够的源电流(通常20mA左右)。假如去掉上拉电阻就可能导致我们的输出电压不足,另外这个上拉电阻还能起到保护电路的作用,直接连接LED到GPIO引脚时可能烧毁LED或损坏MCU。
STM32的上下拉电阻
在STM32微控制器中,每个GPIO引脚都有内置的上拉和下拉电阻,可以通过软件配置:
- 上拉电阻值:典型值约为40KΩ (STM32F4系列)
- 下拉电阻值:典型值约为40KΩ (STM32F4系列)
- HAL配置选项:GPIO_NOPULL、GPIO_PULLUP、GPIO_PULLDOWN
注意:内部上下拉电阻值较大,驱动能力有限。对于需要更强驱动能力的场合,建议使用外部上下拉电阻。
1.LED控制的基本方式
对于普通的LED控制,我们通常使用GPIO的输出模式,并且选择推挽输出类型。这种配置可以提供足够的驱动能力来点亮LED。
GPIO模式:输出模式
输出类型:推挽输出
输出速度:低速即可
上下拉通常不需要
2.LED连接方式
高电平点亮方式和低电平点亮方式,具体看原理图来判断是哪种方式。
3.STM32 HAL库中的LED控制实现
使用HAL库控制LED的常用函数:
1HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
用于设置GPIO引脚输出状态,可以点亮或熄灭LED
2HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
用于切换GPIO引脚状态,实现LED闪烁效果
3HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
读取GPIO引脚当前状态,可用于获取LED当前状态
4.LED控制代码示例
#include "led_app.h"
#include "gpio.h" // 确保包含了HAL库的GPIO头文件
uint8_t ucLed[6] = {1,0,1,0,1,1}; // LED 状态数组 (6个LED)
/**
* @brief 根据ucLed数组状态更新6个LED的显示
* @param ucLed Led数据储存数组 (大小为6)
*/
void led_disp(uint8_t *ucLed)
{
uint8_t temp = 0x00; // 用于记录当前 LED 状态的临时变量 (最低6位有效)
static uint8_t temp_old = 0xff; // 记录之前 LED 状态的变量, 用于判断是否需要更新显示
for (int i = 0; i < 6; i++) // 遍历6个LED的状态
{
// 将LED状态整合到temp变量中,方便后续比较
if (ucLed[i]) temp |= (1 << i); // 如果ucLed[i]为1, 则将temp的第i位置1
}
// 仅当当前状态与之前状态不同的时候,才更新显示
if (temp != temp_old)
{
// 使用HAL库函数根据temp的值设置对应引脚状态 (假设高电平点亮)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, (temp & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 0 (PB12)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, (temp & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 1 (PB13)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (temp & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 2 (PB14)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, (temp & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 3 (PB15)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, (temp & 0x10) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 4 (PD8)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_9, (temp & 0x20) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 5 (PD9)
temp_old = temp; // 更新记录的旧状态
}
}
/**
* @brief LED 显示处理函数 (主循环调用)
*/
void led_proc(void)
{
led_disp(ucLed); // 调用led_disp函数更新LED状态
}
常见问题
为什么我的LED不亮?
- GPIO配置问题:检查GPIO配置是否正确(输出模式、推挽输出)
- 时钟使能:确认对应GPIO端口的时钟已使能
- 电平逻辑:检查LED极性是否匹配代码逻辑(高电平点亮还是低电平点亮)
- 硬件连接:检查限流电阻值是否合适,LED是否连接正确
oid led_proc(void)
{
led_disp(ucLed); // 调用led_disp函数更新LED状态
}
### 常见问题
为什么我的LED不亮?
- **GPIO配置问题**:检查GPIO配置是否正确(输出模式、推挽输出)
- **时钟使能**:确认对应GPIO端口的时钟已使能
- **电平逻辑**:检查LED极性是否匹配代码逻辑(高电平点亮还是低电平点亮)
- **硬件连接**:检查限流电阻值是否合适,LED是否连接正确
- **电源问题**:确认微控制器供电电压是否正常