1、概述
-
GPIO外设是MCU(微控制处理器)最基本,最通用的端口,用于与外部设备进行交互,可通过指令配置多种工作模式,实现通信和数据采集等功能。
-
通过对引脚(PIN)高低电平的控制,输出0/1,以及检测引脚电平状态来控制芯片功能
-
电平一般拟采用TTL电平信号,大于2.4V就是高电平输出1.低于0.4V就是低电平输出0
-
- 电源引脚
- VCC、VDD、VSS、VDDA、VSSA、VREF+等属于电源引脚
- 晶振引脚 ,
- PC14、PC15、PH0、PH1就属于晶振引脚,也可以作为其他的功能使用
- 复位引脚
- SET(置位) RESET(复位)
- BOOT引脚:
- BOOT0是专用引脚,BOOT1就属于功能引脚 (设置芯片的自举模式)
- GPIO引脚
- 芯片一共有144引脚,但是GPIO引脚有114个
- 下载引脚
- PA13、PA14、PB3、PB4等都属于下载引脚(JTAG、SWD)
- 电源引脚
-
一般IO口引脚用(PIN)P开头,分为多个端口,PA ~ PH,每个端口有16个引脚,PA0 ~ PA15
-
注意———芯片引脚有多个功能,但一般复位之后为GPIO功能
-
接口介绍和描述(数据手册)
-
[!NOTE]
PIN_Number 是指哪个型号的芯片有该引脚,通过引脚类型判断引脚属于哪一种并使用对应的代码操作
-
-
2、GPIO外设使用
那么该如何使用这些GPIO外设呢?
-
根据官方提供STM32F40x/41x/427/437/429/439/401/411xE DSP and Standard Peripherals Library 文件(后续会上传)
找到如何使用这些外设库函数)[How to use the Library]点击进去查看,Peripheral initialization and configuration即可
-
-
int main() { //定义结构变量 GPIO_InitTypeDef GPIO_InitStructure; //打开时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); 进行相关函数配置 //指定引脚开启 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //指定引脚输出模式 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //指定引脚输出类型 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //指定引脚输出速度 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //指定上拉/下拉电阻 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //初始化 GPIO_Init(GPIOF,&GPIO_InitStructure); while(1) { /* Reset PF9 */ GPIOF->BSRRH = GPIO_Pin_9 ; } }
- 查看原理图,找到对应引脚
- 分析原理图以及数据手册,查看怎么控制该引脚
- 了解一些常用物理器件,方便计算以及分析(电阻,电容)
- 高电平导通还是低电平导通控制
- 进行程序设计,根据st公司提供的函数库(标准外设库+HAL库)的帮助手册进行学习 (Peripheral initialization and configuration)
- 具体步骤:
- 定义外设结构体
- 打开外设时钟
- 目的:因为STM32属于低功耗的MCU,而为了降低功耗,所以STM32的MCU在复位之后默认会关闭绝大多数的外设的时钟,所以用户想要使用MCU内部的某个外设,就必须打开该外设的时钟(clock)。
- 底层原理 :寄存器是由触发器构成,触发器必须要有时钟新信号参与
- 但是应用函数接口时系统运行效率较低,所以需要了解底层原理,提高CPU运行效率
3、底层原理解释
- 想要控制硬件,则需要控制硬件的寄存器
- 寄存器则是用于暂时存储一组二进制数据
- 32bit的寄存器由32个触发器组成,可以暂时存储32bit的数据
- 触发器为时序逻辑电路,可存储0/1
- 触发信号:电平触发,脉冲触发,边沿触发
- 时钟相当于外设的开关,不打开时钟信号,寄存器则不能进行数据所存
- 时钟信号则是由晶振产生
- 时钟信号则是由晶振产生
- STM32芯片有很多外设接口,但每个外设接口的功能和性能有所不能,ST公司为了降低功耗,则将不同性能的外设挂载到不同性能的总线上
- AHB高级性能总线 168MHZ
- APB1 高级外设总线 42MHZ
- APB2 高级外设总线 84MHZ
- 总线频率则影响 数据传输速率,系统响应时间,功耗与散热,系统稳定性等方面
- 总线和外设需要匹配
4、寄存器控制编码
- 优点 :可以提高程序运行效率,节约内存
- 查找ST公司提供的中文参考手册
- 目的:了解当前外设有哪些寄存器,以及寄存器使用流程
- 如何分析寄存器
- 端口模式寄存器GPIOx_MODEA 可知每个端口都有一个
图中每两个编号代表一个PIN口 PINA9则对应为18和19口,对其进行置位和复位操作
推挽输出
- 推挽输出是一种常见的输出结构,通常用于数字电路中。它由两个晶体管组成,一个负责拉高电平(P型晶体管),另一个负责拉低电平(N型晶体管)。推挽输出的特点是可以直接驱动高电平和低电平,输出阻抗较低,能够提供较强的驱动能力。
- 推挽输出的优点包括: 能够直接输出高电平和低电平,无需外部上拉或下拉电阻。 输出阻抗低,驱动能力强,适合驱动负载较大的电路。
- 响应速度快,适合高频应用。 推挽输出的缺点包括:
- 不能直接与其他推挽输出连接,否则可能导致短路。 在需要开漏或开集输出的场合,推挽输出不适用。 开漏输出 开漏输出(或开集输出)是一种输出结构,通常用于需要多个设备共享同一信号线的场合。开漏输出只包含一个晶体管,通常为N型晶体管,负责拉低电平。开漏输出需要外部上拉电阻来提供高电平。
开漏输出的优点包括:
- 多个开漏输出可以连接在一起,通过“线与”逻辑实现多设备共享信号线。 适合用于I2C等总线协议,允许多个设备在同一总线上通信。
- 输出电平可以通过外部上拉电阻调整,适应不同的电压需求。 开漏输出的缺点包括:
- 需要外部上拉电阻,增加了电路复杂性。 输出高电平的驱动能力较弱,依赖于上拉电阻。 响应速度较慢,不适合高频应用。 推挽与开漏的比较- 驱动能力:推挽输出驱动能力强,开漏输出驱动能力较弱。 电路复杂性:推挽输出无需外部元件,开漏输出需要外部上拉电阻。
应用场景:推挽输出适合单独驱动负载,开漏输出适合多设备共享信号线。 响应速度:推挽输出响应速度快,开漏输出响应速度较慢。 应用示例
推挽输出常用于单片机GPIO引脚、LED驱动等场合。开漏输出常用于I2C、SMBus等总线协议,以及需要多设备共享信号线的场合。
- 上拉和下拉电阻可以确保在引脚未连接外部设备时,引脚处于已知的电平状态,避免悬空状态导致的不可预测行为。
- 上拉寄存器
- 上拉寄存器用于在GPIO引脚上启用内部上拉电阻。当上拉电阻启用时,引脚在没有外部驱动的情况下会被拉至高电平(通常为VCC)。上拉电阻的典型值在几十千欧姆范围内。
- 下拉寄存器
- 下拉寄存器用于在GPIO引脚上启用内部下拉电阻。当下拉电阻启用时,引脚在没有外部驱动的情况下会被拉至低电平(通常为GND)。下拉电阻的典型值也在几十千欧姆范围内。
- 寄存器地址:基地址➕偏移地址
- 复位值:芯片复位之后寄存器默认值
- 寄存器是32bit,每个寄存器分为16组,2bit为一组,一个端口下有16个引脚,每个引脚有4 中选择模式
- 根据引脚编号找到寄存器比特位对其进行操作
置1:xxx |=(1<<2y)
置0:xxx &=~(1<<2y)
关键:怎么找到外设物理地址(根据中文参考手册)
- 第四章内存映射表
其中GPIO外设对应地址为0x4000_0000到0x6000_000地址,同时根据手册找到总线下对应的端口
通过端口物理地址后,结合上图中的偏移地址进而定位到初始化参数
通过对端口相关位进行置位/复位控制
- 将映射区分为多个区,从中找到总线(BUS)的地址,计算出偏移地址
- 对总线进行映射为 外设端口地址和边界地址
- 通过基地址算出端口偏移地址,根据偏移地址+基地址算出端口下寄存器的地址
5、寄存器编码详解
-
ST公式定义好的函数接口
-
int main() { //定义结构体类型 GPIO_InitTypeDef GPIO_InitStructure; //打开外设时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); //配置引脚相应模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //初始化端口 GPIO_Init(GPIOF,&GPIO_InitStructure); while(1) { //引脚置位 GPIOF->BSRRH = GPIO_Pin_9 ; } }
-
-
结构体类型内核源码
-
//通过对结构体进行重命名为GPIO_InitTypeDef,以及对内部成员进行定义,此处使用为宏定义GPIOMode_TypeDef等 typedef struct { uint32_t GPIO_Pin; /* 使用哪个引脚,该参数为引脚的地址,此处进行了宏定义 */ GPIOMode_TypeDef GPIO_Mode; /*使用引脚模式,推挽/开漏*/ GPIOSpeed_TypeDef GPIO_Speed; /*指定引脚速度*/ GPIOOType_TypeDef GPIO_OType; /*指定引脚输出类型 */ GPIOPuPd_TypeDef GPIO_PuPd; /*选择上拉/下拉电阻*/ }GPIO_InitTypeDef;
-
GPIO_Pin引脚
-
指定引脚地址,16个引脚对应16bit,每个bit位对应一个引脚
-
#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 */
-
-
GPIOMode_TypeDe源码
-
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;
-
-
GPIOMode_TypeDef
-
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;
-
-
以此类推可知,每个寄存器模式都是通过地址来控制
-
各端口地址如下
-
#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)
-
-
GPIOF_BASE地址
-
端口地址
-
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000) #define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400) #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) #define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00) #define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000) #define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
-
-
AHB1PERIPH_BASE
-
总线地址
-
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000) #define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)
-
-
PERIPH_BASE
-
在单片机内存区域,外设接口的基本物理地址
-
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
-
-
所有映射地址都是通过,基地址 + 偏移地址 构成
-
-
相应寄存器地址编写代码
#include "stm32f4xx.h" //必须添加 //1. //外设基础地址 #define Base_addr ((volatile unsigned int*)0x40000000) //高级高性能总线地址 #define AHB_addr ((volatile unsigned int *)(Base_addr + 0x00020000)) //GPIOF端口映射地址 #define GPIO_addr ((volatile unsigned int *)(AHB_addr + 0x1400)) //GPIO_Pin_9引脚地址 #define GPIO_Pin_9_addr ((volatile unsigned int *)(GPIO_addr + 0x0200)) //GPIOF_MODE地址 #define GPIOF_MODE_addr (*(volatile unsigned int *)( GPIO_addr +0x00)) #define GPIO_OType_addr (*(volatile unsigned int *)( GPIO_addr +0x04)) #define GPIO_Speed_addr (*(volatile unsigned int *)( GPIO_addr +0x08)) #define GPIO_PuPd_addr (*(volatile unsigned int *)( GPIO_addr +0x0C)) #define RCC_AHB1Periph_GPIOF_addr (*((uint32_t *)0x00000020)) //程序入口 int main() { //打开时钟 RCC_AHB1Periph_GPIOF_addr |=(1<<5) ; //设置接口类型 PIN9 01输出模式 GPIOF_MODE_addr |=(1<<(2*9 +1)) ; GPIOF_MODE_addr &= ~(1<<2*9); //设置开漏 GPIO_OType_addr |=(1<<9); //设置输出速度 GPIO_Speed_addr |=(1<<(2*9 +1)) ; GPIO_Speed_addr &= ~(1<<2*9); //设置上拉电阻 GPIO_PuPd_addr |=(1<<(2*9 +1)) ; GPIO_PuPd_addr &= ~(1<<2*9); while (10){ //输出值置位 *GPIO_Pin_9_addr |=(1<<9); };
6、函数库编码
-
利用函数库提高开发效率
- 利用寄存器对硬件进行控制,需要计算出每个寄存器的物理地址,并把寄存器的地址转换为指针,然后对指针进行操作
- ST公司将地址已经将相应地址以及函数写好,使用时包含相关头文件即可
- include “STM32xx .h”
- 寄存器必须用volatile修饰,使用寄存器中最新的值
- volatile 防止编译器对变量进行优化,CPU每次读取需要从地址读取,用时间换空间
- ((*volatile unsigned int ) GPIOF ))