STM32-GPIO篇

Posted 一直在路上的Tom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32-GPIO篇相关的知识,希望对你有一定的参考价值。

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功能时钟,为什么需要使能这个时钟 & 什么时候需要使能这个时钟, 可以参见下面这个回答

        以上是关于STM32-GPIO篇的主要内容,如果未能解决你的问题,请参考以下文章

stm32 GPIO跑马灯,求精讲啊!!!

关于STM32 GPIO->BSRR GPIO->BRR的问题

STM32入门笔记——GPIO的初始化

STM32入门笔记——GPIO的初始化

STM32F0xx_GPIO配置详细过程

STM32 CubeMX 学习:001-GPIO的使用