GPIO(General Purpose Input/Output,通用输入/输出)是微控制器最基本、最常用的外设功能。本章将以点亮控制板上的LED灯为切入点,系统学习CH582F的GPIO工作原理、寄存器配置和编程方法。
3.1 GPIO工作原理
3.1.1 什么是GPIO
GPIO是微控制器上可以软件配置为输入或输出模式的数字信号引脚。作为输出时,GPIO引脚可以输出高电平(通常为VCC,如3.3V)或低电平(0V);作为输入时,GPIO引脚可以读取外部施加的电平信号。
GPIO的应用非常广泛,包括但不限于:
(1)控制LED的亮灭——输出模式。
(2)读取按键状态——输入模式。
(3)驱动蜂鸣器——输出模式,产生方波。
(4)与外部芯片通信——复用为UART/SPI/I2C等功能。
3.1.2 GPIO输出模式
GPIO作为输出使用时,主要有两种驱动方式:
推挽输出(Push-Pull Output)
推挽输出由两个MOSFET(一个P-MOS和一个N-MOS)组成。输出高电平时P-MOS导通,输出低电平时N-MOS导通。推挽输出的优点是驱动能力强,可以直接驱动LED等负载。CH582F的GPIO推挽输出可提供最大20mA的驱动电流。
开漏输出(Open-Drain Output)
开漏输出只有N-MOS管,只能输出低电平,高电平需要外接上拉电阻。开漏输出常用于I2C总线等需要"线与"逻辑的场合。
3.1.3 GPIO输入模式
GPIO作为输入使用时,有以下几种配置方式:
浮空输入(Floating Input)
引脚内部既不接上拉电阻也不接下拉电阻,电平完全由外部电路决定。浮空输入模式下引脚容易受到干扰,一般不推荐在按键等应用中使用。
上拉输入(Pull-Up Input)
引脚内部连接一个上拉电阻到VCC。无外部信号时,引脚读取为高电平;当外部将引脚拉低时,读取为低电平。上拉输入常用于按键检测(按键一端接地,另一端接GPIO)。
下拉输入(Pull-Down Input)
引脚内部连接一个下拉电阻到GND。无外部信号时,引脚读取为低电平。适用于按键接VCC的场景。
3.2 CH582F的GPIO寄存器详解
CH582F有两组GPIO端口:GPIOA(PA0PA15,共16个引脚)和GPIOB(PB0PB15,共16个引脚)。每组GPIO都有若干控制寄存器,用于配置引脚的工作模式和读写引脚状态。
3.2.1 方向寄存器(DIR)
方向寄存器用于设置每个GPIO引脚是作为输入还是输出。
寄存器名称:R32_GPIOA_DIR(GPIOA方向寄存器),R32_GPIOB_DIR(GPIOB方向寄存器)。
位宽:每个位对应一个引脚。0 = 输入模式,1 = 输出模式。
| 寄存器 | 地址偏移 | 位域 | 说明 |
|---|---|---|---|
| R32_GPIOA_DIR | GPIOA基址+0x00 | bit[15:0] | PA15~PA0方向: 0=输入, 1=输出 |
| R32_GPIOB_DIR | GPIOB基址+0x00 | bit[15:0] | PB15~PB0方向: 0=输入, 1=输出 |
3.2.2 输出数据寄存器
输出数据寄存器用于设置GPIO引脚的输出电平。
| 操作 | 寄存器/操作 | 说明 |
|---|---|---|
| 置位输出(高电平) | R32_GPIOA_SET / GPIOA_SetBits() | 写1的位对应引脚输出高电平 |
| 清零输出(低电平) | R32_GPIOA_CLR / GPIOA_ResetBits() | 写1的位对应引脚输出低电平 |
| 翻转输出 | R32_GPIOA_OUT ^ mask | 异或操作翻转指定位 |
3.2.3 输入数据寄存器
输入数据寄存器用于读取GPIO引脚当前的电平状态。
寄存器名称:R32_GPIOA_IN(读取GPIOA输入),R32_GPIOB_IN(读取GPIOB输入)。
读取方式:直接读取寄存器值,对应位为1表示高电平,为0表示低电平。
3.2.4 上拉/下拉配置寄存器
CH582F的GPIO支持内部上拉和下拉电阻的配置:
寄存器名称:R32_GPIOA_PU(GPIOA上拉使能),R32_GPIOA_PD(GPIOA下拉使能)。
位操作:写1使能对应引脚的上拉/下拉电阻,写0禁用。
3.2.5 功能复用寄存器
CH582F的许多GPIO引脚具有复用功能(如UART TX/RX、SPI MOSI/MISO、PWM输出等)。功能复用通过以下寄存器选择:
| 寄存器 | 说明 | 取值 |
|---|---|---|
| R32_GPIOA_FUN_MODE | GPIOA引脚功能选择 | 00=GPIO, 01=功能1, 10=功能2, 11=功能3 |
| R32_GPIOB_FUN_MODE | GPIOB引脚功能选择 | 00=GPIO, 01=功能1, 10=功能2, 11=功能3 |
ℹ️**** 注意使用GPIO功能时,需要确保引脚的功能复用寄存器设置为GPIO模式(00)。如果引脚被配置为其他功能(如UART TX),则GPIO输出操作将无效。
3.2.6 SDK API函数总结
CH582F的SDK提供了一系列封装好的GPIO操作函数,简化了寄存器操作:
| 函数名称 | 功能描述 | 示例调用 |
|---|---|---|
| GPIOA_ModeCfg(pins, mode) | 配置GPIOA引脚工作模式 | GPIOA_ModeCfg(GPIO_Pin_12, GPIO_ModeOut_PP_20mA) |
| GPIOB_ModeCfg(pins, mode) | 配置GPIOB引脚工作模式 | GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU) |
| GPIOA_SetBits(pins) | GPIOA指定引脚输出高电平 | GPIOA_SetBits(GPIO_Pin_12) |
| GPIOA_ResetBits(pins) | GPIOA指定引脚输出低电平 | GPIOA_ResetBits(GPIO_Pin_12) |
| GPIOA_InvertBits(pins) | GPIOA指定引脚输出翻转 | GPIOA_InvertBits(GPIO_Pin_12) |
| GPIOA_ReadPortPin(pins) | 读取GPIOA指定引脚电平 | val = GPIOA_ReadPortPin(GPIO_Pin_0) |
| GPIOB_SetBits(pins) | GPIOB指定引脚输出高电平 | GPIOB_SetBits(GPIO_Pin_10) |
| GPIOB_ResetBits(pins) | GPIOB指定引脚输出低电平 | GPIOB_ResetBits(GPIO_Pin_10) |
| GPIOB_ReadPortPin(pins) | 读取GPIOB指定引脚电平 | val = GPIOB_ReadPortPin(GPIO_Pin_4) |
GPIO模式常量:
| 模式常量名 | 含义 |
|---|---|
| GPIO_ModeIN_Floating | 浮空输入(无上下拉) |
| GPIO_ModeIN_PU | 上拉输入(内部上拉电阻) |
| GPIO_ModeIN_PD | 下拉输入(内部下拉电阻) |
| GPIO_ModeOut_PP_20mA | 推挽输出,最大20mA驱动 |
| GPIO_ModeOut_PP_5mA | 推挽输出,最大5mA驱动 |
| GPIO_ModeOut_OD_20mA | 开漏输出,最大20mA |
3.3 电路原理分析
3.3.1 LED驱动电路
本控制板上的4个LED指示灯采用以下驱动方式:
GPIO引脚 → 限流电阻(约1kΩ) → LED阳极 → LED阴极 → GND
这是一种"高电平驱动"(Active High)的接法:当GPIO输出高电平时,LED两端产生正向压降,电流从GPIO经限流电阻和LED流向GND,LED点亮;当GPIO输出低电平时,LED两端无压差,LED熄灭。
限流电阻的计算:
假设LED正向压降VF = 2.0V(红色LED),GPIO输出电压VOH = 3.3V,期望LED电流IF = 5mA:
R = (VOH - VF) / IF = (3.3V - 2.0V) / 5mA = 260Ω
实际电路中选用1kΩ电阻,LED电流约1.3mA,亮度适中且更加节能。
3.3.2 LED对应的GPIO引脚
根据1.3.5节的引脚分配表,四个LED对应的GPIO引脚为:
| LED编号 | 位置 | GPIO引脚 | 控制方式 |
|---|---|---|---|
| LED1 | 前左 | PB15 | 高电平点亮 |
| LED2 | 前右 | PA11 | 高电平点亮 |
| LED3 | 后左 | PA14 | 高电平点亮 |
| LED4 | 后右 | PB4 | 高电平点亮 |
ℹ️**** 注意在后续代码中,我们将使用这四个LED进行各种灯光效果的演示。掌握LED的控制是后续飞控状态指示功能的基础。
3.4 代码实现:LED闪烁
下面我们将实现第一个完整的嵌入式程序——让控制板上的一个LED以固定频率闪烁。这是嵌入式开发中的"Hello World"程序。
3.4.1 方法一:使用SDK API函数(推荐)
SDK提供了封装好的GPIO操作函数,使用简单且代码可读性好。
/**
* LED闪烁实验 —— 使用SDK API
* 目标:让LED1(PB15)以500ms间隔交替亮灭
* 硬件:LED1阳极经1kΩ电阻接PA4,阴极接GND
*/
#include “CH58x_common.h”
int main(void)
{
/* 第一步:配置系统时钟为PLL 60MHz */
SetSysClock(CLK_SOURCE_PLL_60MHz);
/* 第二步:配置PA4为推挽输出模式,驱动能力20mA */
/* GPIO_Pin_4 是SDK预定义的宏,值为 (1<<4) = 0x0010 */
GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeOut_PP_20mA);
/* 第三步:初始状态——关闭LED(输出低电平) */
GPIOA_ResetBits(GPIO_Pin_4);
/* 第四步:进入主循环,不断翻转LED状态 */
while(1)
{
/* 翻转PA4引脚的输出电平 */
GPIOA_InvertBits(GPIO_Pin_4);
/* 延时500毫秒 */
DelayMs(500);
}
}
代码逐行解析:
第1行:#include “CH58x_common.h” 引入CH58x系列所有公共头文件,包括GPIO、时钟、延时等函数声明。
第8行:SetSysClock() 函数配置系统主时钟。CLK_SOURCE_PLL_60MHz 表示使用PLL倍频到60MHz。时钟配置必须在所有外设操作之前完成。
第12行:GPIOA_ModeCfg() 函数配置指定引脚的工作模式。第一个参数GPIO_Pin_4指定要配置的引脚(可以是多个引脚的"或"组合),第二个参数GPIO_ModeOut_PP_20mA表示推挽输出、20mA驱动能力。
第15行:GPIOA_ResetBits() 将PA4引脚输出设置为低电平(0V),LED初始状态为关闭。
第21行:GPIOA_InvertBits() 翻转PA4的当前输出电平——如果当前是高电平则变为低电平,反之亦然。
第24行:DelayMs(500) 软件延时500毫秒。该函数在SDK中实现,通过循环计数产生近似延时。
3.4.2 方法二:直接操作寄存器
为了深入理解GPIO的底层工作原理,下面演示如何通过直接读写寄存器来控制LED。
/**
* LED闪烁实验 —— 直接寄存器操作
* 通过直接读写GPIO寄存器来控制PA4
*/
#include “CH58x_common.h”
/* 定义PA4的位掩码 */
#define LED1_PIN (1 << 4) /* PA4对应bit4 */
int main(void)
{
SetSysClock(CLK_SOURCE_PLL_60MHz);
/* 配置PA4为输出模式:设置DIR寄存器bit4为1 */
R32_GPIOA_DIR |= LED1_PIN;
/* 初始输出低电平:使用CLR寄存器清零bit4 */
R32_GPIOA_CLR = LED1_PIN;
while(1)
{
/* 输出高电平,LED点亮 */
R32_GPIOA_SET = LED1_PIN;
DelayMs(500);
/* 输出低电平,LED熄灭 */
R32_GPIOA_CLR = LED1_PIN;
DelayMs(500);
}
}
寄存器操作解析:
R32_GPIOA_DIR |= LED1_PIN:将方向寄存器的bit4置1,设置PA4为输出模式。使用"或"操作可以只修改目标位而不影响其他引脚的配置。
R32_GPIOA_SET = LED1_PIN:SET寄存器是"写1置位"类型,向某位写1会将对应引脚的输出设为高电平,写0无效果。因此可以直接赋值,不需要"或"操作。
R32_GPIOA_CLR = LED1_PIN:CLR寄存器是"写1清零"类型,向某位写1会将对应引脚的输出设为低电平。
编程建议在实际项目中推荐使用SDK API函数进行GPIO操作,代码可读性更好、可移植性更强。寄存器直接操作适用于对性能有极致要求的场合,或者在调试底层问题时使用。
3.4.3 同时控制多个LED
SDK API支持同时配置和操作多个引脚,只需将引脚常量进行"或"运算:
/* 同时配置4个LED引脚为输出模式 */
GPIOA_ModeCfg(GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_12 | GPIO_Pin_13,
GPIO_ModeOut_PP_20mA);
/* 同时点亮4个LED */
GPIOA_SetBits(GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_12 | GPIO_Pin_13);
/* 同时熄灭4个LED */
GPIOA_ResetBits(GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_12 | GPIO_Pin_13);
/* 同时翻转4个LED */
GPIOA_InvertBits(GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_12 | GPIO_Pin_13);
3.5 实验与练习
实验一:不同频率闪烁
修改LED闪烁程序,让LED以不同的频率闪烁,观察并记录效果:
(1)快速闪烁:亮灭各100ms。
(2)中速闪烁:亮灭各500ms(示例代码中的默认频率)。
(3)慢速闪烁:亮灭各1000ms(1秒)。
(4)不均匀闪烁:亮200ms、灭800ms(类似心跳灯效果)。
/* 实验一参考代码:不均匀闪烁(心跳灯效果) */
while(1)
{
GPIOA_SetBits(GPIO_Pin_4); /* LED亮 */
DelayMs(200); /* 亮200ms */
GPIOA_ResetBits(GPIO_Pin_4); /* LED灭 */
DelayMs(800); /* 灭800ms */
}
实验二:交替闪烁
编写程序让LED1(PA4)和LED2(PA5)交替闪烁:当LED1亮时LED2灭,当LED1灭时LED2亮。
/* 实验二参考代码:LED1和LED2交替闪烁 */
#include"CH58x_common.h"intmain(void){GPIOA_ModeCfg(GPIO_Pin_11,GPIO_ModeOut_PP_5mA);// PA11---RLED_R;GPIOB_ModeCfg(GPIO_Pin_15,GPIO_ModeOut_PP_5mA);// PB15---RLED_L;GPIOA_ModeCfg(GPIO_Pin_14,GPIO_ModeOut_PP_5mA);// PA14---GLED_L;GPIOB_ModeCfg(GPIO_Pin_4,GPIO_ModeOut_PP_5mA);// PB4----GLED_R;GPIOB_SetBits(GPIO_Pin_4|GPIO_Pin_15);GPIOA_SetBits(GPIO_Pin_11|GPIO_Pin_14);while(1){GPIOB_ResetBits(GPIO_Pin_15);DelayMs(30);GPIOB_SetBits(GPIO_Pin_15);GPIOA_ResetBits(GPIO_Pin_11);DelayMs(30);GPIOA_SetBits(GPIO_Pin_11);}}实验三:四灯依次点亮
编写程序让4个LED按照LED1→LED2→LED3→LED4的顺序依次点亮,每次只有一个LED亮,间隔300ms,形成"流水灯"效果的雏形。
#include"CH58x_common.h"intmain(void){GPIOA_ModeCfg(GPIO_Pin_11,GPIO_ModeOut_PP_5mA);// PA11---RLED_R;GPIOB_ModeCfg(GPIO_Pin_15,GPIO_ModeOut_PP_5mA);// PB15---RLED_L;GPIOA_ModeCfg(GPIO_Pin_14,GPIO_ModeOut_PP_5mA);// PA14---GLED_L;GPIOB_ModeCfg(GPIO_Pin_4,GPIO_ModeOut_PP_5mA);// PB4----GLED_R;GPIOB_SetBits(GPIO_Pin_4|GPIO_Pin_15);GPIOA_SetBits(GPIO_Pin_11|GPIO_Pin_14);GPIOB_SetBits(GPIO_Pin_4|GPIO_Pin_15);GPIOA_SetBits(GPIO_Pin_11|GPIO_Pin_14);while(1){GPIOB_ResetBits(GPIO_Pin_15);DelayMs(30);GPIOB_SetBits(GPIO_Pin_15);GPIOA_ResetBits(GPIO_Pin_11);DelayMs(30);GPIOA_SetBits(GPIO_Pin_11);GPIOA_ResetBits(GPIO_Pin_14);DelayMs(30);GPIOA_SetBits(GPIO_Pin_14);GPIOB_ResetBits(GPIO_Pin_4);DelayMs(30);GPIOB_SetBits(GPIO_Pin_4);}}实验四:闪烁次数计数
编写程序,让LED闪烁10次后停止闪烁并保持常亮状态。使用一个计数变量记录闪烁次数。
/* 实验四参考框架 */
#include"CH58x_common.h"#include"LED.h"uint8_tTxBuff[]="This is a tx exam\r\n";uint8_tRxBuff[100];uint8_ttrigB;/********************************************************************* * @fn main * * @brief 主函数 * * @return none */intmain(){uint8_ti;// 计数变量,记录闪烁次数LED_Init();// 闪烁10次for(i=0;i<10;i++){RGB_LED_ON();DelayMs(30);// 亮30msRGB_LED_Off();DelayMs(30);// 灭30ms}// 闪烁完毕,保持常亮RGB_LED_ON();while(1){// 空循环,保持常亮状态}}🧪**** 实验拓展挑战:能否实现LED的"SOS"闪烁模式?即短闪3次(亮200ms灭200ms)、长闪3次(亮600ms灭200ms)、再短闪3次,然后暂停2秒后重复。这是嵌入式编程中经典的练习题目。
学习资源
🎬B站系列视频教程:手把手教你从零组装无人机
📦百度网盘资料包(SDK、教材PDF、完整源码):点击下载 提取码: JZS8
本系列持续更新,欢迎收藏关注。如有问题欢迎在评论区交流。