本帖最后由 susutata 于 2022-10-27 15:00 编辑
#申请原创#@21小跑堂
APM32外设GPIO的配置和应用
以下内容主要以F407xx系列为例。
01 PIN脚类型和结构
在讲GPIO之前,我们先看下APM32中的PIN脚类型。
## PIN脚类型
### PIN类型 - P > F407xx中线性调压器为备份域和待机电路以外的所有数字电路供电,调压器输出电压约为 1.3 V。 > 此调压器需要将两个外部电容连接到专用引脚 VCAP_1 和 VCAP_2。> 在调压器使能情况下,这两个引脚输出1.3V电压。
### PIN类型 - I > BYPASS_REG接VSS使能内部调压器,BYPASS_REG接VDD停用内部调压器。 > 若停用调压器,则必须从VCAP1和VCAP2引脚提供1.3V外部电压。
### PIN类型 - I/O > 注意如果使用了STDA类型的IO口,需要留意3.3V的电压容忍值。
02 APM32的GPIO
General-Purpose Input Output,通用型输入输出的,也简称I/O口,有时也简写为IO口。用于电信号的传递,以实现与外部器件的通信、控制外部器件或者采集外部器件数据的功能。
## GPIO的结构
F407xx的GPIO结构包括:
1. 输入钳位保护;
2. 上拉/下拉电阻;
3. 史密斯触发器;
4. PMOS/NMOS结构;
5. 输出选择结构;
6. 输入/输出寄存器;
7. GPIO置位/复位寄存器;
8. 模拟外设/复用外设。
> 在工作模式章节中,会结合这张结构图讲解GPIO的各种工作模式,这里只需了解基本结构。
## GPIO的功能
### 通用
复位后,调试引脚处于复用功能上拉/下拉状态:
- PA15:JTDI 处于上拉状态
- PA14:JTCK/SWCLK 处于下拉状态
- PA13:JTMS/SWDAT 处于下拉状态
- PB4:NJTRST 处于上拉状态
- PB3:JTDO 处于浮空状态
> 1.在复位期间及复位刚刚完成后,复用功能尚未激活时,IO 端口会被配置为输入浮空模式。> 2.输入数据寄存器 (GPIOx_IDATA) 每隔 1 个 AHB1 时钟周期捕获一次 IO 引脚的数据。
### 复用(Alternate functions)
MCU的外设引脚与GPIO口共用,默认作为IO口,但可配置作为多种外设用途。这种配置GPIO口为特定外设功能引脚的操作就叫做复用。F407xx每个 IO 引脚都有一个复用器,且采用 16 路复用功能输入,可通过相应AF寄存器进行配置。
完成复位后,所有 IO 都会连接到系统的复用功能 0 (AF0); 外设的复用功能映射到 AF1 至 AF13,AF14保留; AF15是Cortex™-M4F EVENTOUT功能的映射。
> 使用ADC和DAC外设,只需把IO口配置为模拟通道。
### 锁定
该功能会冻结GPIOx的控制寄存器(包括GPIOx_MODE、GPIOx_OTYPE、GPIOx_OSPEED、GPIOx_PUPD、GPIOx_AFL 和 GPIOx_AFH)。
> 锁定的是工作模式配置,并非输出值。
### 重映射(Remap)
重映射就是将引脚功能重新定义到其他引脚上去。在F1xx系列的芯片中还有“重映射”这一概念,后续的F4xx,L0xx等都是作为“附加功能”,直接由外设寄存器进行配置来映射。
## GPIO的工作模式
根据IO口的特性,我们可以配置IO口为多种工作模式。
> 每个 IO 端口位均可自由编程,但 IO 端口寄存器必须按 32 位字、半字或字节进行访问。> 以确保在读取和修改访问之间发生中断请求也不会有问题。
### 浮空输入
如图中蓝色路线所示,该模式下IO端口的电平信号直接进入输入数据寄存器(GPIOx_IDATA),MCU读取到的IO口电平不确定。
> 如果在引脚处于悬空的情况下,那么浮空输入端口的电平是不确定的,由外部环境决定。
### 上拉输入
如图中蓝色路线所示,该模式下IO内部接入上拉电阻,如果IO口外部没有信号输入或者悬空,则IO口默认为高电平。如果此时IO口有输入低电平,那么引脚就为低电平,MCU读取到的就是低电平。
> APM32的内部上拉是"弱上拉",即通过此上拉输出的电流很弱,如需求大电流还是需要接外部上拉。
### 下拉输入
如图中蓝色路线所示,该模式下IO内部接入下拉电阻,如果IO口外部没有信号输入或者悬空,则IO口默认为低电平。如果此时IO口有输入高电平,那么引脚就为高电平,MCU读取到的就是高电平。
### 模拟模式
> 当GPIO用于模拟功能时,引脚的上、下拉电阻是不起作用的,这个时候即使配置了上拉或下拉模式,也不会影响到模拟信号的输入输出。
如图中蓝色路线所示,该模式用于GPIO作为ADC采集电压的输入通道时,此时信号不经过施密特触发器,直接直接进入ADC外设中,并且输入数据寄存器为空 ,MCU不能在GPIOx_IDATA上读到引脚状态。
如图中蓝色路线所示,该模式用于GPIO作为DAC输出电压的输出通道时,信号直接从外设输出到IO口。
### 开漏输出
如图中蓝色路线所示,该模式下只有N-MOS管工作。如果控制输出为低电平,则P-MOS管关闭,N-MOS管导通,使IO输出低电平。
如果控制输出高电平,则P-MOS管和N-MOS管都关闭,输出指令就不会起到作用,此时IO端口的电平就不会由所控制输出的高电平决定,而是由IO端口外部的上拉或者下拉决定 如果外部没有上拉或者下拉,那么IO口就会处于悬空状态。
> 1.开漏输出模式下施密特触发器是开启的,即输入可用。可以通过输入数据寄存器GPIOx_IDATA读取IO的实际状态。
> 2.IO口的电平不一定是输出的电平。
### 推挽输出
如图中蓝色路线所示,在该模式下,N-MOS管和P-MOS管都工作。如果控制输出为低电平,则N-MOS管关闭,P-MOS管导通,使IO输出低电平。
如果控制输出为高电平,则N-MOS管导通,P-MOS管关闭,使IO输出高电平,此时外部上拉和下拉的作用是控制在没有输出时IO口的电平状态。
> 1.该模式下施密特触发器是开启的,即输入可用。可以通过输入数据寄存器GPIOx_IDATA读取IO的实际状态。
> 2.IO口的电平不一定是输出的电平。
### 复用开漏
如图中蓝色路线所示,在该模式下,GPIO复用为其他外设,输出数据寄存器GPIOx_ODATA无效。 输出的高低电平来自其它内部外设,其他和开漏输出功能相同。
> 该模式下施密特触发器是开启的,即输入可用。可以通过输入数据寄存器GPIOx_IDATA读取IO的实际状态。
### 复用推挽
如图中蓝色路线所示,在该模式下,GPIO复用为其他外设,输出数据寄存器GPIOx_ODATA无效。 输出的高低电平来自其它内部外设,其他和推挽输出功能相同。
> 该模式下施密特触发器是开启的,即输入可用。可以通过输入数据寄存器GPIOx_IDATA读取IO的实际状态。
### 复用输入
该模式不能由GPIO外设配置,由其他外设复用功能决定。如图中蓝色路线所示,在该模式下,IO信号经史密斯触发器通过复用通道输入各外设中。
03 GPIO的配置和应用
## 通用功能
### 浮空输入
> 该模式常应用于按键检测。
#define KEY1_PIN GPIO_PIN_8
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_CLK RCM_AHB1_PERIPH_GPIOA
void GPIO_Config(void)
{
/*定义一个GPIO_Config_T类型的结构体*/
GPIO_Config_T configStruct;
/*开启相关的GPIO外设时钟*/
RCM_EnableAHB1PeriphClock (KEY1_GPIO_CLK);
/*选择要控制的GPIO引脚*/
configStruct.pin = KEY1_PIN;
/*设置引脚模式为输出模式*/
configStruct.mode = GPIO_MODE_IN;
/*设置引脚为不上拉也不下拉模式*/
configStruct.pupd = GPIO_PUPD_NOPULL;
/*初始化GPIO*/
GPIO_Config(KEY1_GPIO_PORT, &configStruct);
}
### 上拉/下拉输入
> 用于给定外部输入一个初始电平状态。
#define KEY1_INT_GPIO_PORT GPIOA
#define KEY1_INT_GPIO_CLK RCM_AHB1_PERIPH_GPIOA
#define KEY1_INT_GPIO_PIN GPIO_PIN_0
#define KEY1_INT_EXTI_PORTSOURCE SYSCFG_PORT_GPIOA
#define KEY1_INT_EXTI_PINSOURCE SYSCFG_PIN_0
#define KEY1_INT_EXTI_LINE EINT_LINE_0
#define KEY1_INT_EXTI_IRQn EINT0_IRQn
void GPIO_Config(void)
{
GPIO_Config_T configStruct;
EINT_Config_T EINT_configStruct;
/*开启按键GPIO口的时钟*/
RCM_EnableAHB1PeriphClock(KEY1_INT_GPIO_CLK);
/*使能 SYSCFG 时钟 ,使用GPIO外部中断时必须使能SYSCFG时钟*/
RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);
/*选择按键1的引脚 */
configStruct.pin = KEY1_INT_GPIO_PIN;
/*设置引脚为输入模式 */
configStruct.mode = GPIO_MODE_IN;
/*设置引脚下拉,用于给引脚一个默认电平状态 */
configStruct.pupd = GPIO_PUPD_DOWN;
/*使用上面的结构体初始化按键 */
GPIO_Config(KEY1_INT_GPIO_PORT, &configStruct);
/*连接 EXTI 中断源 到key1引脚 */
SYSCFG_ConfigEINTLine(KEY1_INT_EXTI_PORTSOURCE,KEY1_INT_EXTI_PINSOURCE);
/*选择 EXTI 中断源 */
EINT_configStruct.line = KEY1_INT_EXTI_LINE;
/*中断模式 */
EINT_configStruct.mode = EXTI_Mode_Interrupt;
/*下降沿触发 */
EINT_configStruct.trigger = EXTI_Trigger_Rising;
/*使能中断/事件线 */
EINT_configStruct.lineCmd = ENABLE;
EXTI_EINT_ConfigInit(&EINT_configStruct);
NVIC_EnableIRQRequest(KEY1_INT_EXTI_IRQn, 0x0f, 0x0f);
}
### 模拟功能
> 用于ADC外设输入配置。
#define ADC_GPIO_PORT GPIOB
#define ADC_GPIO_PIN GPIO_PIN_0
#define ADC_GPIO_CLK RCM_AHB1_PERIPH_GPIOB
void GPIO_Config(void)
{
GPIO_Config_T configStruct;
/*使能 GPIO 时钟*/
RCM_EnableAHB1PeriphClock(ADC_GPIO_CLK);
/*配置ADC IO*/
configStruct.pin = ADC_GPIO_PIN;
/*配置为模拟模式*/
configStruct.mode = GPIO_MODE_AN;
configStruct.pupd = GPIO_PUPD_NOPULL ;
GPIO_Config(ADC_GPIO_PORT, &configStruct);
}
> 用于DAC外设输出配置。
#define DAC_CH1_GPIO_CLK RCM_AHB1_PERIPH_GPIOA
#define DAC_CH1_GPIO_PORT GPIOA
#define DAC_CH1_GPIO_PIN GPIO_PIN_4
void GPIO_Config(void)
{
GPIO_Config_T configStruct;
DAC_Config_T dacConfig;
/*使能GPIOA时钟 */
RCM_EnableAHB1PeriphClock(DAC_CH1_GPIO_CLK, ENABLE);
/*使能DAC时钟 */
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_DAC);
/*DAC的GPIO配置,模拟输入 */
configStruct.pin = DAC_CH1_GPIO_PIN;
configStruct.mode = GPIO_MODE_AN;
configStruct.pupd = GPIO_PUPD_NOPULL;
configStruct.speed = GPIO_SPEED_100MHz;
configStruct.otype = GPIO_OTYPE_PP;
GPIO_Config(DAC_CH1_GPIO_PORT, &configStruct);
}
### (复用)开漏输出
> 常用于驱动外部数字芯片,如I2C接口的芯片。
#define I2C_SCL_PIN GPIO_PIN_8
#define I2C_SCL_GPIO_PORT GPIOB
#define I2C_SCL_GPIO_CLK RCM_AHB1_PERIPH_GPIOB
#define I2C_SCL_SOURCE GPIO_PIN_SOURCE_8
#define I2C_SCL_AF GPIO_AF_I2C1
#define I2C_SDA_PIN GPIO_PIN_9
#define I2C_SDA_GPIO_PORT GPIOB
#define I2C_SDA_GPIO_CLK RCM_AHB1_PERIPH_GPIOB
#define I2C_SDA_SOURCE GPIO_PIN_SOURCE_9
#define I2C_SDA_AF GPIO_AF_I2C1
void GPIO_Config(void)
{
GPIO_Config_T configStruct;
/*I2C Periph clock enable */
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);
/*I2C_SCL_GPIO_CLK and I2C_SDA_GPIO_CLK Periph clock enable */
RCM_EnableAHB1PeriphClock(I2C_SCL_GPIO_CLK | I2C_SDA_GPIO_CLK);
/*GPIO configuration */
/*Connect PXx to I2C_SCL*/
GPIO_ConfigPinAF(I2C_SCL_GPIO_PORT, I2C_SCL_SOURCE, I2C_SCL_AF);
/*Connect PXx to I2C_SDA*/
GPIO_ConfigPinAF(I2C_SDA_GPIO_PORT, I2C_SDA_SOURCE, I2C_SDA_AF);
/*Configure I2C pins: SCL */
configStruct.pin = I2C_SCL_PIN;
configStruct.mode = GPIO_Mode_AF;
configStruct.speed = GPIO_SPEED_50MHz;
configStruct.otype = GPIO_OTYPE_OD;
configStruct.pupd = GPIO_PUPD_NOPULL;
GPIO_Config(I2C_SCL_GPIO_PORT, &configStruct);
/*I2C pins: SDA */
configStruct.pin = I2C_SDA_PIN;
GPIO_Config(I2C_SDA_GPIO_PORT, &configStruct);
}
### (复用)推挽输出
> MCU输出电流有限,该模式常用于驱动小功率负载,如LED。
#define LED1_PIN GPIO_PIN_6
#define LED1_GPIO_PORT GPIOA
#define LED1_GPIO_CLK RCM_AHB1_PERIPH_GPIOA
void GPIO_Config(void)
{
/*定义一个GPIO_Config_T类型的结构体*/
GPIO_Config_T configStruct;
/*开启相关的GPIO外设时钟*/
RCM_EnableAHB1PeriphClock (LED1_GPIO_CLK);
/*选择要控制的GPIO引脚*/
configStruct.pin = LED1_PIN;
/*设置引脚模式为输出模式*/
configStruct.mode = GPIO_Mode_OUT;
/*设置引脚的输出类型为推挽输出*/
configStruct.otype = GPIO_OTYPE_PP;
/*设置引脚为上拉模式*/
configStruct.pupd = GPIO_PuPd_UP;
/*设置引脚速率为100MHz */
configStruct.speed = GPIO_SPEED_100MHz;
/*初始化GPIO*/
GPIO_Config(LED1_GPIO_PORT, &configStruct);
}
### 重映射功能
> 该功能多见于早期F1xx系列中,用于映射外设功能到不同IO。
#define TIM3_CH2_GPIO_CLK RCM_APB2_PERIPH_GPIOB
#define TIM3_CH2_GPIO_PORT GPIOB
#define TIM3_CH2_GPIO_PIN GPIO_PIN_5
void GPIO_Config(void)
{
GPIO_Config_T configStruct;
/*使能定时器3时钟*/
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR3);
/*使能GPIO外设*/
RCM_EnableAPB2PeriphClock(TIM3_CH2_GPIO_CLK);
/*Timer3部分重映射 TIM3_CH2->PB5*/
GPIO_ConfigPinRemap(GPIO_PARTIAL_REMAP_TMR3);
/*设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形*/
configStruct.pin = TIM3_CH2_GPIO_PIN;
/*复用推挽输出*/
configStruct.mode = GPIO_MODE_AF_PP;
configStruct.speed = GPIO_SPEED_50MHz;
GPIO_Config(TIM3_CH2_GPIO_PORT, &configStruct);
}
### 锁定功能
> 常用于锁定不能被轻易更改工作模式的IO控制,如电机控制IO。
#define MOTOR_A_PIN GPIO_PIN_10
#define MOTOR_A_GPIO_PORT GPIOB
#define MOTOR_A_GPIO_CLK RCM_AHB1_PERIPH_GPIOB
void GPIO_Config(void)
{
/*定义一个GPIO_Config_T类型的结构体*/
GPIO_Config_T configStruct;
/*开启相关的GPIO外设时钟*/
RCM_EnableAHB1PeriphClock (MOTOR_A_GPIO_CLK);
/*选择要控制的GPIO引脚*/
configStruct.pin = MOTOR_A_PIN;
/*设置引脚模式为输出模式*/
configStruct.mode = GPIO_Mode_OUT;
/*设置引脚的输出类型为推挽输出*/
configStruct.otype = GPIO_OTYPE_PP;
/*设置引脚为上拉模式*/
configStruct.pupd = GPIO_PUPD_UP;
/*设置引脚速率为100MHz */
configStruct.speed = GPIO_SPEED_100MHz;
/*初始化GPIO*/
GPIO_Config(MOTOR_A_GPIO_PORT, &configStruct);
/*锁定IO工作模式*/
GPIO_ConfigPinLock(MOTOR_A_GPIO_PORT,MOTOR_A_PIN)
}
### 位带操作
位带操作就是可以单独的对一个比特位读和写。在F407 中,有两个地方实现了位带,一个是SRAM 区的最低1MB 空间,另一个是外设区最低1MB 空间。这两个1MB 的空间除了可以像正常的RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这1MB 的空间的每一个位扩展成一个32 位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。
> 1.这部分位带区大家可以看之前的《APM32存储器和寄存器》中讲到的Cortex-M4存储器映射。
> 2.位带区的一个比特位经过扩展之后,虽然变大到4 个字节,但是还是LSB才有效。
> 3.Cortex-M4系统总线是32bit的,所以这里把别名区扩展成32bit,使访问更高效。
/**
* @ brief 这里只定义了 GPIO ODR和IDR这两个寄存器的位带别名区地址,其他寄存器的没有定义
* SRAM 位带区: 0X2000 0000~0X200F 0000
* SRAM 位带别名区:0X2200 0000~0X23FF FFFF
* 外设 位带区: 0X4000 0000~0X400F FFFF
* 外设 位带别名区:0X4200 0000~0X43FF FFFF
*/
/*把“位带地址+位序号”转换成别名地址的宏*/
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))
/*把一个地址转换成一个指针*/
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
/*把位带别名区地址转换成指针*/
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
/*GPIO ODR 和 IDR 寄存器地址映射*/
#define GPIOA_ODR_Addr (GPIOA_BASE+20)
#define GPIOB_ODR_Addr (GPIOB_BASE+20)
#define GPIOC_ODR_Addr (GPIOC_BASE+20)
#define GPIOD_ODR_Addr (GPIOD_BASE+20)
#define GPIOE_ODR_Addr (GPIOE_BASE+20)
#define GPIOF_ODR_Addr (GPIOF_BASE+20)
#define GPIOG_ODR_Addr (GPIOG_BASE+20)
#define GPIOH_ODR_Addr (GPIOH_BASE+20)
#define GPIOI_ODR_Addr (GPIOI_BASE+20)
#define GPIOJ_ODR_Addr (GPIOJ_BASE+20)
#define GPIOK_ODR_Addr (GPIOK_BASE+20)
#define GPIOA_IDR_Addr (GPIOA_BASE+16)
#define GPIOB_IDR_Addr (GPIOB_BASE+16)
#define GPIOC_IDR_Addr (GPIOC_BASE+16)
#define GPIOD_IDR_Addr (GPIOD_BASE+16)
#define GPIOE_IDR_Addr (GPIOE_BASE+16)
#define GPIOF_IDR_Addr (GPIOF_BASE+16)
#define GPIOG_IDR_Addr (GPIOG_BASE+16)
#define GPIOH_IDR_Addr (GPIOH_BASE+16)
#define GPIOI_IDR_Addr (GPIOI_BASE+16)
#define GPIOJ_IDR_Addr (GPIOJ_BASE+16)
#define GPIOK_IDR_Addr (GPIOK_BASE+16)
/*单独操作 GPIO的某一个IO口,n(0,1,2...16),n表示具体是哪一个IO口*/
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n)
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n)
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n)
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n)
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n)
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n)
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n)
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n)
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n)
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n)
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n)
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n)
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n)
#define PHout(n) BIT_ADDR(GPIOH_ODR_Addr,n)
#define PHin(n) BIT_ADDR(GPIOH_IDR_Addr,n)
#define PIout(n) BIT_ADDR(GPIOI_ODR_Addr,n)
#define PIin(n) BIT_ADDR(GPIOI_IDR_Addr,n)
#define PJout(n) BIT_ADDR(GPIOJ_ODR_Addr,n)
#define PJin(n) BIT_ADDR(GPIOJ_IDR_Addr,n)
#define PKout(n) BIT_ADDR(GPIOK_ODR_Addr,n)
#define PKin(n) BIT_ADDR(GPIOK_IDR_Addr,n)
void Application(void)
{
/*LED 端口初始化 */
LED_GPIO_Config();
while (1)
{
/*PA6 = 0,点亮LED*/
PAout(6) = 0;
Delay_ms(100);
/*PA6 = 1,熄灭LED*/
PAout(6) = 1;
Delay_ms(100);
}
}
|
共1人点赞
|