GPIO详解
一. GPIO内部结构图
1. 核心器件分析
如图标号1: 当引脚上有大于VDD的电流进入, 上面的保护二极管导通. 当小于VSS的电信号进入, 下面的保护二极管导通. 防止大电流进入芯片内部烧毁芯片, 这个保护能力有限.
如图标号2: 内部上下拉电阻, 当引脚处于非模拟输入模式时, 让引脚处于稳定的电平状态.
如图标号3: TTL肖特基触发器, 也称施密特触发器. 其工作原理为输入电压高于正向阈值输出为高, 输入电压低于负向阈值输出为低. 当输入电压处于中间状态, 则输出维持上一次输出的状态.
如图标号4: 这里实际上是由P-MOS和N-MOS组成. P-MOS为低电平导通并输出1, N-MOS为高电平导通并输出0.
2. GPIO八种工作模式
2.1浮空输入: 2号位上下拉电阻都断开即为浮空输入, 这时候引脚的电平状态是不确定的. 采集输入模拟信号时候会用到, 其他场景下我们应该避免使用浮空输入, 导致出现这种引脚电平不确定的状态. 除非有外部电路实现了上下拉, 为引脚提供了可确定的电平状态.
2.2上拉输入: 2号位上拉电阻连接, 此时输入方式称为上拉输入. 经常应用到需要检测GPIO引脚产生上升/下降沿时候用到.
2.3下拉输入: 2号位下拉电阻连接, 此时输入方式称为下拉输入. 经常应用到需要检测GPIO引脚产生上升/下降沿时候用到.
2.4模拟输入: 上下拉电阻断开,施密特触发器关闭,双 MOS 管也关闭。其他外设可以通过模拟通道输入输出。该模式下需要用到芯片内部的模拟电路单元单元,用于 ADC、DAC、MCO这类操作模拟信号的外设.
2.5开漏输出: P-MOS管处于截止状态, 此时输出状态仅由N-MOS控制. 当ODR输出0时经过反相器变成1, 此时N-MOS导通引脚通过N-MOS接地输出低电平. 当ODR输出1经过反相器变成0, 此时N-MOS处于高阻态, GPIO引脚状态不确定.实际应用的时候开漏输出需要外部上拉电子来输出高电平, 否则开漏模式只能输出低电平.
2.6推挽输出: 当ODR输出0时经过反相器变成1, 此时P-MOS截止, N-MOS导通, GPIO输出低电平. 当ODR输出1经过反相器变成0, 此时N-MOS截止, P-MOS导通GPIO输出高电平.
2.7开漏式复用输出: 跟开漏输出功能一样, 只不过芯片内部不是连接到ODR寄存器, 而是连接到其他外设.
2.8推挽式复用输出: 跟推挽输出功能一样, 只不过芯片内部不是连接到ODR寄存器, 而是连接到其他外设.
二. GPIO寄存器分析
1. GPIOx_CRL和GPIOx_CRH寄存器
1.1这两个寄存器一共64个比特位, 用于控制16根GPIO引脚(Pin0-15). 每4个比特位控制一根引脚. 如图CNF0, MODE0控制Pin0. 以此类推... (GPIOx_CRL控制Pin0-7, CRH控制Pin8-15)
2. GPIOx_IDR和GPIOx_ODR寄存器
2.1GPIOx_IDR寄存器为端口输入寄存器, 只能以16位的形式读出(因为高16位保留), 读出的值为对应I/O的口的状态.
2.2GPIOx_ODR寄存器为端口输出寄存器, 高16位保留低16位可读可写(不可单独操作一个位), 另外也可以通过GPIOx_BSRR和GPIOx_BRR寄存器控制.
3. GPIOx_BSRR, GPIOx_BRR和GPIOx_LCKR寄存器
3.1GPIOx_LCKR用于锁住GPIO引脚的配置, 用的很少不详细分析.
3.2GPIOx_BRR这个寄存器在F4, 7, H等更高配置的STM系列芯片中被去除. 考虑到代码的兼容性这个寄存器尽量不使用, 其功能完全可以被GPIOx_BSRR代替. 低16位写1可以将ODR寄存器中对应位复位为0.
3.3GPIO_x_BSRR高16位写1可以将ODR寄存器对应位复位为0. 低16为写1可以将ODR寄存器对应位置为为1.
三. GPIO外设驱动
1. 操作寄存器方式
1.1这里以配置GPIOC, Pin13端口输0为例: 点亮核心板PC13的绿灯.
#include "stm32f10x.h"
/*
配置GPIOC, Pin13端口输0为例: 点亮核心板PC13的绿灯.
*/
#define RCC_BASE_ADDRESS ((unsigned int)0x40021000)
#define RCC_APB2ENABLE_M *((unsigned int*)(RCC_BASE_ADDRESS + 0x18))
#define GPIOC_ADDRESS ((unsigned int)0x40011000)
#define GPIOC_CRL_M *((unsigned int*)GPIOC_ADDRESS)
#define GPIOC_CRH_M *((unsigned int*)(GPIOC_ADDRESS + 0x04))
#define GPIOC_ODR_M *((unsigned int*)(GPIOC_ADDRESS + 0x0C))
int main()
{
//使能GPIOC的RCC时钟
RCC_APB2ENABLE_M = 0x0010;
//配置CRL寄存器, 将GPIO配置为推挽输出模式, 输出速度为50MHz
GPIOC_CRH_M = 0x00300000;
//配置ODR寄存器, 使PC13引脚输出0
GPIOC_ODR_M = 0x0000;
}
2. 调用固件库(标准库)方式
2.1这里同样以配置GPIOC, Pin13端口输0为例: 点亮核心板PC13的绿灯.
#include "stm32f10x.h"
/*
配置GPIOC, Pin13端口输0为例: 点亮核心板PC13的绿灯.
*/
int main()
{
GPIO_InitTypeDef GPIO_InitStructures;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructures.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructures.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructures.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructures);
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
}
3. GPIO_Init()函数分析.
3.1这里GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)函数实际上就是我们配置GPIO模式的过程.
/*代码太长可以参考野火指南者: PDF154页. 这里省略了CRH部分代码*/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
}
4. GPIO外设驱动总结
4.1GPIO驱动外设四步法:
1. RCC时钟使能(这里实际上还需要配置时钟xMHz, 但是大部分系统有默认值, 只需要去使能即可. 也可以自行修改)
2. 初始化结构体, 这里实际上官方将GPIO所有相关寄存器都放在一个结构体中, 我们可以通过将结构体成员设置成我们想要的值即可.
3. 初始化GPIO, 这里实际上也是将我们初始化好的结构体写入到寄存器中的过程.
4. 操作GPIO, 当我们将引脚功能配置好后, 就可以通过官方提供的库函数去驱动GPIO进行读写操作.