GPIO功能描述
每个GPI/O 端口有两个32位配置寄存器(GPIOx_CRL ,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR 和GPIOx_ODR),一个32位置位/ 复位寄存器(GPIOx_BSRR) ,一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR) 。
根据数据手册中列出的每个I/O 端口的特定硬件特征,GPIO 端口的每个位可以由软件分别配置成多种模式。
─输入浮空
─输入上拉
─输入下拉
─模拟输入
─开漏输出
─推挽式输出
─推挽式复用功能
─开漏复用功能
每个I/O 端口位可以自由编程,然而I/0端口寄存器必须按32位字被访问( 不允许半字或字节访问) 。GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器的读/ 更改的独立访问;这样,在读和更改访问之间产生IRQ 时不会发生危险。
推挽与开漏
推挽输出:可以输出高,低电平,连接数字器件; 推挽结构一般是指两个三极管分别受两
互补信号的控制,总是在一个三极管导通的时候另一个截止.
开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内).
单独的位设置或位清除
当对GPIOx_ODR 的个别位编程时,软件不需要禁止中断:在单次APB2写操作里,可以只更改一个或多个位。
这是通过对“置位/ 复位寄存器”(GPIOx_BSRR ,复位是GPIOx_BRR) 中想要更改的位写’1’来实现的。没被选择的位将不被更改。
外部中断/唤醒线
所有端口都有外部中断能力。为了使用外部中断线,端口必须配置成输入模式。
复用功能(AF)
使用默认复用功能前必须对端口位配置寄存器编程。
●对于复用的输入功能,端口必须配置成输入模式(浮空、上拉或下拉)且输入引脚必须由外部驱动
注意:也可以通过软件来模拟复用功能输入引脚,这种模拟可以通过对GPIO控制器编程来实现。此时,端口应当被设置为复用功能输出模式。显然,这时相应的引脚不再由外部驱动,而是通过GPIO控制器由软件来驱动。
●对于复用输出功能,端口必须配置成复用功能输出模式(推挽或开漏)。
●对于双向复用功能,端口位必须配置复用功能输出模式(推挽或开漏)。这时,输入驱动器被配置成浮空输入模式。
如果把端口配置成复用输出功能,则引脚和输出寄存器断开,并和片上外设的输出信号连接。
如果软件把一个GPIO脚配置成复用输出功能,但是外设没有被激活,它的输出将不确定。
输入配置
当I/O 端口配置为输入时:
●输出缓冲器被禁止
●施密特触发输入被激活
●根据输入配置(上拉,下拉或浮动)的不同,弱上拉和下拉电阻被连接
●出现在I/O 脚上的数据在每个APB2时钟被采样到输入数据寄存器
●对输入数据寄存器的读访问可得到I/O 状态
输出配置
当I/O 端口被配置为输出时:
●输出缓冲器被激活
─开漏模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将端口置于高阻状态(P-MOS从不被激活)。
─推挽模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将激活P-MOS。
●施密特触发输入被激活
●弱上拉和下拉电阻被禁止
●出现在I/O 脚上的数据在每个APB2时钟被采样到输入数据寄存器
●在开漏模式时,对输入数据寄存器的读访问可得到I/O 状态
●在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的值。
复用功能配置
当I/O 端口被配置为复用功能时:
●在开漏或推挽式配置中,输出缓冲器被打开
●内置外设的信号驱动输出缓冲器(复用功能输出)
●施密特触发输入被激活
●弱上拉和下拉电阻被禁止
●在每个APB2时钟周期,出现在I/O 脚上的数据被采样到输入数据寄存器
●开漏模式时,读输入数据寄存器时可得到I/O 口状态
●在推挽模式时,读输出数据寄存器时可得到最后一次写的值。
一组复用功能I/O 寄存器允许用户把一些复用功能重新映象到不同的引脚。
模拟输入配置
当I/O 端口被配置为模拟输入配置时:
●输出缓冲器被禁止;
●禁止施密特触发输入,实现了每个模拟I/O 引脚上的零消耗。施密特触发输出值被强置为’0’;
●弱上拉和下拉电阻被禁止;
●读取输入数据寄存器时数值为’0’。
复用功能I/O 和调试配置(AFIO)
为了优化64脚或100 脚封装的外设数目,可以把一些复用功能重新映射到其他引脚上。设置复用重映射和调试I/O 配置寄存器(AFIO_MAPR) 实现引脚的重新映射。这时,复用功能不再映射到它们的原始分配上。
复用表参看手册
GPIO代码分析
外设库
typedef struct
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
typedef struct
{
__IO uint32_t EVCR;
__IO uint32_t MAPR;
__IO uint32_t EXTICR[4];
uint32_t RESERVED0;
__IO uint32_t MAPR2;
} AFIO_TypeDef;
主要函数分析
void GPIO_DeInit(GPIO_TypeDef* GPIOx) 恢复默认设置
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
如果需要初始化GPIOA,则先enbale A端口,然后关闭A端口
if (GPIOx == GPIOA)
{
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA, DISABLE);
}
}
同理,先启用,然后关闭
void GPIO_AFIODeInit(void) 恢复默认设置
{
RCC_APB2PeriphResetCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_AFIO, DISABLE);
}
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); typedef struct
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14, 输出都是1X
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
判断是不是1X,如果是则是输出类型。
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 */
输出模式下,只要把速度加上就可以了,2,3位上已经表示了CNFX的类型
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 */
对于上下拉的输入,需要对PXODR进行控制,下拉为0,上拉为1.
IPD和IPU设为2X和4X,因为设为1X与输出相同了,但是也不能是0X,0X的话,IPU 与IPD都是08,就无法区分了。因此采用了目前的机制
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;
}
/*---------------------------- GPIO CRH Configuration ------------------------*/ /* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding high 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 + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState)
{
uint32_t tmp = 0x00, tmp1 = 0x00, tmpreg = 0x00, tmpmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_REMAP(GPIO_Remap));
assert_param(IS_FUNCTIONAL_STATE(NewState));
看看ramap的地址是多少,
if((GPIO_Remap & 0x80000000) == 0x80000000)
{
tmpreg = AFIO->MAPR2;
}
else
{
tmpreg = AFIO->MAPR;
}
tmpmask = (GPIO_Remap & DBGAFR_POSITION_MASK) >> 0x10;
tmp = GPIO_Remap & LSB_MASK;
if ((GPIO_Remap & (DBGAFR_LOCATION_MASK | DBGAFR_NUMBITS_MASK)) == (DBGAFR_LOCATION_MASK | DBGAFR_NUMBITS_MASK))
{
tmpreg &= DBGAFR_SWJCFG_MASK;
AFIO->MAPR &= DBGAFR_SWJCFG_MASK;
}
else if ((GPIO_Remap & DBGAFR_NUMBITS_MASK) == DBGAFR_NUMBITS_MASK) {
tmp1 = ((uint32_t)0x03) << tmpmask;
tmpreg &= ~tmp1;
tmpreg |= ~DBGAFR_SWJCFG_MASK;
}
else
{
tmpreg &= ~(tmp << ((GPIO_Remap >> 0x15)*0x10));
tmpreg |= ~DBGAFR_SWJCFG_MASK;
}
if (NewState != DISABLE)
{
tmpreg |= (tmp << ((GPIO_Remap >> 0x15)*0x10));
}
Tmpreg中保存的是最终要写进寄存器的值
if((GPIO_Remap & 0x80000000) == 0x80000000)
{
AFIO->MAPR2 = tmpreg;
}
else
{
AFIO->MAPR = tmpreg;
}
}
GPIO应用实例
void LED_Config(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //设置pin5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //口线翻转速度为50MHz GPIO_Init(GPIOB, &GPIO_InitStructure); PB的5引脚进行设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_3; //LED2, LED3 V7 V8
GPIO_Init(GPIOD, &GPIO_InitStructure); PD的6,3引脚也设置为50MHZ与推挽输出
}
例:
/* I2C1_SCL on PB.08, I2C1_SDA on PB.09 */
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE); 这样就可以启用I2C1的引脚定义为PB8,PB9
库函数