STM32的学习笔记—GPIO
我使用的是STM32F401ZGT6,有7组IO口,每组16个引脚,共112个引脚。
因为太菜了,确实容易出错,还请赐教
参考官方文档:八种IO口模式区别
结构原理
该单片机在GPIO功能方面有以下特点:
(1)4种输入模式:
GPIO_Mode_AIN 模拟输入
GPIO_Mode_IN_FLOATING 浮空输入
GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入
(2)4种输出模式:
GPIO_Mode_Out_OD 开漏输出
GPIO_Mode_Out_PP 推挽输出
GPIO_Mode_AF_OD 复用开漏输出
GPIO_Mode_AF_PP 复用推挽输出
(3)4种最大输出速度:2MHz,25MHz,50MHz,100MHz
图为该单片机芯片的IO口对应的结构图:
平时接触比较多的有:推挽输出,开漏输出,上拉输入。
推挽输出和开漏输出的区别:
推挽输出可以输出高电平和低电平,因为推挽结构是由两个三极管实现的,两只对称的功率开关管子每次只导通一个。因此输出既可以向负载灌电流,也可以从负载抽取电流。
开漏输出因为输出端只有集电极,只可输出低电平,所以如果想要输出高电平,需要同时使用上拉电阻,但是这也意味着只要改变上拉电源电压便可以改变输出电平。
以下为使用IO口时的模式选择情况:
1.浮空输入-IN_FLOATING,用的还不多,可以在识别KEY时候使用(因为KEY也是逻辑电平)
2.带上下拉输入_IPU/_IPD,根据实际情况随意选择
3.模拟输入_AIN,可用于模数转换中(ADC)模拟电平的输入
4.开漏输出 _OUT_OD,可以输出为0,要想输出为1,要接上拉电阻。可以读取IO输入电平变化
5.推挽输出_OUT_PP,可以输出0,1,相当于GND,VCC
6.复用功能的推挽/开漏输出 _AF_PP/_AF_OD,输出时候使用片外设功能
注意:如果为无上拉电阻,IO默认高电平;可以用上拉输入,浮空输入和开漏输出读取电平
通常有5种方式使用引脚功能,配置方式如下:
(1)普通情况下GPIO输入:浮空输入,上下拉输入,同时不使用引脚复用功能
(2)普通情况下GPIO输出:推挽开漏输出,不要使用复用功能
(3)GPIO模拟输入:引脚为模拟输入模式,不要使用复用功能
(4)内置外设的输入:在(1)基础上使能复用功能
(5)内置外设输出:在(2)的基础上使能复用功能
注意:如果有多个复用功能对应同一个引脚,比如经过查询原理图知道
PB0IO口可以复用为定时器一的CH2N通道(即二号高级通道),定时器三的CH3通道等,在编程之后的运行时,只使这个引脚实现这些功能的一种。
程序
以官方跑马灯实验库函数版本为例:
其框架排布如下:
由于是笔记的第一篇,因此对框架作解释:
为啦方便查找,keil的框架和实际文件夹的框架保持一致。
首先是USER文件夹,其中包括主文件,和两个官方文件
HARDWARE文件一般用来配置引脚以实现外设功能,如本文通过IO口输出高低电平来实现灯的亮灭
SYSTEM文件包含系统文件,和引脚的功能有关,如图中的sys是时钟方面的,delay是延时函数方面的,usart为串口通信方面的(这里其实不用的)
CORE文件中只有一个文件,但是必须要有
FWILB文件中为官方库,配置GPIO以及复用功能的库函数的声明和定义以及一些变量和结构体的宏定义都在里面。
在配置好框架后,我们主要负责两个地方:主文件和led.c文件
首先是HARDWARE中的led文件:
#ifndef __LED_H
#define __LED_H
#include "sys.h"//这里不一定要包含这个文件,但是led.c中必须包含
void LED_Init(void);
#endif//与开头的#ifndef对应
以下为led.c配置文件
#include "led.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIOInitStructure;//自定义的结构体变量名,成员由结构体类型决定,该类型于stm32f4xx_gpio.c中定义
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//该函数和变量于stm32f4xx_rcc.c和对应头文件中定义
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//结构体名字必须和第一行对应,也必须是PA9和PA10才行
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//总共有IN OUT AF AN四种模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//由OD和PP两种
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz,还有2M(低速),25M(中速),50M(快速),100M(高速)
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//有上拉下啦和无三种
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化完毕
}
由原理图可知led外设的结构,当对应端口输出低电平时,灯亮,当端口输出高电平,灯不亮
因此我们可以在LED_Init()末尾加这样一句话来确保刚开始时候灯是灭的
GPIO_SetBits(GPIOF, GPIO_Pin_9|GPIO_Pin_10);
下边是主文件的代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
int main(void)
{
delay_init(168); //初始化延时函数
LED_Init(); //使用led.c中定义的文件
while(1)
{
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //LED0灯亮
GPIO_SetBits(GPIOF,GPIO_Pin_10); //LED1灯灭
delay_us(500); //等0.5s
GPIO_SetBits(GPIOF,GPIO_Pin_9); //LED0灯灭
GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1灯亮
delay_ms(500); //延迟0.5s
}
}
—————————————————————————————
输入实例(因为上边这个实例是输出)
按键输入实例:(仅看初始化部分即可)
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//KEY0,1,2一端是IO口,另一端是低电平,在按键按下后,低电平连入IO口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;//下拉,WAKE_UP键一端IO,一端VCC
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
u8 KEY_Scan(u8 mode)//检测引脚输入电平的函数
{
static u8 key_up=1;
if(mode)key_up=1;
if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
{
delay_ms(10);
key_up=0;
if(KEY0==0)return 1;
else if(KEY1==0)return 2;
else if(KEY2==0)return 3;
else if(WK_UP==1)return 4;
}else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;
return 0;
}