1.1.1 GPIO基本概念
1.通用I/O(GPIO)的含义
所谓通用I/O,也记为GPIO(General Purpose I/O),即基本的输入/输出,有时也称并行I/O,或普通I/O,它是I/O的最基本形式。本书中使用正逻辑,电源(Vcc)代表高电平,对应数字信号“1”;地(GND)代表低电平,对应数字信号“0”。作为通用输入引脚,MCU内部程序可以通过端口寄存器获取该引脚状态,以确定该引脚是“1”(高电平)或“0”(低电平),即开关量输入。作为通用输出引脚,MCU内部程序通过端口寄存器控制该引脚状态,使得引脚输出“1”(高电平)或“0”(低电平),即开关量输出。大多数通用I/O引脚可以通过编程来设定其工作方式为输入或输出,称之为双向通用I/O。
2.输出引脚的基本接法
作为通用输出引脚,MCU内部程序向该引脚输出高电平或低电平来驱动器件工作,即开关量输出,如图4-2所示,输出引脚O1和O2采用了不同的方式驱动外部器件。一种接法是O1直接驱动发光二极管LED,当O1引脚输出高电平时,LED不亮;当O1引脚输出低电平时,LED点亮。这种接法的驱动电流一般在2mA~10mA。另一种接法是O2通过一个NPN三极管驱动蜂鸣器,当O2引脚输出高电平时,三极管导通,蜂鸣器响;当O2引脚输出低电平时,三极管截止,蜂鸣器不响。这种接法可以用O2引脚上的几个mA的控制电流驱动高达100mA的驱动电流。若负载需要更大的驱动电流,就必须采用光电隔离外加其他驱动电路,但对MCU编程来说,没有任何影响。
3.上拉下拉电阻与输入引脚的基本接法
芯片输入引脚的外部有三种不同的连接方式:带上拉电阻的连接、带下拉电阻的连接和“悬空”连接。通俗地说,若MCU的某个引脚通过一个电阻接到电源(Vcc)上,这个电阻被称为“上拉电阻”;与之相对应,若MCU的某个引脚通过一个电阻接到地(GND)上,则相应的电阻被称为“下拉电阻”。这种做法使得,悬空的芯片引脚被上拉电阻或下拉电阻初始化为高电平或低电平。根据实际情况,上拉电阻与下拉电阻可以取值在1KΩ~10KΩ之间,其阻值大小与静态电流及系统功耗有关。
1.1.2 芯片GPIO引脚的基本特征
每个GPIO引脚都可以通过软件配置为推挽式或开放式输出、上下拉输入、无上下拉输入或作为外部复用功能(adc、spi、i2c等等)。而且,大部分的GPIO引脚可配置为数字复用或模拟复用。由于在AHB2总线上的映射,可以做到快速I/O切换。此外,如果需要,可以通过写入特定的序列,关闭I/O复用功能,以避免对I/O寄存器的错误写入。
STM32L431RCT6芯片引脚的负载电容为50pF,85℃下功耗为444mW。(VDDX-VSS),即外部主电源电压(包括VDD、VDDA、VBAT)在-0.4V到4V之间。经过VDD/VDDA电源线的总电流(供应电流)最大值为140mA,经过VSS地线的总电流,反向输出电流最大值为140mA;每个VDD电源引脚的最大电流不超过100mA,每个VSS地脚最大反向输出电流不超过100mA;任何I/O和控制引脚的反向输出电流不超过20mA,任何I/O和控制引脚的输出电流不超过20mA,所有I/Os和控制引脚的总注入电流不超过25mA。
1.1.3 GPIO模块编程寄存器
每个通用I/O端口包括4个32位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR和GPIOx_PUPDR)、2个32位数据寄存器(GPIOx_IDR和GPIOx_ODR)和1个32位置位/复位寄存器(GPIOx_BSRR)。此外,所有GPIO都包括1个32位锁定寄存器(GPIOx_LCKR)和2个32位复用功能选择寄存器(GPIOx_AFRH和GPIOx_AFRL)。
1.GPIO端口模式寄存器(GPIOx_MODER)(x=A to E and H)
复位值:
0xABFF FFFF(GPIOx_MODER寄存器掩码为0xABFF FFFF,使用端口A)
0xFFFF FEBF(GPIOx_MODER寄存器掩码为0xFFFF FEBF,使用端口B)
0xFFFF FFFF(GPIOx_MODER寄存器掩码为0xFFFF FFFF,使用端口C..E)
0x0000 000F(GPIOx_MODER寄存器掩码为0x0000 000F,使用端口H)
数据位 |
D31~D30 |
D29~D28 |
D27~D26 |
… |
D3~D2 |
D1~D0 |
读 |
MODE15[1:0] |
MODE14[1:0] |
MODE13[1:0] |
… |
MODE1[1:0] |
MODE[1:0] |
写 |
D31~D0(MODEy[1:0],y∈[0,15]):x端口y引脚配置位。这些位通过软件写入,用于配置I/O模式。00:输入模式;01:通用输出模式;10:复用功能模式(可复用为ADC,SPI模块功能引脚);11:模拟模式(芯片复位后,引脚默认模式)。eg:MODE15[1:0]=01,X端口15号引脚被配置为通用输出模式(类似的情况,下面就不再描述)。
2.GPIO端口输出类型寄存器(GPIOx_OTYPER)(x=A to E and H)
数据位 |
D31~D16 |
D15 |
D14 |
D13 |
D12 |
D11 |
… |
D2 |
D1 |
D0 |
读 |
0 |
OT15 |
OT14 |
OT13 |
OT12 |
OT11 |
… |
OT2 |
OT1 |
OT0 |
写 |
— |
D31~D16:保留,必须保持复位值。
D15~D0(OTy[1:0],y∈[0,15]):x端口y引脚配置位。这些位通过软件写入,用于配置I/O输出类型。OTy=0:推挽输出(复位状态);OTy=1:开漏输出。GPIOx_OTYPER寄存器复位值为0,
3.GPIO端口输出速度寄存器(GPIOx_OSPEEDR)(X=A to E and H)
复位值:
0x0C00 0000(端口A)
0x0000 0000(其他端口)
数据位 |
D31~D30 |
D29~D28 |
D27~D26 |
… |
D3~D2 |
D1~D0 |
读 |
OSPEED15[1:0] |
OSPEED14[1:0] |
OSPEED13[1:0] |
… |
OSPEED1[1:0] |
OSPEED0[1:0] |
写 |
D31~D0(OSPEEDy[1:0],y∈[0,15]):x端口y引脚配置位。这些位通过软件写入,用于配置引脚输出速度。00:低速;01:中速;10:高速;11:超高速。
4.GPIO端口上拉/下拉寄存器(GPIOx_PUPDR)(X=A to E and H)
GPIOx_PUPDR寄存器与GPIOx_OSPEEDR寄存器结构类似,有16个PUPDy[1:0]位(y∈[0,15]),即16个引脚配置位。这些位通过软件写入,用于配置I/O引脚上拉或下拉。00:y引脚无上拉或下拉;01:y引脚上拉;10:y引脚下拉;11:保留。
5.GPIO端口输入数据寄存器(GPIOx_IDR)(X=A to E and H)
GPIOx_IDR寄存器与GPIOx_OTYPER寄存器结构类似,D31~D16位保留,必须保持复位值,即0。D15~D0(IDy,y∈[0,15]),端口输入数据位。这些位为只读位,它们包含相应I/O引脚的电平信息。
6.GPIO端口输出数据寄存器(GPIOx_ODR)(X=A to E and H)
GPIOx_ODR寄存器与GPIOx_OTYPER寄存器结构类似,D31~D16位保留,必须保持复位值,即0。D15~D0(OD y,y∈[0,15]):端口输出数据位。这些位可通过软件读取和写入,该位决定着被配置为输出引脚的电平的高低。若OD5=0,5号引脚为低电平;反之,为高电平。
7.GPIO端口位置1/复位寄存器(GPIOx_BSRR)(X=A to E and H)
数据位 |
D31~D16 |
D15~D0 |
读 |
— |
|
写 |
BR[15:0] |
BS[15:0] |
D31~D0:这些位为只写,读取这些位可返回值0x0000,复位值为0。
D31~D16(BRy,y∈[0,15]):端口x复位位y, 0:不会对相应的ODx位执行任何操作;1:复位相应的ODx位,即将ODx位写0。
注:如果同时对BSx和BRx置位,则BSx的优先级更高。
D15~D0(BSy,y∈[0,15]):端口x置位位y, 0:不会对相应的ODx位执行任何操作;1:将相应的ODx位置1。
8.GPIO端口位复位寄存器(GPIOx_BRR)(X=A to E and H)
GPIOx_BRR寄存器与GPIOx_OTYPER寄存器结构类似,D31~D16位保留,必须保持复位值,即0。D15~D0(BR[15:0]):端口x复位位y(y=0..15),这些位为只写。读取这些位可返回值0x0000。0:不会对相应的ODx位执行任何操作;1:复位相应的ODx位。
9.GPIO端口配置锁定寄存器(GPIOx_LCKR)(X=A to E and H)
当正确的写序列应用到第 16 位(LCKK)时,此寄存器将用于锁定端口位的配置。位[15:0]的值用于锁定GPIO的配置。在写序列期间,不能更改LCKR[15:0]的值。将LOCK序列应用到某个端口位后,在执行下一次MCU复位或外设复位之前,将无法对该端口位的值进行修改。
GPIOx_LCKR寄存器与GPIOx_OTYPER寄存器结构类似,D31~D16位保留,必须保持复位值,即0。D16(LCKK):锁定键,可随时读取此位。可使用锁定键写序列对其进行修改。LCKK=0:端口配置锁定键未**。LCKK=1:端口配置锁定键已**,GPIOx_LCKR寄存器被锁定,直到下一次MCU复位或外设复位。
锁定键写序列:
WR LCKR[16]=‘1’+LCKR[15:0]
WR LCKR[16]=‘0’+LCKR[15:0]
WR LCKR[16]=‘1’+LCKR[15:0]
RD LCKR
RD LCKR[16]=‘1’(此读操作为可选操作,但它可确认锁定已**)
D15~D0(LCK[15:0]):端口x锁定位,这些位都是读/写位,但只能在LCKK位等于“0”时执行写操作。0:端口配置未锁定;1:端口配置已锁定。
10.GPIO复用功能低位寄存器(GPIOx_AFRL)(X=A to E and H)
数据位 |
D31~D28 |
D27~D24 |
D23~D20 |
… |
D11~D8 |
D7~D4 |
D3~D0 |
读 |
AFSEL7[3:0] |
AFSEL6[3:0] |
AFSEL5[3:0] |
… |
AFSEL2[3:0] |
AFSEL1[3:0] |
AFSEL0[3:0] |
写 |
D31~D0(AFSELy[3:0],y∈[0,7]):端口x引脚y的复用功能选择,这些位通过软件写入,用于配置复用功能I/O,复位值为0。
eg:GPIOC_AFSEL0[3:0]=0100(AF4),则C端口0号引脚被配置为I2C3_SCL功能引脚。
AFSELy选择:该4位二进制值为0000~1111,分别记为AF0~AF15
11.GPIO复用功能高位寄存器(GPIOx_AFRH)(X=A to E and H)
GPIOx_AFRH寄存器的结构与GPIOx_AFRL寄存器的结构类似。D31~D0(AFSEL y[3:0],y∈[8,15]):端口x引脚y的复用功能选择,这些位通过软件写入,用于配置复用功能I/O。
之所以会存在AFRL,AFRH两个复用功能寄存器,是因为AFSELy(y∈[0,15])分别与SYS_AF,TIM1/TIM2/LPTIM1,TIM1/TIM2,USART2,I2C1/I2C2/I2C3,SPI1/SPI2,SPI3,USART1/USART2/USART3,LPUART1,CAN1/TSC ,QUADSPI,SDMMC1/COMP1/COMP2/SWPMI1,SAI1,TIM2/TIM15/TIM16/LPTIM2,EVENTOUT功能模块分别相对应,但AFSEL11没有对应复用功能。而且,EVENTOUT是用于多个ARM之间同步用。与用GPIO输出相比较,GPIO至少要3条指令输出一个脉冲,并且访问总线有延时,而输出EVENTOUT只用 一条SEV指令,脉冲直接由内核控制,延迟最小。
1.1.4 GPIO编程基本步骤
GPIO编程具体实例可参“User_STM32_Light_Simple_20200423”,该工程实现了直接地址改变小灯亮暗状态的基本功能。但需要注意PortB端口的基地址为0x48000400,bsrr寄存器偏移量为0x18(十进制偏移量为24),brr寄存器偏移量为0x28(十进制为40)。
但是,vuint_32* gpio_bsrr=(vuint_32*)(gpio_ptr+0x18);
gpio_bsrr 的地址为0x48000460非0x48000418,故所加值为偏移量的1/4。点亮小灯具体步骤如下:
1)使能PORTB端口时钟
//使能PORTB时钟。 RCC_AHB2=(vuint_32*)0x4002104C; *RCC_AHB2|=(1<<1); |
2)计算引脚模式寄存器、位置1寄存器、复位寄存器基地址
//变量定义,对应GPIO各寄存器基地址 vuint_32* gpio_mode=(vuint_32*)0x48000400; vuint_32* gpio_bsrr=(vuint_32*)(gpio_ptr+0x6); vuint_32* gpio_brr=(vuint_32*)(gpio_ptr+0xA); |
3)//配置引脚功能模式,定义为输出引脚
vuint_32 temp; //先将mode7(红灯),mode8(绿灯),mode9(蓝灯)均清零 temp=~(uint_32)((3<<18)|(3<<16)|(3<<14)); *gpio_mode&=temp; //先mode7(红灯),mode8(绿灯),mode9(蓝灯)均设置为01 temp=(uint_32)((1<<18)|(1<<16)|(1<<14)); *gpio_mode|=temp; //将PB9,PB8,PB7设置为gpio输出引脚 |
4)改变小灯亮暗状态
//设置引脚为低电平,点亮小灯 temp=(uint_32)((1<<7)|(1<<8)|(1<<9)); // temp=(uint_32)(7<<7); *gpio_brr|=temp; //PB7,PB8,PB9为低电平,红绿蓝灯亮 //设置引脚为高电平,熄灭小灯 temp=(uint_32)(7<<7); *gpio_bsrr=temp; //PB8为高电平,红绿蓝灯灭 |