GPIO
Created time: 2024年9月4日 15:03
Status: In progress
类型: 理论&实践
参考STM32F10xxx参考手册:Ch8(P105+)
STM32-GPIO介绍_stm32 gpio-CSDN博客
在学习GPIO之前,请复习STM32的时钟系统!
什么是GPIO?
GPIO : General Purpose Input/Output,通用输入输出端口.
GPIO最基本的输入功能是检测外部电平变化,比如把GPIO引脚连接到按键电路,通过电平的高低变化来识别按键是否被按下。输出功能同上.
STM32 GPIO简介
GPIO特点
- 不同型号GPIO数量不一样,请参阅选型手册;
- 每个GPIO都可以用作中断
- 快速翻转,每次翻转最快只需要2个周期(极限条件即超频条件下,官方给的F1的最高速度可以达到50MHz),F103系列(72MHz)翻转达到36MHz;
- 有八种工作特性(这个之后讲)
GPIO电气特性(比较重要)
- STM32工作范围:2V≤VDD≤3.6V
- GPIO识别电压的范围?
- COMS(3.3V)端口: -0.3V≤ V I L V_{IL} VIL≤1.164V (在1.164~1.833V之间为不确定值) 1.833V≤ V I H V_{IH} VIH≤3.6V
- TTL(5V&3.3V)端口(芯片手册上凡是标了FT的都是TTL端口)
- GPIO输出电流:单个IO口最大输出25mA,然而F103系列最大输入输出电流为150mA,所以不可能每个IO口都输出25mA(e.g.STM32RCT6有51个GPIO,要是都输出25mA到1.7A了肯定不行)如果想要更大的输入输出电流的话,就需要想一些扩流的方案,比如加个三极管\MOS管\继电器.etc
GPIO引脚分布
有电源引脚,晶振引脚,复位引脚,下载引脚,BOOT引脚,GPIO引脚
不同芯片有不同的引脚分布情况,以(Mini)RCT6为例,他有64个引脚,51个GPIO,其中GPIOAC分别有16个IO口,GPIOD有02共3个IO口,即48+3=51
个IO口.
GPIO基本结构介绍
摘于STM32F10xxx参考手册。这是GPIO的硬件结构框图,可以从这个框图中清晰的了解GPIO外设极其各种应用模式,最右端的I/O引脚就是STM32芯片引出的GPIO引脚,其它的部件都位于芯片内部.看不懂?你先别急…
施密特触发器
这是一个整形的电路,可以将非标准方波(如正选信号)整形成标准方波,原理很简单:当输入电压高于正向阈值电压,则输出为高;输出低于负向阈值电压,则输出为低;如果输出在正负向阈值之间,那么输出不改变.
P-MOS&N-MOS
复习下魔电,MOS压控元件,通过 V g s 栅源电压 V_{gs}栅源电压 Vgs栅源电压控制器件的导通或关闭
P-MOS: V g s < 0 V_{gs}<0 Vgs<0导通;
N-MOS: V g s > 0 V{gs}>0 Vgs>0导通.
简单来讲就是两个端不一样(1\0)就能导通.
GPIO的8种工作模式
输入
浮空输入
完全浮空,状态不定
- 对应前面那张I/O端口位的基本结构里的橙色1~4:上下拉电阻关闭,TTL肖特基触发器打开(即施密特触发器),双MOS管不导通
- 空闲状态(即外部高阻态时,IO状态不确定,由外部决定.
这里的高阻态,就是外部什么都没接,电阻很大的时候.(我的理解)
上拉输入
内部上拉,默认高电平
和浮空输入唯一的不同是使用了上拉电阻,空闲时(外部高阻态)IO呈现高电平.注意这是弱上拉(电流很小)
下拉输入
内部下拉,默认低电平
模拟输入
ADC/DAC
上下拉电阻&施密特&双MOS都不导通,那么只有走左上角的那一条路,到片上外设模拟信号的输入(之后会讲)
输出
开漏输出
软件IIC的SDL,SCL等
- 上下拉电阻关闭,施密特触发器打开,这里可以发现输出是可以走到IDR(输入数据寄存器)中的,即配置为输出模式我们可以读取引脚的高低电平.
- P-MOS始终不导通,即在输出控制模块接高) V D D V_{DD} VDD;
- 往ODR对应位写0,N-MOS导通,写1则N-MOS不导通.
- 那么怎么输出1呢?可以在外部(或内部如:F4 H7 H7)加一个上拉电阻(就是单单看内部是不能输出高电平…)
开漏复用输出
片上外设功能(硬件IIC的SDL,SCL等)
- 上下拉电阻关闭,施密特触发器打开,P-MOS管始终不导通(和开漏输出一样的,也是不能自己输出高电平(F1))
- 注意这里的控制是上面那张图的左下角(
片上外设
)控制的,就不用寄存器控制了
推挽输出
驱动能力强,25mA(max),通用输出(高低电平都能输出)
- 上下拉电阻关闭,施密特触发器打开,往ODR(就是输出数据寄存器)(走中间那条道)对应位写0的时候,
N-MOS
导通,写一则P-MOS
导通. - 当然,我们先前说想要让P/N-MOS管导通,要和他们不一样才能导通,那么像输出0,进入输出驱动器时候得有一个反相器,反一下就能输出了,看眼这张图:
推挽复用输出
片上外设功能(SPI的SCK,MISO,MOSI引脚等)
- 上下拉电阻关闭,施密特触发器打开,P/N-MOS可开可关.
- 通过
片上外设
(左下角)控制
总结一下:
- F1是不使用内部上下拉电阻的,其他(F4,F7,H7)可以使用
- 不同系列IO翻转速度不同
Q:STM32能输出5V电平嘛?——————————结论是可以,但是得加外部上拉(翻)
GPIO的寄存器介绍
有同学说,寄存器这玩意好枯燥不想看啊,这东西学了有啥用??告诉你哦,想学单片机确实需要了解一个个寄存器的,这和Python或者Java不一样(这其实也是对我自己说的,因为以前学的感觉都是顶层或者偏上位机),在这里初学者可能会有些痛苦,但耐心学你会知道这些有啥用的(doge)
看眼参考手册8.2(F1系列通用寄存器GPIO通用寄存器GPIOx_yyy)(x:A~E)
CRL | CRH | IDR | ODR | BSRR | BRR | LCKR |
---|---|---|---|---|---|---|
配置工作速度,输出速度 | 同CRL | 输入数据 | 输出数据 | 设置ODR寄存器的值 | F4之后没有这个寄存器 | 配置锁定,用的不多(把CRL&CRH锁定) |
CRL有32位+CRH有32位=64个位,而每个组(AE)有16个引脚(GPIOA0A15),∴4个位控制一个IO引脚的工作模式.接下来来看这俩寄存器(注意结合**参考手册(F1)**这玩意太他妈重要了):
端口配置低寄存器(GPIOx_CRL)(x=A…E)
CRL寄存器控制的是GPIOx0~7
的工作模式(就对应着上文8个工作模式),然后CRH就是GPIOx8~15
的工作模式.
端口配置高寄存器(GPIOx_CRH)(x=A…E)
端口输入数据寄存器(GPIOx_IDR)(x=A…E)
0~15位分别控制GPIOx_0~15
注意这个寄存器是只读的,也就是只能判断引脚电平
端口输出数据寄存器(GPIOx_ODR)(x=A…E)
这个寄存器有俩功能:
-
设置上下拉(这个功能是补充CRL&CRH中设置上下拉模式不知道是上拉还是下拉的时候确定的,看这个表:)
-
第二个功能就顾名思义,设置引脚输出的电平
端口位设置/清除寄存器(GPIOx_BSRR)(x=A…E)
间接控制ODR,注意这个寄存器只能写操作,并且写1才有用.
0~15
位写1:将ODR寄存器设置为1;16~31
位写1:清ODR寄存器为0.
ODR&BSRR有啥区别?
使用ODR在读和修改访问之间产生中断时,可能会发生风险;BSRR则无风险.
啥风险??
比如我想操作GPIOB3置高:
GPIOB->ODR |= 1<<3;
GPIOB->BSRR = Ox00000008;
可见,ODR修改:读→改→写而BSRR是直接写,而操作ODR时需要中断,而中断恰好要用到这个寄存器,这样就很容易产生覆盖值的问题.当然还会有操作系统中的线程(任务调度)的先后,所以选择BSRR会降低所谓的风险.建议大家使用BSRR控制输出哈,.
通用外设驱动模型(四步法)
这一个小点是对于STM32所有外设
的驱动模型:
- 初始化
- 时钟设置
- 给外设选择时钟源(有默认时钟源的话就不用动了哈)
- 开启时钟源
- 参数设置
- 比如说GPIO有8种工作模式,那想要其工作在其中一种模式下就需要对其参数进行设置(寄存器.etc)
- IO设置,中断设置:这俩都是可选的,像GPIO就不需要设置IO,但是像串口(USART/UART)就需要设置IO(设置为复用功能);也是串口,他需要设置接收中断&发送中断,而在中断设置中我们也有开中断,设置NVIC等步骤(先别急之后会讲)
- 时钟设置
- 读函数(可选):就是从外设读取数据(串口)
- 写函数(可选):从外设写数据(串口)
- 中断服务函数(可选):根据中断标志,处理外设各种中断事务
GPIO配置步骤
接下来来看看具体的GPIO是怎么设置的:
- 使能时钟:__HAL_RCC_GPIOx_CLK_ENABLE()(??为啥这里没有选择时钟源?因为GPIO是挂载在总线时钟上的,具体哪个总线得看不同的系列)
- 设置工作模式(也就是八种工作模式的设置):HAL_GPIO_Init()
- 设置输出状态(可选)(写函数):有HAL_GPIO_WritePin()和HAL_GPIO_TogglePin()
- 设置输入状态(可选(读函数):HAL_GPIO_ReadPin()
相关HAL库函数简介
HAL库相关函数 | 主要寄存器 | 功能 |
---|---|---|
__HAL_RCC_GPIOx_CLK_ENABLE | F1:RCC_APB2ENR | |
F4:RCC_AHB1ENR | ||
F7:RCC_AHB1ENR | ||
H7:RCC_AHB4ENR | 开启GPIO时钟 | |
HAL_GPIO_Init() | F1:CRL,CRH,ODR | 初始化GPIO |
HAL_GPIO_WritePin() | BSRR | 控制IO输出高/低电平 |
HAL_GPIO_TogglePin() | BSRR | 每次调用IO输出电平翻转一次 |
HAL_GPIO_ReadPin() | IDR | 读IO电平 |
这里以__HAL_RCC_GPIOA_CLK_ENABLE()
为例:
__HAL_RCC_GPIOx_CLK_ENABLE
这边主要看蓝色那一行,自己把这些参数都跳转一遍,可以得出结论:这一句就是把1<<2(1左移2位)再对RCC_APB2ENR
做或运算.那么RCC_APB2ENR
是啥呢?看眼参考手册:
我们现在是对位2进行写1的操作,再看眼位2:
破案了,就是将GPIOA的时钟开启,和我们要做的事情一致,.
HAL_GPIO_Init
举一反三,现在你已经会自己找到相关函数的定义了,所以我就解释一下帮助你理解:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
看一眼函数形参:
- 第一个:
GPIO_TypeDef
是各种寄存器的声明:
GPIOx
是寄存器基地址,以GPIOA
为例,它在\USER\stm32f103xe.h
下:
我们把这些都称作xx外设寄存器基地址.
GPIO_InitTypeDef
:
typedef struct
{
uint32_t Pin; /*引脚号*/
uint32_t Mode; /*模式设置(8)*/
uint32_t Pull; /*上拉下拉设置*/
uint32_t Speed; /*速度设置*/
}GPIO_InitTypeDef;
这个Ctrl+F就可以找到.看到后面注释有个@ref
的宏,Ctrl+C&Ctrl+F
进去就可以找到宏定义了,之后每查阅一些需要的函数或者变量都记得这个方法.
HAL_GPIO_WritePin()
HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
3个参数分别是GPIO组别(A~G),引脚号,状态(0/1)
HAL_GPIO_TogglePin**()**
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
HAL_GPIO_ReadPin()
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) //返回0/1
编程实战:点亮一个LED灯
Do It Yourself !