参考文章 (参考芯片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_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库内有寄存器信息
步骤与库函数相似
- 使能时钟
RCC->AHB1ENR |= 1<<5;
- 初始化参数
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;
- 控制ORC来控制io接口
GPIOF->ODR |= 1<<9;
GPIOF->ODR |= 1<<10;
蜂鸣器
蜂鸣器的硬件连接:
io口只能传一个信号,不能作为驱动,因为其电流太小。需要通过放大器放大后作为驱动来驱动蜂鸣器,不过浮空状态会产生一些误差。下拉一个电阻来让小电流放电。一样的流程控制蜂鸣器
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);
按键控制
电路结构
根据按下的时候数据有效可以知道,要将一开始的时候设置为按下的相反
模式区别:
按键扫描输入
- 可以连续按,
如果按下了
延时10ms,因为按下去以后有一些抖动,
判断如果按下,就返回值
没按下,就不返回 - 不允许连续按
用static 在第一次生成的时候初始化在外部
延时判断真的按下
判断上一次是否松开
松开且按下-> 有效
设置外部的key up
代码实现
寄存器
IO口复用和映射
中文操作通用io
一个gpio可以作为内置外设复用引脚
内置外设如串口,ADC啥的
目的可以节约引脚
寻找复用:查表
芯片手册的Alternate functions就是复用信息
复用原理:通过复用器连接到内置外设模块;一次只允许一个外设的复用功能(AF)连接到对应的io口,确保同一个io和外设之间不会发生冲突。
复用器的原理
用到两个寄存器每个32位,一共映射16个io口
配置到所需的映射线
库函数可以直接配置名字: