1 GPIO简介
GPIO是通用输入输出端口的简称,简单来说就是STM32可控制的引脚,STM32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32芯片的GPIO被分成很多组,每组有16个引脚,所有的GPIO引脚都有基本的输入输出功能。上电复位后,GPIO默认为浮空状态,部分特殊功能引脚为特定状态。
最基本的输出功能是由STM32控制引脚输出高、低电平,实现开关控制,如把GPIO引脚接入LED灯,那就可以控制LED灯的亮灭,引脚接入到继电器或三极管,那就可以通过继电器或三极管控制外部大功率电路的通断。最基本的输入功能是检测外部电平,如把GPIO引脚连接到按键,通过电平高低区分按键是否被按下。
STM32的 GPIO模式有以下几种:
GPIO_Mode_AIN ---------------------- 模拟输入
GPIO_Mode_IN_FLOATING -------- 输入浮空
GPIO_Mode_IPD ---------------------- 输入下拉
GPIO_Mode_IPU ---------------------- 输入上拉
GPIO_Mode_Out_OD ---------------- 开漏输出
GPIO_Mode_Out_PP ---------------- 推挽式输出
GPIO_Mode_AF_OD ---------------- 开漏复用功能
GPIO_Mode_AF_PP ----------------- 推挽式复用功能
typedef enum
{
GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
} GPIOMode_TypeDef;
保护二极管及上、下拉电阻:
引脚的两个保护二极管可以防止引脚外部过高或过低的电压输入,当引脚电压高于时,上方的二极管导通,当引脚电压低于Vss时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。但是尽管如此,还是不能直接外接大功率器件,须加大功率及隔离电路驱动,防止烧坏芯片或者外接器件无法正常工作。
上下拉电阻,从它的结构我们可以看出,通过上、下拉电阻的开关配置,我们可以控制引脚默认状态的电压,开启上拉的时候引脚电压为高电平,开启下拉的时候引脚电压为低电平。也可以设置“既不上拉也不下拉模式”,我们也把这种状态称为浮空模式,配置成这个模式时,直接用电压表测量其引脚电压为1点几伏,这是个不确定的值。所以一般来说我们都会选择给引脚设置“上拉模式”或“下拉模式”使它有默认状态。STM32的内部上拉时“弱上拉”,即通过上拉输出的电流时很弱的,如要求大电流还是需要外部上拉,通过“上拉/下拉寄存器GPIOx_CRL和GPIOx_CRH”控制引脚的上、下拉及浮空模式。
TTL施密特触发器:
基本原理是当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;信号经过触发器后,模拟信号转化为0和1的数字信号。但是,当GPIO引脚作为ADC采集电压的输入通道时,用其“模拟输入”功能,此时信号不再经过触发器进行TTL电平转换。ADC外设要采集到的原始的模拟信号。IO口信号经过触发器后,模拟信号转化为0和1的数字信号 也就是高低电平 并且是TTL电平协议 这也是为什么STM32是TTL电平协议的原因。
P-MOS管和N-MOS管:
GPIO引脚线路经过两个保护二极管后,向上流向“输入模式”结构,向下流向“输出模式”结构。先看输出模式部分,线路经过一个由P-MOS和N-MOS管组成的单元电路,这个结构使GPIO具有了“推挽输出”和“开漏输出”两种模式。
所谓的推挽输出模式,是根据这两个MOS管的工作方式来命名的。在该结构中输入高电平时,经过反向后,上方的P-MOS导通,下方的N-MOS关闭,对外输出高电平;而在该结构中输入低电平时,经过反向后,N-MOS导通,P-MOS关闭,对外输出低电平,当引脚高低电平切换时,两个管子轮流导通,P管负责灌电流,N管负责拉电流,使其负载能力和开关速度都比普通的方式由很大的提高。推挽输出的低电平为0伏,高电平为3.3V,如下图,它是推挽输出模式时的等效电路。
而在开漏输出模式时,上方的P-MOS完全不工作。如果我们控制输出为0,低电平,则P-MOS管完全关闭,N-MOS管导通,使输出接地,若控制输出为1(它无法直接输出高电平)时,则P-MOS和N-MOS都关闭,所以引脚既不输出高电平也不输出低电平,为高阻态。为了正常使用时必须外部接上拉电阻,参考下图中的等效电路。它具有“线与”特性,也就是说,若有很多个开漏模式引脚连接到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此电平的电压为外部上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整条线路都为低电平,0伏。
推挽输出模式一般应用在输出电平0和3.3伏而且需要高速切换开关状态的场合。在STM32的应用中,除了必须用开漏模式的场合,我们都习惯使用推挽输出模式。开漏输出一般应用在I2C、SMBUS通讯等需要“线与”功能的总线电路中。除此之外,还用在电平不匹配的场合,如需要输出5伏的高电平,就可以在外部接一个上拉电阻,上拉电源为5伏,并且把GPIO设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出5伏电平,如下图所示。
这里需要注意的是,在查看《STM32中文参考手册V10》中的GPIO的表格时,会看到有“FT”一列,这代表着这个GPIO口时兼容3.3V和5V的;如果没有标注“FT”,就代表着不兼容5V。
2 GPIO设置模式
根据上面的几种模式,可以大致分为 4种模式:输入、输出、复用、模拟。先来了解一下M3和M4 I/O口总的功能结构:
2.1 输入配置
当I/O端口配置为输入时:
● 输出缓冲器被禁止
● 施密特触发输入被激活
● 根据输入配置(上拉,下拉或浮动)的不同,弱上拉和下拉电阻被连接
● 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
● 对输入数据寄存器的读访问可得到I/O状态
下图给出了I/O端口位的输入配置:
浮空输入模式:GPIO_Mode_IN_FLOATING
浮空输入模式下,I/O端口的电平信号直接进入输入数据寄存器。电平进入后,不经过上下拉,在触发施密特触发器后,进入输入数据寄存器,最后由CPU读取。MCU直接读取I/O口电平,I/O的电平状态是不确定的,完全由外部输入决定;如果在该引脚悬空(在无信号输入)的情况下,读取该端口的电平是不确定的。 (接用电压表测量其引脚电压为1点几伏,这是个不确定值) 以用来做KEY识别。
输入上拉模式:GPIO_Mode_IPU
IO内部接上拉电阻,此时如果IO口外部没有信号输入或者引脚悬空,IO口默认为高电平,如果I/O口输入低电平,那么引脚就为低电平,MCU读取到的就是低电平。STM32的内部上拉是"弱上拉",即通过此上拉输出的电流是很弱的,如要求大电流还是需要外部上拉。 触发时需要低电平触发,有上拉电阻存在,使得端口为高电平,达到抗干扰作用,只接受低电平!
输入下拉模式:GPIO_Mode_IPD
IO内部接下拉电阻,此时如果IO口外部没有信号输入或者引脚悬空,IO口默认为低电平,如果I/O口输入高电平,那么引脚就为高电平,MCU读取到的就是高电平。需要高电平触发,有下拉电阻存在,使得端口为低电平,达到抗干扰作用,只接受高电平!
GPIO输入模式总结:
在输入模式时,施密特触发器打开,输出被禁止。数据寄存器每隔1个AHB1时钟周期更新一次,可通过数据寄存器GPIOx_IDR读取I/O状态。其中F1的AHB1的时钟如按默认配置一般为72MHz,F4的AHB1的时钟如按默认配置一般为180MHz。
2.2 输出配置
当I/O端口被配置为输出时:
● 输出缓冲器被激活
─ 开漏模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将端口置于高阻状态(P-MOS从不被激活)。
─ 推挽模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将激活P-MOS。
● 施密特触发输入被激活
● 弱上拉和下拉电阻被禁止
● 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
● 在开漏模式时,对输入数据寄存器的读访问可得到I/O状态
● 在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的值。
下图给出了I/O端口位的输出配置:
开漏输出模式:GPIO_Mode_Out_OD
CPU先写入输出数据寄存器中,再经过输出控制电路,最终到达端口(在此模式下,IO口也可以读取IO口电压)。在开漏输出模式时,只有N-MOS管工作,如果我们控制输出为0,低电平,则P-MOS管关闭,N-MOS管导通,使输出低电平,I/O端口的电平就是低电平,若控制输出为1时,高电平,则P-MOS管和N-MOS管都关闭,输出指令就不会起到作用,此时I/O端口的电平就不会由输出的高电平决定,而是由I/O端口外部的上拉或者下拉决定 如果没有上拉或者下拉 IO口就处于悬空状态,并且此时施密特触发器是打开的,即输入可用,通过输入数据寄存器GPIOx_IDR可读取I/O的实际状态。,I/O口的电平不一定是输出的电平。
优点:
1)输出端相当于三极管的集电极,要得到高电平需要上拉电阻才行,适合做电流型的驱动,其吸收电流的能力较强(20ma以内)
2)开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。但其也带来上升沿的延时!
3)可以将多个开漏输出的Pin,连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系。这也是I2C,SMBus等总线判断总线占用状态的原理。
(补充:什么是“线与”?: 在一个结点(线)上, 连接一个上拉电阻到电源 VCC 或 VDD 和 n 个 NPN 或 NMOS 晶体管的集电极 C 或漏极 D, 这些晶体管的发射极 E 或源极 S 都接到地线上, 只要有一个晶体管饱和, 这个结点(线)就被拉到地线电平上. 因为这些晶体管的基极注入电流(NPN)或栅极加上高电平(NMOS),晶体管就会饱和, 所以这些基极或栅极对这个结点(线)的关系是或非NOR 逻辑. 如果这个结点后面加一个反相器, 就是或 OR 逻辑.)
推挽输出模式:GPIO_Mode_Out_PP
CPU先写入输出数据寄存器中,再经过输出控制电路,最终到达端口(在此模式下,IO口也可以读取IO口电压),与开漏输出在于其在经过输出控制电路后面的MOS管不同!在推挽输出模式时,N-MOS管和P-MOS管都工作,如果我们控制输出为0,低电平,则P-MOS管关闭,N-MOS管导通,使输出低电平,I/O端口的电平就是低电平,若控制输出为1 高电平,则P-MOS管导通N-MOS管关闭,使输出高电平,I/O端口的电平就是高电平, 外部上拉和下拉的作用是控制在没有输出时IO口电平。此时施密特触发器是打开的,即输入可用,通过输入数据寄存器GPIOx_IDR可读取I/O的实际状态。I/O口的电平一定是输出的电平。
优点:
推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。
GPIO输出模式总结:
在输出模式中,输出使能,推挽模式时以双MOS管的方式工作,输出数据寄存器GPIOx_ODR可控制I/O输出高低点评。开漏模式时,只有N-MOS工作,输出数据寄存器可控制I/O输出高阻态或低电平。输出速度可配置,有2MHz\25MHz\50MHz的选项。此处的输出速度即I/O支持的高低电平状态最高切换频率,支持的频率越高,功耗越大,如果功耗要求不严格,把速度设置成最大即可。
此时施密特触发器时打开的,即输入可用,通过输入数据寄存器GPIOx_IDR可读取I/O的实际状态。
用于输出模式时,可使用上拉、下拉或悬空模式。但此时由于输出模式时引脚电平会收到ODR寄存器的影响,而ODR寄存器对应引脚的位为0,即引脚初始化后默认输出低电平,所以在这种情况下,上拉只能起到小幅提高输出电流能力,但不会影响引脚的默认状态。
2.3 复用功能配置
当I/O端口被配置为复用功能时:
● 在开漏或推挽式配置中,输出缓冲器被打开
● 内置外设的信号驱动输出缓冲器(复用功能输出)
● 施密特触发输入被激活
● 弱上拉和下拉电阻被禁止
● 在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器
● 开漏模式时,读输入数据寄存器时可得到I/O口状态
● 在推挽模式时,读输出数据寄存器时可得到最后一次写的值
下图示出了I/O端口位的复用功能配置。一组复用功能I/O寄存器允许用户把一些复用功能重新映象到不同的引脚。
开漏复用输出模式:GPIO_Mode_AF_OD
复用与不是复用的区别在于———非复用输出是cpu控制,复用功能则是从复用功能输出口上接受输出信号,可以是从外设接受输入信号输出。GPIO复用为其他外设,输出数据寄存器GPIOx_ODR无效; 输出的高低电平的来源于其它外设,施密特触发器打开,输入可用,通过输入数据寄存器可获取I/O实际状态 除了输出信号的来源改变 其他与开漏输出功能相同。
推挽复用输出模式:GPIO_Mode_AF_PP
复用功能与之前类似,输出信号来源来自复用功能输出口。与开漏复用输出在于其在经过输出控制电路后面的MOS管不同!
GPIO复用功能总结:
复用功能模式中,输出使能,输出速度可配置,可工作在开漏及推挽模式,但是输出信号源于其它外设,输出数据寄存器GPIOx_ODR无效;输入可用,通过输入数据寄存器可获取I/O实际状态,但一般直接用外设的寄存器来获取该数据信号。
用于复用功能时,可使用上拉、下拉或者浮空模式。同输出模式,在这种情况下,初始化后引脚默认输出低电平,上拉只起到小幅提高输出电流能力,但不会影响引脚的默认状态。
2.4 模拟输入配置
当I/O端口被配置为模拟输入配置时:
● 输出缓冲器被禁止
● 禁止施密特触发输入,实现了每个模拟I/O引脚上的零消耗。施密特触发输出值被强置为’0’
● 弱上拉和下拉电阻被禁止
● 读取输入数据寄存器时数值为’0’
下图示出了I/O端口位的高阻抗模拟输入配置:
模拟输入通道:GPIO_Mode_AIN
当GPIO用于模拟功能时,引脚的上、下拉电阻是不起作用的,输入没有上拉下拉电阻,这个时候即使配置了上拉或下拉模式,也不会影响到模拟信号的输入输出,且输入的是电压而非电平(电平只有高低之分,电压则是一个连续值),该输入方式可用于AD转换接受模拟信号。当GPIO引脚用于ADC采集电压的输入通道时,用作"模拟输入"功能,此时信号不经过施密特触发器,直接直接进入ADC模块,并且输入数据寄存器为空 ,CPU不能在输入数据寄存器上读到引脚状态。
除了 ADC 和 DAC 要将 IO 配置为模拟通道之外其他外设功能一律 要配置为复用功能模式。
GPIO模拟输入功能总结:
模拟输入输出模式中,双MOS管结构被关闭,施密特触发器停用,上/下拉也被禁止,其他外设通过模拟通道进行输入输出。
IO口总结:
在STM32中选用IO模式总结:
(1) 浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别
(2)带上拉输入_IPU——IO内部上拉电阻输入
(3)带下拉输入_IPD—— IO内部下拉电阻输入
(4) 模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电
(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现STM32的IO双向功能
(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的
(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)
(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)
正点原子对常用输入输出方式的作用的总结:
(1)作为普通GPIO 输入:根据需要配置该引脚为浮空输入、带弱上拉输入或带弱下拉输入,同时不要使能该引脚对应的所有复用功能模块。
(2)作为普通GPIO 输出:根据需要配置该引脚为推挽输出或开漏输出,同时不要使能该引脚对应的所有复用功能模块。
(3)作为普通模拟输入:配置该引脚为模拟输入模式,同时不要使能该引脚对应的所有复用功能模块。
(4)作为内置外设的输入:根据需要配置该引脚为浮空输入、带弱上拉输入或带弱下拉输入,同时使能该引脚对应的某个复用功能模块。
(5)作为内置外设的输出:根据需要配置该引脚为复用推挽输出或复用开漏输出,同时使能该引脚对应的所有复用功能模块。
3 外设 I/O配置模式选择
高级定时器TIM1/TIM8:
通用定时器TIM2/3/4/5:
USART:
SPI:
I2S:
I2C接口:
BxCAN:
USB:
全速USB OTG引脚配置:
SDIO:
ADC/DAC:
FSMC:
其它I/O功能:
4 F1寄存器配置
端口配置低寄存器(GPIOx_CRL) (x=A..E)
端口配置高寄存器(GPIOx_CRH) (x=A..E)
端口输入数据寄存器(GPIOx_IDR) (x=A..E)
看GPIO结构框图的上半部分,它时GPIO引脚经过上、下拉电阻后引入的,它连接到施密特触发器,信号经过触发器后,模拟信号转化为0、1的数字信号,然后存储再“输出数据寄存器GPIOx_IDR”中,通过读取该寄存器就可以了解GPIO引脚的电平状态。
端口输出数据寄存器(GPIOx_ODR) (x=A..E)
前面提到的双MOS管结构电路的输入信号,是由GPIO“端口输出数据寄存器“GPIOx_ODR”提供的,因此我们通过修改输出数据寄存器的值就可以修改GPIO引脚的输出电平。
端口位设置/清除寄存器(GPIOx_BSRR) (x=A..E)
”端口位设置/清除寄存器GPIOx_BSRR“可以通过修改输出数据寄存器的值从而影响电路的输出。
端口位清除寄存器(GPIOx_BRR) (x=A..E)
”端口位清除寄存器GPIOx_BRR“可以通过修改输出数据寄存器的值从而影响电路的输出。
端口配置锁定寄存器(GPIOx_LCKR) (x=A..E)
5 F4寄存器配置
端口模式寄存器(GPIOx_MODER)
端口输出类型寄存器(GPIOx_OTYPER)
端口输出速度寄存器(GPIOx_OSPEEDR)
端口上拉/下拉寄存器(GPIOx_PUPDR)
端口输入数据寄存器(GPIOx_IDR)
端口输出数据寄存器(GPIOx_ODR)
端口置位/复位寄存器(GPIOx_BSRR)
端口配置锁存寄存器(GPIOx_LCKR )
复位功能寄存器(低位GPIOx_AFRL & GPIOx_AFRH)
6 GPIO的初始化(F1)
我们以初始化LED为例
6.1 定义一个 GPIO_InitTypeDef 类型的结构体
GPIO_InitTypeDef GPIO_InitStructure; /*定义一个 GPIO_InitTypeDef 类型的结构体*/
一共有3个参数
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
6.2 开启 LED 相关的 GPIO 外设时钟
RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOB, ENABLE); /*开启 AHB1时钟*/
相对应的外设功能所使用的时钟在stm32f10x_rcc.h 中即可查看到,下面是部分时钟:
//APB2_peripheral
#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040)
#define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080)
#define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100)
#define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200)
#define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400)
#define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800)
#define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000)
#define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000)
#define RCC_APB2Periph_USART1 ((uint32_t)0x00004000)
#define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000)
#define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000)
#define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000)
#define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000)
#define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000)
#define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000)
#define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000)
6.3 选择要控制的 GPIO 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ; /*选择Pin9引脚*/
可选引脚为0-15 一组IO口有16个引脚
#define GPIO_Pin_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /* Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /* Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /* Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /* Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /* Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /* Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /* Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /* Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /* Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /* Pin 15 selected */
6.4 设置所选引脚的模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /*设定为推挽输出模式*/
引脚的模式共有八种,分别为模拟输入,输入浮空,输入下拉,输入上拉,开漏输出,推挽式输出,开漏复用功能,推挽式复用功能
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
6.5 设定所选管脚的速度
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /*设定速度为50MHz 高速模式*/
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
6.6 初始化GPIO
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化所设置的引脚
GPIO_Init() 是官方配置的初始化函数 第一个参数是GPIOX 第二个参数是结构体所对应GPIO各种参数的配置
7 GPIO的初始化(F4)
我们以初始化LED为例
7.1 定义一个 GPIO_InitTypeDef 类型的结构体
GPIO_InitTypeDef GPIO_InitStructure; /*定义一个 GPIO_InitTypeDef 类型的结构体*/
一共有5个参数
typedef struct
{
uint32_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOOType_TypeDef GPIO_OType; /*!< Specifies the operating output type for the selected pins.
This parameter can be a value of @ref GPIOOType_TypeDef */
GPIOPuPd_TypeDef GPIO_PuPd; /*!< Specifies the operating Pull-up/Pull down for the selected pins.
This parameter can be a value of @ref GPIOPuPd_TypeDef */
}GPIO_InitTypeDef;
7.2 开启 LED 相关的 GPIO 外设时钟
RCC_AHB1PeriphClockCmd ( RCC_AHB1Periph_GPIOB, ENABLE); /*开启 AHB1时钟*/
Q:为什么要设置时钟?
任何外设都需要时钟,51单片机,stm32,430等等,因为寄存器是由D触发器组成的,往触发器里面写东西,前提条件是有时钟输入。stm32是低功耗,他将所有的门都默认设置为disable(不使能),在你需要用哪个门的时候,开哪个门就可以,也就是说用到什么外设,只要打开对应外设的时钟就可以, 其他的没用到的可以还是disable(不使能),这样耗能就会减少。
Q:为什么 STM32 要有多个时钟源呢?
因为首先STM32本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率, 比如看门狗以及 RTC 只需要几十k的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。
相对应的外设功能所使用的时钟在stm32f4xx_rcc.h 中即可查看到,下面是部分时钟:
//RCC_AHB1_Peripherals
#define RCC_AHB1Periph_GPIOA ((uint32_t)0x00000001)
#define RCC_AHB1Periph_GPIOB ((uint32_t)0x00000002)
#define RCC_AHB1Periph_GPIOC ((uint32_t)0x00000004)
#define RCC_AHB1Periph_GPIOD ((uint32_t)0x00000008)
#define RCC_AHB1Periph_GPIOE ((uint32_t)0x00000010)
#define RCC_AHB1Periph_GPIOF ((uint32_t)0x00000020)
#define RCC_AHB1Periph_GPIOG ((uint32_t)0x00000040)
#define RCC_AHB1Periph_GPIOH ((uint32_t)0x00000080)
#define RCC_AHB1Periph_GPIOI ((uint32_t)0x00000100)
#define RCC_AHB1Periph_GPIOJ ((uint32_t)0x00000200)
#define RCC_AHB1Periph_GPIOK ((uint32_t)0x00000400)
#define RCC_AHB1Periph_CRC ((uint32_t)0x00001000)
#define RCC_AHB1Periph_FLITF ((uint32_t)0x00008000)
#define RCC_AHB1Periph_SRAM1 ((uint32_t)0x00010000)
#define RCC_AHB1Periph_SRAM2 ((uint32_t)0x00020000)
#define RCC_AHB1Periph_BKPSRAM ((uint32_t)0x00040000)
#define RCC_AHB1Periph_SRAM3 ((uint32_t)0x00080000)
#define RCC_AHB1Periph_CCMDATARAMEN ((uint32_t)0x00100000)
#define RCC_AHB1Periph_DMA1 ((uint32_t)0x00200000)
#define RCC_AHB1Periph_DMA2 ((uint32_t)0x00400000)
#define RCC_AHB1Periph_DMA2D ((uint32_t)0x00800000)
#define RCC_AHB1Periph_ETH_MAC ((uint32_t)0x02000000)
#define RCC_AHB1Periph_ETH_MAC_Tx ((uint32_t)0x04000000)
#define RCC_AHB1Periph_ETH_MAC_Rx ((uint32_t)0x08000000)
#define RCC_AHB1Periph_ETH_MAC_PTP ((uint32_t)0x10000000)
#define RCC_AHB1Periph_OTG_HS ((uint32_t)0x20000000)
#define RCC_AHB1Periph_OTG_HS_ULPI ((uint32_t)0x40000000)
#define IS_RCC_AHB1_CLOCK_PERIPH(PERIPH) ((((PERIPH) & 0x810BE800) == 0x00) && ((PERIPH) != 0x00))
#define IS_RCC_AHB1_RESET_PERIPH(PERIPH) ((((PERIPH) & 0xDD1FE800) == 0x00) && ((PERIPH) != 0x00))
#define IS_RCC_AHB1_LPMODE_PERIPH(PERIPH) ((((PERIPH) & 0x81106800) == 0x00) && ((PERIPH) != 0x00))
7.3 选择要控制的 GPIO 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; /*选择Pin9引脚*/
可选引脚为0-15 一组IO口有16个引脚
#define GPIO_Pin_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /* Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /* Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /* Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /* Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /* Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /* Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /* Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /* Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /* Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /* Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /* All pins selected */
7.4 设置所选引脚的模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; /*设定为输出模式*/
引脚的模式共有四种,分别为输入,输出,复用,和模拟模式
typedef enum
{
GPIO_Mode_IN = 0x00, /*!< GPIO Input Mode */
GPIO_Mode_OUT = 0x01, /*!< GPIO Output Mode */
GPIO_Mode_AF = 0x02, /*!< GPIO Alternate function Mode */
GPIO_Mode_AN = 0x03 /*!< GPIO Analog Mode */
}GPIOMode_TypeDef;
7.5 设定所选引脚的输出类型
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; /*设置引脚的输出类型为推挽输出*/
输出模式有两种:推挽输出和开漏输出
typedef enum
{
GPIO_OType_PP = 0x00,
GPIO_OType_OD = 0x01
}GPIOOType_TypeDef;
只有输出模式才需要配置,输入模式下不需要配置
7.6 设定所选管脚的速度
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//设定速度为100MHz 高速模式
typedef enum
{
GPIO_Low_Speed = 0x00, /*!< Low speed */
GPIO_Medium_Speed = 0x01, /*!< Medium speed */
GPIO_Fast_Speed = 0x02, /*!< Fast speed */
GPIO_High_Speed = 0x03 /*!< High speed */
}GPIOSpeed_TypeDef;
/* Add legacy definition */
#define GPIO_Speed_2MHz GPIO_Low_Speed
#define GPIO_Speed_25MHz GPIO_Medium_Speed
#define GPIO_Speed_50MHz GPIO_Fast_Speed
#define GPIO_Speed_100MHz GPIO_High_Speed
7.7 设定所选管脚的上拉与下拉
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; /*设置引脚为上拉模式*/
可设置为:上拉,下拉,与浮空
typedef enum
{
GPIO_PuPd_NOPULL = 0x00,
GPIO_PuPd_UP = 0x01,
GPIO_PuPd_DOWN = 0x02
}GPIOPuPd_TypeDef;
7.8 初始化GPIO
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化所设置的引脚
GPIO_Init() 是官方配置的初始化函数 第一个参数是GPIOX 第二个参数是结构体所对应GPIO各种参数的配置
8 stm32 GPIO速率
8.1 GPIO 引脚速度
GPIO 引脚输出速度有:GPIO_Speed_2MHz(10MHz,50MHz,100MHz)
官方一点的解释:GPIO口的驱动电路响应速度,不是输出信号的速度。输出信号的速度与程序有关,通过选择速度来选择不同的驱动电路,降低功耗控制噪声。又称输出驱动电路的响应速度:(芯片内部在I/O口的输出部分安排了多个响应速度不同的输出驱动电路,用户可以根据自己的需要选择合适的驱动电路,通过选择速度来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。)
可理解为: 输出驱动电路的带宽:即一个驱动电路可以不失真地通过信号的最大频率。(如果一个信号的频率超过了驱动电路的响应速度,就有可能信号失真。) 例如如果信号频率为10MHz,而你配置了2MHz的带宽,则10MHz的方波很可能就变成了正弦波。就好比是公路的设计时速,汽车速度低于设计时速时,可以平稳地运行,如果超过设计时速就会颠簸,甚至翻车。
关键是:GPIO的引脚速度跟应用相匹配,速度配置越高,输出驱动电路的带宽越大,噪声也越大,功耗越大,速度配置越低,输出驱动电路的带宽越小,噪声也越小,功耗越小。使用合适的驱动器可以降低功耗和噪声。我们选择的只是不同的输出驱动电路,而电路在设计好了后它本身的带宽也就确定了,也就是说这个速率(带宽)与系统时钟无关。
比如:高频的驱动电路,噪声也高,当不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。当然如果要输出较高频率的信号,但却选用了较低频率的驱动模块,很可能会得到失真的输出信号。关键是GPIO的引脚速度跟应用匹配(推荐10倍以上)。
比如:
1)USART串口,若最大波特率只需115.2k,那用2M的速度就够了,既省电也噪声小。
2)I2C接口,若使用400k波特率,若想把余量留大些,可以选用10M的GPIO引脚速度。
3)SPI接口,若使用18M或9M波特率,需要选用50M的GPIO的引脚速度。
8.2 GPIO的翻转速度
GPIO的翻转速度指:输入/输出寄存器的0 ,1 值反映到外部引脚(APB2上)高低电平的速度。F1手册上指出GPIO最大翻转速度可达18MHz。
通过简单的程序测试,用示波器观察到的翻转时间: 是综合的时间,包括取指令的时间、指令执行的时间、指令执行后信号传递到寄存器的时间(这其中可能经过很多环节,比如AHB、APB、总线仲裁等),最后才是信号从寄存器传输到引脚所经历的时间。
如:有上拉电阻,其阻值越大,RC延时越大,即逻辑电平转换的速度越慢,功耗越大。
8.3 GPIO的输出速度
GPIO 输出速度:与程序有关,(程序中写的多久输出一个信号)。
9 stm32端口重映射
重映射:为了使不同器件封装的外设 IO 功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上,目的为了让设计工程师可以更好地安排引脚的走向和功能,在 STM32 中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。简单的讲就是把管脚的外设功能映射到另一个管脚去使用,但是不是可以随便映射的,根据手册是否可以映射。
重映射技术的需求背景:
1)I/O的复用:GPIO和内置外设共用引出管脚
2)I/O的重映射:复用功能(AFIO)从不同的GPIO管脚引出
3)方便了PCB的设计,减少了信号交叉干扰
4)分时复用某些外设,虚拟地增加了端口数目
5)为了使不同器件封装的外设IO功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。STM32中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。
以串口1为例:
上图中的,Remap对应的I/O就是可以重映射到的I/O,Default就是该I/O默认可复用的功能。从上图中可以看出 串口1 可以重映射到 PB6和PB7引脚,也就是说如果PA9和PA10引脚不好用的时候,或者已经被占用了;那么可以用PB6和PB7来实现串口1的功能。
9.1 AFIO重映射的操作步骤
1)使能被重新映射到的I/O端口时钟
2)使能被重新映射的外设时钟
3)使能AFIO功能的时钟(勿忘)
4)进行重映射
注意第3步,使能AFIO功能时钟,为什么需要使能这个时钟 & 什么时候需要使能这个时钟, 可以参见下面这个回答
那么,问题来了!AFIO 是什么?AFIO 时钟什么时候需要开启?
我们从《STM32中文参考手册_V10》中找到:对寄存器 AFIO_EVCR、AFIO_MAPR 和 AFIO_EXTICRX 进行读写操作前,应当首先打开 AFIO 的时钟(设置 APB2 外设时钟使能寄存器 RCC_APB2ENR)。
也就是说:当你需要配置 AFIO 这些寄存器的时候,就需要把 RCC_APB2ENR 寄存器的 AFIO 位置‘1’打开 AFIO 时钟。
跟 AFIO 相关的寄存器有:
1、 事件控制寄存器(AFIO_EVCR)
2、 复用重映射和调试I/O 配置寄存器(AFIO_MAPR)
3、 外部中断配置寄存器1(AFIO_EXTICR1)
4、 外部中断配置寄存器2(AFIO_EXTICR2)
5、 外部中断配置寄存器3(AFIO_EXTICR3)
6、 外部中断配置寄存器4(AFIO_EXTICR4)
看看这些寄存器的定义,我们就明白,这些寄存器是用于“事件控制”、“重映射”、“调试IO配置”、“外部中断”的。例如 AFIO_EXTICRX 用于选择 EXTIx 外部中断的输入源。
总结:当我们需要配置这些 AFIO 寄存器的时候,就需要打开 RCC_APB2ENR 寄存器的 AFIO 时钟,而不是用到引脚复用功能的时候打开。
9.2 部分重映射 & 完全重映射
部分重映射:功能外设的部分引脚重新映射,还有一部分引脚是原来的默认引脚。
完全重映射:功能外设的所有引脚都重新映射。
引脚重映射配置过程(串口3为例):
1)使能GPIO时钟(重映射后的IO);
2)使能功能外设时钟(例如串口3);
3)使能AFIO时钟。重映射必须使能AFIO时钟:
4)开启重映射。
GPIO_PinRemapConfig(GPIO_FullRemap_USART3, ENABLE);
//根据第一个参数,来确定是部分重映射还是全部重映射
以串口3为例,如果想部分映射:GPIO_PinRemapConfig()的第一个参数取值应该是: GPIO_PartialRemap_USART3,如果是完全映射就是:GPIO_FullRemap_USART3