STM32:GPIO
1 GPIO引脚电路
IO_port输入输出模式的配置 等于 对IO_port电路连接方式的配置;
IO_port的弱上下拉电阻默认30-50kΩ;单个GPIO的电流最大值为20mA;全部IO的电流总值约120mA;具体见数据手册;
1.1 输入模式
上拉输入:使能弱上拉电阻的连接,断开下拉电阻的连接;以防输入电压过高保护内部电路;
下拉输入:断开上拉电阻的连接,使能弱下拉电阻的连接;以防输入电压过低保护内部电路;
浮空输入:默认浮空输入,不使能上下拉电阻,浮空输入信号,较为准确反应外部输入信号;
1.2 输出模式
推挽输出:默认推挽输出,将P-MOS管和N-MOS管以推挽方式连接;通过两个MOS管的导通与截止来输出高低电平;配置上下拉电阻不使能;
特点是既可以消耗负载的灌电流,也可以向负载输出拉电流,开关时间快;
两个MOS管工作原理相同,作用相反,误差中和后,可以减小电路误差积累,增加电路稳定;
开漏输出:P-MOS管始终截止,通过N-MOS管结合上下拉电阻,控制输出高低电平;配置上下拉电阻同时使能;
开漏输出的"开漏"即"漏极开路";即MOS管的Drain漏极断开,由内部或外部的上拉电阻来输出高电平的模式;
特点是输出的高电平由弱上拉电阻决定,输出电流小,负载驱动能力弱;输出低电平时可以消耗负载的灌电流,驱动能力稳定;
如果觉得开漏输出的输出驱动能力弱,可以自己外加一个强上拉电阻,这样负载驱动能力就变强了;
“线与”逻辑:将多个开漏输出并连至一个上拉电阻,当其中一个输出低电平时,其他输出也被接到地了;iic_sda就是这样的;
1.4 电平范围
仅对于STM32而言,其兼容的TTL电平和CMOS电平范围如下:
TTL电平:大于2.4V(in:2.0v)为高电平;低于0.8V(in:1.2v)为低电平;噪声容限0.4V;TTL为流控器件,电阻小功耗大驱动强;
CMOS电平:大于0.7V为高电平;小于0.2V为低电平;噪声容限0.2V;功耗较小,电压阈值宽;
STM32H7的GPIO对TTL电平和CMOS电平的兼容问题 - STM32H7 - 硬汉嵌入式论坛 - Powered by Discuz! (armbbs.cn)
2 GPIO ports
GPIO全称 general purpose input and output ports,通用输入输出端口;
STM32一共有7组GPIO port:GPIOA-GPIOG,每组GPIO port 有16个 pin;每组GPIO port都有一组寄存器;
GPIO寄存器的控制单位是GPIO port,而不是pin;所以寄存器的最小处理单位是一个16位的字长(0xFFFF);
GPIO_TypeDef()
/*stm32f10x.h
*GPIO都属于APB2总线,使用的时候要使能APB2总线的时钟源;
*uint32_t 使用需要 #include <stdint.h>*/
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
/*core_cm3.h*/ #define __IO volatile
2.1 CRL端口配置低寄存器 control register low,CRH端口配置高寄存器 control register high;
GPIO_InitTypeDef()
/***stm32f10x_gpio.h 定义了CRH和CRL寄存器需要的参数;***/
typedef struct
{
uint16_t GPIO_Pin; /*用16位bit的每一位分别表示一个引脚*/
GPIOSpeed_TypeDef GPIO_Speed; /*用2位bit来表示输出模式的最大速度,寄存器中的MODE[1:0]*/
GPIOMode_TypeDef GPIO_Mode; /*用2位bit来表示输入输出的电阻连接,寄存器中的CNF[3:2]*/
}GPIO_InitTypeDef;
#define GPIO_Pin_0 ((uint16_t)0x0001) /*0000 0000 0000 0001b*/
#define GPIO_Pin_1 ((uint16_t)0x0002) /*0000 0000 0000 0010b*/
#define GPIO_Pin_2 ((uint16_t)0x0004) /*0000 0000 0000 0100b*/
#define GPIO_Pin_3 ((uint16_t)0x0008) /*0000 0000 0000 1000b*/
#define GPIO_Pin_4 ((uint16_t)0x0010) /*0000 0000 0001 0000b*/
#define GPIO_Pin_5 ((uint16_t)0x0020) /*0000 0000 0010 0000b*/
#define GPIO_Pin_6 ((uint16_t)0x0040) /*0000 0000 0100 0000b*/
#define GPIO_Pin_7 ((uint16_t)0x0080) /*0000 0000 1000 0000b*/
#define GPIO_Pin_8 ((uint16_t)0x0100) /*0000 0001 0000 0000b*/
#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 */
#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))
typedef enum
{
GPIO_Speed_10MHz = 1, /*output reg MODE[1:0]*/
GPIO_Speed_2MHz, /*output reg MODE[1:0]*/
GPIO_Speed_50MHz /*output reg MODE[1:0]*/
}GPIOSpeed_TypeDef;
#define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_Speed_10MHz) || ((SPEED) == GPIO_Speed_2MHz) || \
((SPEED) == GPIO_Speed_50MHz))
typedef enum
{ GPIO_Mode_AIN = 0x0, /*0000 0000b [4]0 input [3:2]CNF*/
GPIO_Mode_IN_FLOATING = 0x04, /*0000 0100b [4]0 input [3:2]CNF*/
GPIO_Mode_IPD = 0x28, /*0010 1000b [4]0 input [3:2]CNF,[5]下拉*/
GPIO_Mode_IPU = 0x48, /*0100 1000b [4]0 input [3:2]CNF,[6]上拉*/
GPIO_Mode_Out_OD = 0x14, /*0001 0100b [4]1 output,[3:2]CNF*/
GPIO_Mode_Out_PP = 0x10, /*0001 0000b [4]1 output,[3:2]CNF*/
GPIO_Mode_AF_OD = 0x1C, /*0001 1100b [4]1 output,[3:2]CNF*/
GPIO_Mode_AF_PP = 0x18 /*0001 1000b [4]1 output,[3:2]CNF*/
}GPIOMode_TypeDef;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
#define IS_GET_GPIO_PIN(PIN) (((PIN) == GPIO_Pin_0) || ((PIN) == GPIO_Pin_1) ||((PIN) == GPIO_Pin_2) || \
((PIN) == GPIO_Pin_3) || ((PIN) == GPIO_Pin_4) || ((PIN) == GPIO_Pin_5) || \
((PIN) == GPIO_Pin_6) || ((PIN) == GPIO_Pin_7) || ((PIN) == GPIO_Pin_8) || \
((PIN) == GPIO_Pin_9) || ((PIN) == GPIO_Pin_10) || ((PIN) == GPIO_Pin_11) || \
((PIN) == GPIO_Pin_12) ||((PIN) == GPIO_Pin_13) || ((PIN) == GPIO_Pin_14) || \
((PIN) == GPIO_Pin_15))
typedef enum
{ Bit_RESET = 0,
Bit_SET
}BitAction;
在usart外设使用的时候是AF_PP_output加上IN_FLOATING_input;在spi外设使用的时候,把MI、MO都配置为AF_PP了,这咋也行呢?
因为对于双向复用端口,规定将其配置成复用输出,外设输入驱动器默认会被自动配置成浮空输入模式,中文手册是建议自己主动配置的;
GPIO_Init( )
/***stm32f10x_gpio.c***
***配置相应port的CRL和CRH;
***这里的输入输出模式在代码中新设置了一个bit来判断,但是在赋值寄存器的时候,芯片大概率是通过MODE[1:0]的值来物理区分的;***/
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;
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));
//currentmode保留了GPIO_Mode[3:0];
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
//如果GPIO_Mode[4]为1,表示为输出模式;
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/*if(输出模式),将CNF[3:2]和MODE[1:0]的信息保存到currentmode[3:0]*/
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;//currentmode或上GPIO_Speed[1:0];
}
/*以下部分为CRL Configuration*/
//if(是低8位pin);
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;、//temreg存放CRL寄存器的信息;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)//pinpos为几,表示引脚几;
{
pos = ((uint32_t)0x01) << pinpos;
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;//当pos与结构体pin脚都为1,则引脚为pos执行if语句;
if (currentpin == pos)
{
pos = pinpos << 2;//pos:引脚对应的CRL配置位,左移<<2 == *4;
pinmask = ((uint32_t)0x0F) << pos;//pinmask:引脚对应的CRL[3:0]置1,其他位清0后与0为0;
tmpreg &= ~pinmask;//temreg中对应引脚的[3:0]清0
tmpreg |= (currentmode << pos);//temreg中对应引脚的[3:0]配置成currentmode[3:0]
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)//if(输入连下拉电阻)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);//BRR的对应bit置0,连接下拉电阻;
}
else
{
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) //if(输入连上拉电阻)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos); //BSRR的对应bit置1,连接上拉电阻;
}
}
}
}
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;
}
}
2.2 LCKR lock register 锁定寄存器
锁存对应端口的CRL、CRH寄存器的配置,对应的CRL、CRH配置将会持续到下次系统复位信号来临;
GPIO_PinLockConfig()
/**
* @brief Locks GPIO Pins configuration registers.
* @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
* @param GPIO_Pin: specifies the port bit to be written.
* This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
* @retval None
*/
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint32_t tmp = 0x00010000;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
tmp |= GPIO_Pin;
/* Set LCKK bit */
GPIOx->LCKR = tmp;
/* Reset LCKK bit */
GPIOx->LCKR = GPIO_Pin;
/* Set LCKK bit */
GPIOx->LCKR = tmp;
/* Read LCKK bit*/
tmp = GPIOx->LCKR;
/* Read LCKK bit*/
tmp = GPIOx->LCKR;
}
2.3 IDR input data register 端口输入数据寄存器,ODR output data register 端口输出数据寄存器
GPIO_ReadInputDataBit()
/*以下代码位于stm32f10x_gpio.c中*1 读取IDR寄存器某一位的值,即pin值;*2 读取IDR寄存器的值,即port值;*/
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));
/*先读取整个IDR寄存器,然后通过&来读取bit,IDR寄存器的最小处理单位是16bit*/
if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
{
bitstatus = (uint8_t)Bit_SET;
}
else
{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
GPIO_ReadInputData()
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
return ((uint16_t)GPIOx->IDR);
}
GPIO_ReadOutputDataBit()
/*以下代码位于stm32f10x_gpio.c中;*/
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t bitstatus = 0x00;
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
/*先读取整个ODR寄存器,然后通过&来读取bit,ODR寄存器的最小处理单位是16bit*/
if ((GPIOx->ODR & GPIO_Pin) != (uint32_t)Bit_RESET){
bitstatus = (uint8_t)Bit_SET;
}
else{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
GPIO_ReadOutputData()
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
{
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
return ((uint16_t)GPIOx->ODR);
}
GPIO_Write()
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)
{
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
GPIOx->ODR = PortVal;
}
2.4 BSRR bit set/reset register 端口置位/复位寄存器,BRR bit reset register 端口复位寄存器;
GPIO_SetBits( )
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRR = GPIO_Pin;
}
GPIO_ResetBits( )
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BRR = GPIO_Pin;
}
GPIO_WriteBit( )
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
{
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_BIT_ACTION(BitVal));
/*引脚置1,通过置1BSRR对应位;引脚清0,通过置1BRR对应位;
*虽然BSRR和BRR都可以用来配置单bit位,但是它们也是通过16bit长度来设置的*/
if (BitVal != Bit_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BRR = GPIO_Pin;
}
}
3 GPIO的复用和重映射寄存器
3.1 GPIO的复用
各种外设的复用功能,输入输出的配置举例具体见中文参考手册8.1.11小节;
外设输入通常配置成浮空输入,外设输出通常配置为复用推挽、复用开漏;
GPIO口复用之后,GPIO的硬件就连接到外设的DR了,要是外设没使能则IO口输出不定值;
3.2 GPIO的重映射
重映射功能具体见<中文参考手册>8.3和8.4小节;
8.3小节主要是列出了外设的重映射引脚;8.4小节是外设的重映射寄存器的配置;
3.1.1 外设的重映射功能需要通过AFIO_MAPR寄存器来映射到GPIO口;然后配置GPIO口复用该外设;这时候就可以使用该外设了;
3.1.2 芯片为GPIO的复用外设提供了16个可设置的中断,对应16个引脚号;
AFIO_EXTICRx中断寄存器配置端口号;EXTICR1配置引脚[3:0]的端口号,EXTICR4配置引脚[15:12]的端口号;
3.1.3 外设应该有外设自己的中断函数的吧,这里为什么又为复用的外设提供了16个中断呢?这些中断对应哪些中断处理函数呢?
3.3 GPIO的位带操作
将目标寄存器的特定bit重映射到bit-band位带区域的对应地址上,对位带区域对应32位地址的操作等于对目标寄存器对应位的操作;
3.3.1 范围
[ 0x2000 0000 - 0x200F FFFC ],SRAM的1MBytes寻址范围,共有8Mbits物理存储空间;
[ 0x2200 0000 - 0x23FF FFFC ],位带区域地址;32MBytes寻址范围,共有256Mbits物理存储空间;
膨胀后原先寄存器里的每一bit现在都有专门一个32bit空间可存储;
[ 0x4000 0000 - 0x400F FFFC ],外设的1MBytes寻址范围,共有8Mbits物理存储空间;
[ 0x4200 0000 - 0x43FF FFFC ],位带区域地址;32MBytes寻址范围,共有256Mbits物理存储空间;
膨胀后原先寄存器里的每一bit现在都有专门一个32bit空间可存储;
3.3.2 公式
bit_band_addr = bit_band_base_addr + byte_offset*32 + bit_offset*4 ;
bit_band_addr : bit_band位带区映射地址;
bit_band_base_addr : bit_band位带区基地址,外设bit_band基地址0x4200 0000;
byte_offset: 目标寄存器地址偏移量;*32表示bit_band存储空间膨胀了32倍;
bit_offset: 目标寄存器bit存储空间的所在位数;*4是因为32bit存储空间寻址4个寄存器对齐,那不就是4地址对齐嘛;
3.3.3 地址对应demo
bit_band32bits地址内的数据LSB有效;
4 通用GPIO的示例代码
main()
#include "delay.h"
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_Struct;
//portA_pin1 input;
GPIO_Struct.GPIO_Pin = GPIO_Pin_1;
GPIO_Struct.GPIO_Mode =GPIO_Mode_IPD ;
GPIO_Init(GPIOA, &GPIO_Struct);
//portA_pin2 output;
GPIO_Struct.GPIO_Pin = GPIO_Pin_2;
GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_Struct);
while(1)
{
GPIO_SetBits(GPIOA,GPIO_Pin_2);
delay_ms(100);
GPIO_ResetBits(GPIOA,GPIO_Pin_2);
delay_ms(100);
}
}
5 小结
内核的函数接口标准都是CMSIS,了解了GPIO外设的原理,也就了解了其他外设是如何封装的;
GPIO使用时先确定是否为外设复用;目的是确定输入输出数据是给外设DR处理,还是GPIO的DR处理;
然后确定IO的输入输出模式;目的是通过软件配置,选择端口在芯片内部的电路连接方式;
过一段时间重新来看,于是删改了一下,感觉自己写的也太冗长了,很简单的功能我为什么要写的这么繁琐细碎呢;
之所以现在觉得太繁琐了是因为知道重点在哪里了,可是一开始没有头绪只能通过繁琐细碎来堆积问题范围;
如果重新写一遍也只是重复一遍而已,大约没有什么变化;