参考文章 (参考芯片stm32f407zgt6)
有7组io口每组io口有16个io,一共16*7 = 112个io
从GPIOA到GPIOG
引脚还可以复用为外设

GPIO介绍

  • GPIO(general porpose intput output):通用输入输出端口的简称。可以通过软件控制其输出和输入。stm32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通信,控制以及数据采集的功能。
  • 上拉和下拉:一般作用于断开的端口,让断开的端口能有信号,上拉就是当断开的时候这个端口的电压信息是VDD(高电平);下拉就是当断开的时候端口是接地(低电平)
  • TTL施密特触发器:
    作用:能容许噪音地进行模数信号转换

GPIO工作模式

五伏容忍:io structure FT

输入

浮空输入模式(浮空状态称为高阻态)

上下拉电阻的开关关闭没有上下拉的情况,所以输入的信号完全取决于io口的外部信号。浮空的意思就是不确定,所以这个模式在没有输入的时候信号是不确定的,电路是一个整体,所以外部在进行电信号传播的时候会出现电信号跳变的情况,只有当io口给了确定信号的时候信号才会确定下来。
信号通过TTL施密特触发器,然后将信号存储到输入数据寄存器中

上拉输入

和浮空输入的区别是上拉电阻的开关合上了,所以在没有信号输入的时候,io口上一直都是高电平信号,相比于浮空输入更加的稳定,这个时候有效的电平是低电平
当输入信号是一个低电平的时候,此时O点的电平的电平就会变成低电平,那么VDD和O点之间形成了电势差,但是因为上拉电阻的存在,所以不会出现一个大电流。此时单片机读取到的一个电平就是一个低电平。在上拉输入的情况下,低电平的是能够非常明显的读取到的。

下拉输入

与上拉输入相反,不加以赘述
当输入信号是一个低电平的时候,此时O点的电平的电平就会变成低电平,那么VDD和O点之间形成了电势差,但是因为上拉电阻的存在,所以不会出现一个大电流。此时单片机读取到的一个电平就是一个低电平。在上拉输入的情况下,低电平的是能够非常明显的读取到的。
其有效信号是高电平

模拟输入模式 (片上外设

这个输入读到了片上外设模块(以上四个输入都是读进寄存器的)
这条路没有经过施密特触发器,而是直接将信号通到了外设,可以让AD采集io口上面的真实电压。这个输入方式没有外接额外消耗电源,可以省电。

输出

开漏输出模式

Nmos管高电平导通,低电平关闭
从输出寄存器到输出控制电路,当通向N-mos管的时候高电平导通,在io口下拉出低电平信号;当低电平的时候,io口上面是低电平的时候,信号浮空,由外界决定。

  • 可以在外部来一个上拉,这样子可以变成可以控制上拉电压的输出输出
  • 开漏输出的是实质是一个OD门,是用N-mos管的漏极输出。OD门有一个非常重要的特性就是可以实现线与的功能,简单来说,就是在像IIC这样的总线协议中,只要有一个给低电平,那么总线都会被拉低

推挽输出模式

当输入信号为高电平时,N型MOS管会导通,P型MOS管会截止,此时输出信号为低电平;当输入信号为低电平时,N型MOS管会截止,P型MOS管会导通,此时输出信号为高电平。通过这种方式,推挽输出模式可以实现高低电平输出,同时具有较强的驱动能力和稳定性。
MOS管在推挽输出模式中的另一个重要作用是保护电路。由于MOS管具有很高的输入阻抗和输出阻抗,可以有效地隔离输入信号和输出负载,从而防止电路受到外界干扰和负载变化的影响。此外,MOS管还具有较好的电压开关特性和热稳定性,可以保证电路的安全和可靠性。

复用

参考文章 所谓“复用功能”,是指单片机的引脚既可以做普通GPIO使用,也可以作为内部外设控制器的引脚来使用
例如:PA5可以做为普通GPIO来使用;其次,如果作为外设的引脚,它可以作为SPI1的时钟(SPI1_SCK)、DAC的输出通道1(DAC_OUT1)或者ADC的输入通道5(ADC12_IN5)
普通和复用区别在于控制输出的信号来源:(普通)推挽输出控制MOS管的信号来自输出数据寄存器,而复用推挽输出的控制信号来自单片机的内置外设控制器(比如SPI1)

控制复用推挽输出电路所使用的内置外设控制器,需要通过编程来完成。下面以STM32系列单片机为例,介绍如何控制常用的SPI、I2C、USART和GPIO控制器。
SPI控制器:通过STM32的SPI库函数,可以控制SPI控制器来控制复用推挽输出电路。具体的控制流程为:初始化SPI控制器 -> 配置SPI通信参数 -> 使能SPI控制器 -> 写入数据到SPI发送缓存区 -> 等待数据发送完成 -> 读取SPI接收缓存区中的数据。
I2C控制器:通过STM32的I2C库函数,可以控制I2C控制器来控制复用推挽输出电路。具体的控制流程为:初始化I2C控制器 -> 配置I2C通信参数 -> 使能I2C控制器 -> 向I2C发送设备地址和寄存器地址 -> 等待I2C传输完成 -> 读取I2C接收缓存区中的数据。
USART控制器:通过STM32的USART库函数,可以控制USART控制器来控制复用推挽输出电路。具体的控制流程为:初始化USART控制器 -> 配置USART通信参数 -> 使能USART控制器 -> 写入数据到USART发送缓存区 -> 等待数据发送完成 -> 读取USART接收缓存区中的数据。
GPIO控制器:通过STM32的GPIO库函数,可以控制GPIO控制器来控制复用推挽输出电路。具体的控制流程为:初始化GPIO控制器 -> 配置GPIO引脚的输入输出模式 -> 将GPIO引脚设置为高电平或低电平。
总之,控制复用推挽输出电路所使用的内置外设控制器,需要根据具体的控制器类型和单片机型号来选择相应的库函数,并按照相应的控制流程进行编程。

开漏复用和推挽复用都是相似的复用操作。

代码实现

走马灯

库函数版本

所需库
  • stm32f4xx_gpio.c io口的库
  • stm32f4xx_rcc.c
  • stm32f4xx_syscfg.c 时钟
  • stm32f4xx_usart.c 串口
初始化io口void GPIO init
GPIO_InitTypeDef (结构体,对引脚初始化的定义
typedef struct
{
  uint32_t GPIO_Pin;             (指定io口1到16) /*!< 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;
GPIO_TypeDef

对于一组io口寄存器的定义

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint16_t BSRRL;    /*!< GPIO port bit set/reset low register,  Address offset: 0x18      */
  __IO uint16_t BSRRH;    /*!< GPIO port bit set/reset high register, Address offset: 0x1A      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

内置有,可以直接选择调用

GPIO 输出低电平架构 gpio dio_stm32

GPIO_Init()
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);
 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 */
   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 */
       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 RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph));

  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    RCC->AHB1ENR |= RCC_AHB1Periph;
  }
  else
  {
    RCC->AHB1ENR &= ~RCC_AHB1Periph;
  }
}
  • RCC_AHB1Periph
    - NewState

