GPIO详细介绍
一.GPIO是什么
GPIO = 单片机通用引脚,用来输出高低电平、读取外部电平,是单片机和硬件交互最基础的通道。
二. 输入输出模式
2.1、 输入类(4 种)
浮空输入 :引脚无上下拉,悬空时电平随机,仅适合外部自带驱动的信号。
上拉输入 :内部接上拉电阻,默认高电平,按键检测最常用。
下拉输入 :内部接下拉电阻,默认低电平。
图1 上拉输入/下拉输入(两个差不多只是上电默认电平不同)
模拟输入 :关闭数字电路,专供 ADC 电压采集,不能做普通 IO。
图2 模拟输入
2.2、通用输出类(2 种)
推挽输出:高低电平都能驱动,驱动 LED、指示灯首选。
D:漏极 G:栅极 S:源极
N-MOS(下管):栅极电压 > 源极电压(Source接地,即0V)时导通。也就是说,给栅极高电平(1,如3.3V),NMOS就导通。
PMOS(上管):栅极电压<源极电压(Source接VDD,即3.3V)时导通。也就是说,必须给栅极低电平(0,如0V),PMOS才导通。下面两个分别是推挽模式写入高低电平的电流走向图:
图3 推挽输出 置1
图4 推挽输出 置0
为什么电平到输出控制那里变成相反的电平了,因为输出控制那个位置串联着一个反相器(非门)所以变成相反的电平了
开漏输出:只能主动拉低,高电平需要外部上拉,多用于总线通讯。
在开漏输出模式中P-MOS是不工作的,N-MOS又只能输出低电平,在写入1(高电平)时VG==VS,N-MOS管截止,这个时候I/O口属于高阻态的状态,想要输出高电平必须要自己外接上拉电阻。
高阻态(High-Z):这是引脚内部驱动器的状态。指的是芯片内部的 N-MOS 和 P-MOS 全部截止(关闭),导致引脚内部与 VDD 和 VSS 完全断开。这是个“电路动作”,描述的是“芯片有没有在干活”。
为什么非要“外接”而不用内部的?
因为内部上拉电阻阻值太大(40kΩ),驱动能力极弱。在 I2C 高速通信(400kHz 或更快)时,这么大的电阻加上线路寄生电容,会导致上升沿极其缓慢(波形变“钝”),造成通信时序错乱。
所以,外部上拉电阻通常选用 2kΩ ~ 10kΩ(阻值越小,上升沿越快,但功耗越大)。这在硬件电路设计时是必须外挂的元器件。下面是开漏输出的写入低电平的电流走向图:
图5 开漏输出
2.3、复用外设输出类(2 种)
复用推挽输出:引脚分配给串口、定时器 PWM 等外设,推挽驱动。
复用推挽、复用开漏的 MOS 驱动电路结构和普通推挽 / 开漏完全一致,区别仅在于驱动信号来源不同:普通模式由 CPU 写输出寄存器控制,复用模式由片上外设直接输出信号控制,软件寄存器不再干预引脚电平。下面是复用推挽输出的写入高电平的电流走向图:
图6 复用推挽输出
复用开漏输出:外设复用 + 开漏,典型用于 I2C 总线。
三、编写驱动程序
3.1、介绍部分寄存器(GPIO、RCC)
3.1.1、APB2 外设时钟使能寄存器(RCC_APB2ENR)
它是挂载在APB2总线上的所有外设(GPIO、USART1、ADC1等)的“总电闸”——复位后默认全部关闭,使用前必须通过该寄存器把对应位置1打开时钟。
3.1.2、端口配置低寄存器(GPIOx_CRL) (x=A..E)
它用 4 个位(共 32 位)控制一个引脚,同时决定了该引脚是输入/输出、输出速度、以及推挽/开漏/复用/模拟等具体模式。这是配置该GPIO的低8位引脚
高两位CNFy[1:0],低二位MODEy[1:0]
3.1.3、端口配置高寄存器(GPIOx_CRH) (x=A..E)
它用 4 个位(共 32 位)控制一个引脚,同时决定了该引脚是输入/输出、输出速度、以及推挽/开漏/复用/模拟等具体模式。这是配置该GPIO的高8位引脚
高两位CNFy[1:0],低二位MODEy[1:0]
3.1.4、端口输入数据寄存器(GPIOx_IDR) (x=A..E)
这是一个“只读”寄存器,它的 16 个有效位(Bit0~Bit15)直接反映了 GPIO 引脚 Px0~Px15 当前的实时电平状态(高/低)。 你读到的 1 就是高电平(3.3V),读到的 0 就是低电平(0V)。
3.1.5、端口输出数据寄存器(GPIOx_ODR) (x=A..E)
它是一个“可读可写”的寄存器,向对应位写入 1 让引脚输出高电平(3.3V),写入 0 让引脚输出低电平(0V)。 但写入的效果取决于当前引脚的模式:推挽输出时直接驱动电压,开漏输出时只能控制拉低,而在输入模式下它另有妙用(控制上下拉)。
3.1.6、端口位设置/清除寄存器(GPIOx_BSRR) (x=A..E)
这是一个 32 位的“只写”寄存器(读回值无效),低 16 位用来将对应引脚输出高电平,高 16 位用来将对应引脚输出低电平。 向任意位写 1 执行动作,写 0 则无影响。最核心的优势是:一条硬件指令即可完成置位或清零,绝对原子,永不被打断。
3.1.7、端口位清除寄存器(GPIOx_BRR) (x=A..E)
这是一个 32 位的“只写”寄存器,只有低 16 位有效(对应 Px0~Px15)。向某个位写 1,对应引脚就被拉低为 0V;写 0 则无任何影响。 它和 BSRR 配合,构成了 F10x 下原子操作 GPIO 的“黄金搭档”。
3.1.8、端口配置锁定寄存器(GPIOx_LCKR) (x=A..E)
一旦按特定序列锁定,GPIO 引脚的模式配置(输入/输出/复用等)就会被硬件冻结,直到下次复位前都无法再修改。 它像一个保险开关,专门用来防止关键引脚的配置被意外篡改。
3.2、STM32编程
我们今天的任务是使用STM32F103C8T6最小系统板,点亮一个led灯让其进行闪烁,在原理图上led灯连接的是PC13低电平点亮,所以我们控制PC13一直产生一个高低电平即可
3.2.1、寄存器编程
先找到外设基地址,打开STM32f10x-中文参考手册,在储存器映射这里最下面就是外设基地址也是APB1的基地址
直接写在代码里面
/*片上外设基地址 */ #define PERIPH_BASE ((unsigned int)0x40000000) #define APB1PERIPH_BASE PERIPH_BASE一下是APB2和AHB的基地址,AHB原本应该用0x40018000的我们这边为了方便使用0x40020000
/*APB2 总线基地址 */ #define APB2PERIPH_BASE (PERIPH_BASE+0x10000) /* AHB总线基地址 */ #define AHBPERIPH_BASE (PERIPH_BASE+0x20000)找到GPIOC的外设基地址
/*GPIOC外设基地址*/ #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)GPIO的寄存器我们只使用到了GPIOC_CRH,GPIOC_ODR,直接用宏定义#define定义用GPIOC的外设基地址加上我们要使用到的寄存器的偏移地址就可以了
/* GPIOC寄存器地址,强制转换成指针 */ #define GPIOC_CRH *(unsigned int *)(GPIOC_BASE + 0x04) #define GPIOC_ODR *(unsigned int *)(GPIOC_BASE + 0x0C)除了以上的几个寄存器还是不够的,我们还需要启动该GPIO的时钟
因为RCC是在AHB上面,所以我们用AHB的基地址加上RCC的基地址,然后再加RCC_APB2ENR
寄存器的偏移地址就可以了
(unsigned int *):强制转换成无符号32位的指针类型
*:解引用(找到地址里的数据)
/*RCC外设基地址*/ #define RCC_BASE (AHBPERIPH_BASE + 0x1000) /*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/ #define RCC_APB2ENR *(unsigned int *)(RCC_BASE + 0x18)主函数文件夹
#include "stm32f10x.h" void Delay(unsigned int us) { for(volatile unsigned int y = 0;y < us;y++) { ; } } /** * 主函数 */ int main(void) { // 开启GPIOC 端口时钟 RCC_APB2ENR |= (1<<4); //先清空控制PC13的端口位 GPIOC_CRH &= ~( 0x0F<< (4*5));//~( 0x0F<< (4*5)) == 1111 1111 0000 1111 | 1111 1111 1111 1111 // 然后再配置PC13为通用推挽输出,速度为10M GPIOC_CRH |= (0x01<<4*5); // PC13 输出 低电平 GPIOC_ODR &= ~(1<<13); while(1) { // PC13 输出 高电平 GPIOC_ODR |= (1<<13); //直接操控ODR来置位/清零可能会被中断打断不安全 //可以用下面这这两个寄存器来做置位/清零 //GPIOC_BSRR = (1 << 13); // 高电平 //GPIOC_BRR = (1 << 13); // 低电平 (F10x 专用) // //延时 Delay(0xffff); // P13 输出 低电平 GPIOC_ODR &= ~(1<<13); //延时 Delay(0xffff); } } // 函数为空,目的是为了骗过编译器不报错 void SystemInit(void) { ; }3.2.2、标准库操作
#include "stm32f10x.h" // Device header //延时 void Delay(unsigned int us) { for(volatile unsigned int y = 0;y < us;y++) { ; } } int main(void) { //启动GPIOC的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //创建一个GPIO的初始化结构体 GPIO_InitTypeDef GPIO_InitStructure; //使用推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //使用PC13 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //速度配置为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //以上程序还没有写入GPIOC的寄存器的 //GPIO_Init将刚刚配置的全部写到寄存器 GPIO_Init(GPIOC, &GPIO_InitStructure); while (1) { //PC13置零 GPIO_ResetBits(GPIOC, GPIO_Pin_13); //延时 Delay(0xffff); //PC13置零 GPIO_SetBits(GPIOC, GPIO_Pin_13); //延时 Delay(0xffff); } }上面大部分图片来源为《STM32F10x-中文参考手册》
如果你觉得有帮助,欢迎点赞、收藏、评论,让更多人看到!
