GPIO全称为General Purpose Input Output,中文理解为通用输入输出端口。它指的是编程可控制的引脚,便可以控制引脚是做为输入来用,仍是输出功能,又或者是交给片上外设使用(复用)。编程
理解GPIO硬件电路的实现,有助于编程的理解。下图是官方手册给出的GPIO框图:ui
此框图中只有最右端的I/O pin是板上外露可见的,其余部分均在芯片内部。该框图结构被分为两个部分,其一是输出结构,主要由引脚(I/O pin)、保护二极管(protection diode)、双MOS管(P-MOS+N-MOS)、输出控制(output control)、输出数据寄存器(output data register)、位置位/复位寄存器(bit set/reset register)组成 ;其二是输入结构,由引脚、保护二极管、双开关(on/off)、TTL肖特基触发器(TTL Schmitt trigger)、输入数据寄存器(input data register)组成。spa
两个二极管构成了双向限幅电路。当外部输入电压大于VDD时,上方二极管导通(全文皆忽略二极管和MOS管导通压降),使得电压限制在VDD左右;当外部输入电压低于VSS,下方二极管导通,电压限制在VSS左右。然而这样的保护并不能使引脚直接驱动大功率器件,例如电机,就须要外加驱动电路。3d
这个双开关电路与上拉、下拉和浮空模式有关,当处于上拉模式时上方开关闭合,下方断开,除非引脚的外部输入为低电平,不然输入到肖特基触发器的电平都是高电平;当处于下拉模式时上方开关断开,下方闭合,除非引脚的外部输入为高电平,不然输入到肖特基触发器的电平都是低电平;当处于浮空模式时开关所有断开,电平默认为高阻态,输入的电平彻底取决于外部输入。上下拉多用于按键检测,而浮空则经常使用于ADC检测。code
该触发器主要是将模拟电压转为0/1数字信号,上图中模拟输入(Analog input)就取自触发器以前,而输入数据寄存器则取自触发器以后的数字信号。blog
这个寄存器用来存储各个引脚电平状态,可经过读取该寄存器内容,来获取任意引脚状态。input
经过读写该寄存器,能够间接地更改输出数据寄存器的值。it
能够直接读写该寄存器,来更改各个引脚的输出状态,当寄存器值保持不变时,每一个引脚的电平也就不发生变化。io
双MOS管组成一个推挽输出(push-pull)电路,当输出寄存器中值为1时,通过反相器,两管的共栅极输入低电平,则PMOS导通,NMOS截止,引脚输出高电平;当输出寄存器中值为0时,通过反相器,两管的共栅极输入高电平,则NMOS导通,PMOS截止,引脚输出低电平。当PMOS被禁用时,电路变为开漏输出,引脚仍可输出低电平,但没法正常输出高电平,取而代之的是高阻态。若是此时想要输出高电平,则必须接上拉电阻。开漏输出仅在一些特殊场合下使用,更多的是推挽输出。class
RCC_APB2ENR并不属于GPIO的寄存器组,可是它控制着GPIO的引脚时钟使能,芯片上电后,全部外设时钟默认是关闭的,因此在使用GPIO前要先打开端口时钟使能。该寄存器内容以下,咱们只关心IOPxEN位,置1则开启时钟。
该寄存器可配置低8个GPIO引脚的工做模式:模拟输入、浮空输入、上拉\下拉输入、通用推挽输出、通用开漏输出、复用推挽输出、复用开漏输出。复用输入模式没有单独的配置,只要将引脚配置为浮空/上拉/下拉输入模式,由片上外设驱动便可。CRL还能够在输出模式下配置工做频率,频率越大,功耗越高。注意每一个引脚模式配置占4bit。对于上拉\下拉输入来讲,是上拉仍是下拉取决于ODR寄存器配置的值。
该寄存器同GPIOx_CRL,只是它控制的是高8个GPIO引脚的工做模式。
该寄存器存储了当前引脚的状态,不管GPIO在哪一种工做模式下,均可以读取该寄存器,但在模拟输入模式下,寄存器的值一直为0。
在通用输出模式下,该寄存器可用来控制电平高低;在复用推挽输出模式下,可读取ODR获取引脚状态(在复用开漏输出模式下,可读取IDR获取引脚状态)。在上拉或下拉输入模式下,ODR可决定是上拉仍是下拉。
经过该寄存器,能够单独控制某个引脚的电平变化。由于向这个寄存器写0的结果是无操做,写1是置位或者复位,因此访问该寄存器能够直接用=号;而ODR寄存器写0则表示给低电平,写1表示给高电平,为了避免影响其余引脚,写入必须用&=和|=号。另外还有个寄存器叫GPIOx_BRR,同该寄存器差很少,只不过BRR只能复位,高16位是保留的。
GPIO最经常使用的就是通用推挽输出和上拉/下拉输入这两种模式。一下程序实现功能为:
PA0为按键输入,按下为低电平。PC13是led控制引脚,低电平点亮LED。当按下按键时,LED亮;松开按键时,LED灭。
程序代码以下,根据GPIO原理,编程顺序通常是:先开启时钟使能,配置引脚工做模式,最后给引脚电平信号。
int main(void) { uint32_t key_value; //开启GPIO外设时钟使能 //PORTA,第三位置1 RCC->APB2ENR |= (1<<2); //PORTC,第五位置1 RCC->APB2ENR |= (1<<4); //GPIO输入输出模式配置 //清零PA0控制位 GPIOA->CRL &= ~((uint32_t)(0x0F)<<(4*0)); //设置PA0为上拉输入 GPIOA->CRL |= (uint32_t)(0x08)<<(4*0); GPIOA->ODR |= (uint32_t)(0x01)<<0; //清零PC13控制位 GPIOC->CRH &= ~((uint32_t)(0x0F)<<(4*5)); //设置PC13为推免输出 GPIOC->CRH |= (uint32_t)(0x01)<<(4*5); while(1){ //获取PA0电平状态,key_value==1表示按键没有按下 key_value = (GPIOA->IDR & (uint32_t)(0x01)) == (uint32_t)(0x01); //改变PC13电平 if (key_value==0) //PC13低电平输出 GPIOC->ODR &= ~((uint32_t)(0x01)<<13); else //PC13高电平输出 GPIOC->ODR |= (uint32_t)(0x01)<<13; } }