ENABLE , DISABLE使能和不使能

位操作
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 判断io位是否设置
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); 读取所有管脚的输入状态

原函数实现

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  uint8_t bitstatus = 0x00;

  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GET_GPIO_PIN(GPIO_Pin));

  if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
  {
    bitstatus = (uint8_t)Bit_SET;// 1
  }
  else
  {
    bitstatus = (uint8_t)Bit_RESET;//0
  }
  return bitstatus;
}



uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

  return ((uint16_t)GPIOx->IDR);
}

输出类似

uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)

设置输出电平函数

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 高电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); 低电平


void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

寄存器

stm32f4xx.h库内有寄存器信息
步骤与库函数相似

  1. 使能时钟

RCC->AHB1ENR |= 1<<5;
  1. 初始化参数
GPIOF->MODER &= ~(3<<2*9);
	GPIOF->MODER &= ~(3<<2*10);
	GPIOF->MODER |= 1<<2*9;
	GPIOF->MODER |= 1<<2*10;
	GPIOF->OSPEEDR &= ~(3<<2*9);
	GPIOF->OSPEEDR &= ~(3<<2*10);
	GPIOF->OSPEEDR |= 1<<2*9;
	GPIOF->OSPEEDR |= 1<<2*10;
	GPIOF->OTYPER&= ~(1<<9);
	GPIOF->OTYPER&= ~(1<<10);
	GPIOF->OTYPER|= 0<<9;
	GPIOF->OTYPER|= 0<<10;
	GPIOF->PUPDR&= ~(3<<2*9);
	GPIOF->PUPDR&= ~(3<<2*10);
	GPIOF->PUPDR|= 1<<2*9;
	GPIOF->PUPDR|= 1<<2*10;
  1. 控制ORC来控制io接口
GPIOF->ODR |= 1<<9;
	GPIOF->ODR |= 1<<10;

蜂鸣器

蜂鸣器的硬件连接:

io口只能传一个信号,不能作为驱动,因为其电流太小。需要通过放大器放大后作为驱动来驱动蜂鸣器,不过浮空状态会产生一些误差。下拉一个电阻来让小电流放电。

GPIO 输出低电平架构 gpio dio_stm32_02


一样的流程控制蜂鸣器

void beepinit(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOF, &GPIO_InitStructure);
}
GPIO_ResetBits(GPIOF,GPIO_Pin_8);

按键控制

电路结构

GPIO 输出低电平架构 gpio dio_单片机_03


GPIO 输出低电平架构 gpio dio_复用_04


根据按下的时候数据有效可以知道,要将一开始的时候设置为按下的相反

模式区别:

按键扫描输入

  1. 可以连续按,
    如果按下了
    延时10ms,因为按下去以后有一些抖动,
    判断如果按下,就返回值
    没按下,就不返回
  2. 不允许连续按
    用static 在第一次生成的时候初始化在外部
    延时判断真的按下
    判断上一次是否松开
    松开且按下-> 有效
    设置外部的key up

代码实现

寄存器

IO口复用和映射

中文操作通用io

一个gpio可以作为内置外设复用引脚

内置外设如串口,ADC啥的

目的可以节约引脚

寻找复用:查表

GPIO 输出低电平架构 gpio dio_单片机_05


芯片手册的Alternate functions就是复用信息

复用原理:通过复用器连接到内置外设模块;一次只允许一个外设的复用功能(AF)连接到对应的io口,确保同一个io和外设之间不会发生冲突。

复用器的原理

用到两个寄存器每个32位,一共映射16个io口

配置到所需的映射线

GPIO 输出低电平架构 gpio dio_物联网_06


GPIO 输出低电平架构 gpio dio_单片机_07


库函数可以直接配置名字:

GPIO 输出低电平架构 gpio dio_复用_08