一、实验目的
1. 深入理解STM32处理器的GPIO接口原理和使用方法;
2. 掌握GPIO接口寄存器、固件函数及应用编程方法;
3. 掌握GPIO寄存器编程方法和固件函数应用编程方法。
二、实验内容
1. 使用寄存器开发模版编写GPIO应用程序,当按键key_up按下时可以实现LED灯D1和D2的交替闪烁,当key_up未按下时灯不闪烁;
2. 使用库函数开发模版编写GPIO应用程序,当按键key_up按下时可以实现LED灯D1和D2的交替闪烁,当key_up未按下时灯不闪烁。
三、实验设备仪器及材料
硬件:STM32开发板, PC 机;
软件:MDK5集成开发环境,Windows 操作系统;
四、实验原理
GPIO工作模式:
1. 输入模式(上拉、下拉、浮空)
(1)GPIO_Mode_IN_FLOATING:浮空输入模式
浮空输入模式下,I/O端口的电平信号直接进入输入数据寄存器,I/O的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空(在无信号输入)的情况下,读取该端口的电平是不确定的。浮空输入一般多用于外部按键输入。
(2)GPIO_Mode_IPU:上拉输入
上下拉的电阻阻值都在30-50k之间,上拉就是使I/O接口接上拉电阻到VCC,下拉就是使I/O口接下拉电阻到GND。
上拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在高电平;并且在I/O端口输入为低电平的时候,输入端的电平也还是低电平。
(3)GPIO_Mode_IPD:下拉输入
下拉输入模式下,I/O端口的电平信号直接进入输入数据寄存器。但是在I/O端口悬空(在无信号输入)的情况下,输入端的电平可以保持在低电平;并且在I/O端口输入为高电平的时候,输入端的电平也还是高电平。
2. 输出模式(推挽/开漏、上拉/下拉)
(1)GPIO_Mode_Out_PP:推挽输出
推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止。
这种结构既可以输出高电平,也可以输出低电平,可以用于连接数字器件。
推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,
所以推挽电路导通损耗小,效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。
(2)GPIO_Mode_Out_OD:开漏输出
一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为如果外部不接上拉电阻时,只能输出低电平,所以想要输出高电平必须要外接上拉电阻。很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平,用于不同电压的系统之间的通信。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。)
利用外部电路的驱动能力,减小内部电流,当IC内部MOSFET导通时,驱动电流是从外部的VCC流经上拉电阻、MOSFET到GND。内部是需要是很小的栅极驱动电流。
开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时,通信的速度也受到上拉电阻阻值的影响。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,通信速度可以很快,但功耗大;反之延时大通信速度变慢功耗小。所以如果对延时有要求,则建议用下降沿输出。
可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。
3. 复用功能(推挽/开漏、上拉/下拉)
(1)GPIO_Mode_AF_PP:复用推挽输出
(2)GPIO_Mode_AF_OD:复用开漏输出
复用模式相当于把GPIO配置为第二功能能使用的时候的配置,并非作为通用I/O口使用。
4. 模拟输入输出(上下拉无影响)
(1)GPIO_Mode_AIN:模拟输入
I/O端口的模拟信号(电压信号,而非电平信号)直接模拟输入到片上外设模块,比如ADC模拟输入等。
GPIO端口置位/复位寄存器(GPIOx_BSRR):
端口低16位(0-15)为置位位,即端口的某一位(例如BS2)写1,则该一小位(BS2)输出高电平;若该一小位(例如BS2)写0,则该一小位(BS2)输出状态仍为原状态(并不是输出低电平)。
端口高16位(16-31)为复位位(与低16位置位位状态相反),即端口的某一位(例如BR2)写1,则该一小位(BR2)输出状态仍为原状态(并不是输出高电平);若该一小位(BR2)写0,则该一小位(BS2)输出低电平。
五、实验操作步骤
(一)使用寄存器开发模板编写GPIO应用程序
1. 编写led.c文件
Led.c文件用于进行LED初始化
void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量,该语句的作用是将GPIO_InitTypeDef结构体命名为GPIO_InitStructure
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); //使能端口F时钟,
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//设置输出模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;//管脚设置为F9和F10
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//IO口速度为100M
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF,&GPIO_InitStructure); //初始化结构体,初始化PF口
GPIO_SetBits(GPIOF,GPIO_Pin_9|GPIO_Pin_10);//输出高电平
}
2. 编写key.c文件
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOA,ENABLE); //使能端口PORTE、PORTA时钟
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //设置GPIO工作模式为输入
GPIO_InitStructure.GPIO_Pin=KEY_LEFT_Pin|KEY_DOWN_Pin|KEY_RIGHT_Pin;//设置管脚
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;//上拉
GPIO_Init(KEY_Port,&GPIO_InitStructure); //初始化结构体
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //设置GPIO工作模式为输入
GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin;//设置管脚
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉
GPIO_Init(KEY_UP_Port,&GPIO_InitStructure); //初始化结构体
}
KEY_Scan函数用于按键的扫描检测,mode为0时表示不支持连续按键,mode为1时可支持连续按键,按键K_UP是高电频有效,而K_DOWN、K_LEFT、K_RIGHT都是低电频有效,所以当K_UP为1时表示按下K_UP,而其它按键为0表示按下,由此可以完成按键的扫描检测,具体代码如下:
u8 KEY_Scan(u8 mode)
{
static u8 key=1;
if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0)) //任意一个按键按下
{
delay_ms(10); //减少抖动
key=0;
if(K_UP==1)
{
return KEY_UP;
}
else if(K_DOWN==0)
{
return KEY_DOWN;
}
else if(K_LEFT==0)
{
return KEY_LEFT;
}
else
{
return KEY_RIGHT;
}
}
else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1) //无按键按下
{
key=1;
}
if(mode==1) //连续按键按下
{
key=1;
}
return 0;
}
3. 编写main.c文件
寄存器开发即直接操作寄存器。
操作步骤和思路:
(1)RCC_AHB1ENR |= 1<<5
开启GPIOF时钟。RCC_AHB1ENR该寄存器的第5位是控制GPIOF外设的时钟使能位,只有该位为1时才使能,如果为0即关闭GPIOF时钟,所以要让1左移5位。
(2)GPIOF_MODER = (101<<(2*9))
配置GPIOF为输出模式。让PF9管脚输出一个低电平,所以采用输出模式。该寄存器每2位控制一个管脚,00表示输入(复位);01表示通用输出模式;10表示复用功能模式;11表示模拟模式,所以选择01通用输出模式,让1左移2*9位即可。
(3)GPIOF_BSRR=(1<<(16+9))
使PF9输出为低电平,GPIOF_BSRR为置位复位寄存器,其高16位用于复位,如果当高16位某位为1,表示那一位管脚输出为低电平,为0不影响其输出。如果当低16位的某位为1,表示那一位管脚输出为高电平,为0不影响其输出,所以让1左移16+9位。
具体代码:
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "key.h"
#include "stm32f4xx2.h"
typedef unsigned int u32;
void delay(u32 i)
{
while(i--);
}//延迟函数
int main()
{
u8 key,i=0;
SysTick_Init(168);
//LED_Init();
BEEP_Init();
KEY_Init();
while(1)
{
key=KEY_Scan(0); //扫描按键
switch(key)
{
case KEY_UP:
//led1闪烁
RCC_AHB1ENR |= 1<<5;
//初始化pf9和pf10工作模式通用输出模式01
GPIOF_MODER = (101<<(2*9));
while(1){
GPIOF_BSRR=(1<<(16+9));//高16位中的第9位置1,高电平
delay_ms(500);
GPIOF_BSRR=(1<<(9));//低16位汇总的第9位置1,高电平
delay_ms(500);
//led2闪烁
GPIOF_BSRR=(1<<(16+10));//高16位中的第10位置1,高电平
delay_ms(500);
GPIOF_BSRR=(1<<(10));//低16位中的第10位置1,高电平,具体参考GPIO端口置位/复位寄存器相关知识
delay_ms(500);
}
}
}
}
(二)使用库函数开发模板编写GPIO应用程序
1. 编写led.c文件
2. 编写key.c文件
库函数开发模板的led.c文件、key.c文件与寄存器开发模板一致
3. 编写main.c文件
#include "system.h"
#include "SysTick.h"
#include "stm32f4xx.h"
#include "led.h"
#include "key.h"
int main()
{
u8 key,i=0;
SysTick_Init(168);
LED_Init();//调用初始化led函数
KEY_Init();//调用初始化key函数
while(1)
{
key=KEY_Scan(0); //扫描按键程序
switch(key)
{
case KEY_UP: //当识别到KEY_UP键被按下时,进行led1与led2闪烁
while(1){
led1=!led1;//不断改变led1与led2的状态即可实现闪烁
delay_ms(500);//设置延迟为0.5秒,否则不容易观察到闪烁现象
led2=!led2;
delay_ms(500);}
}
}
}
六、实验结果
未按下KEY_UP健:
按下KEY_UP键:
led1与led2交替闪烁画面: