在之前我们都是讲解如何使用IMX6UL的GPIO输出控制等功能,IMX6U的IO不仅能作为输出,而且也可以作为输入,而我们开发板上具有一个按键,按键肯定是连接了一个IO口的额,我们在这一节将会把IO配置成输入功能,读取这个IO的值,即可获取按键的状态,然后可以利用这个特性来控制蜂鸣器
那么我们对这个实验做一个简单的分析,按键就只有两个状态,按下或者弹起,将按键连接在一个IO口上,读取这个IO口就知道按键是按下的还是弹起的。后面需要根据实际电路图来判断按键按下的时候是产生高电平还是低电平
下面就开始硬件的分析,按键KEY0连接到UART_CTS这个引脚的IO上,KEY0上面接了一个10K的上拉电阻,因此KEY0在没有被按下的时候是一个高电平的状态,当KEY0按下以后就改变了引脚的状态,也就是低电平
那么我们创建一个文件夹作为本次实验,可以是07_key,然后将前面蜂鸣器的代码复制过来,我们在此基础上添加代码,这一节实验除了要编写按键实验以外,我们还有个任务要求是,将我们的GPIO编写一个函数集合,也可以理解为封装一下,类似于STM32中的库,这样在之后每次编写的时候就不用再去反复操作寄存器了
首先创建bsp_gpio.h和bsp_gpio.c,然后先编写如下代码
typedef enum _gpio_pin_direction
{
kGPIO_DigitalInput = 0U,
kGPIO_DigitalOutput = 1U,
}gpio_pin_direction_t;
这里我们定义了一个名为_gpio_pin_direction的枚举类型,并通过类型别名,也就是起了另一个名字叫做gpio_pin_direction_t,也就是在之后引用这个名字的时候就变相的引用了上面的枚举类型,使用 gpio_pin_direction_t
会比直接使用 _gpio_pin_direction
更简洁
kGPIO_DigitalInput = 0U是枚举的第一个成员,也被称为枚举常量表示GPIO引脚被配置为数字输入模式。0U表示这个成员的值是0,U后缀表示这是一个无符号整形(unsigned integer)字面量,保证涉及枚举值的计算时保持一致,避免隐式类型转换,且枚举成员主要用于表示一组固定的、命名的常量值
然后定义一个结构体,
#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#include "imx6ul.h"
typedef enum _gpio_pin_direction
{
kGPIO_DigitalInput = 0U,
kGPIO_DigitalOutput = 1U,
}gpio_pin_direction_t;
typedef struct _gpio_pin_config
{
gpio_pin_direction_t direction; /*GPIO方向,输入还是输出*/
uint8_t outputLogic; /*如果是输出的话,默认输出电平*/
}gpio_pin_config_t;
在结构体的这行代码中,gpio_pin_direction_t意思是声明了一个名为direction的枚举变量,它的类型是direction_t。也就是上面的枚举类型(gpio_pin_direction_t diretion)。这个变量可以存储该枚举类型中定义的任何一个常量(也称为枚举成员)
这里的意思是,在结构体中的枚举变量direction可以被赋值为 kGPIO_DigitalInput = 0U或者kGPIO_DigitalOutput = 1U,中的任何一个。这些常量是枚举类型gpio_pin_direction_t的成员
outputLogic是创建一个这个uint8_t的变量,被用作了结构体的一部分
#include "bsp_gpio.h"
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
if(config->direction == kGPIO_DigitalInput)
{
base->GDIR &= ~(1 << pin);
}
else
{
base->GDIR |= 1 << pin;
gpio_pinwrite(base,pin, config->outputLogic);
}
}
然后我们在bsp_gpio.c当中这么写,首先这段代码的作用是用于初始化GPIO引脚的函数,它接受三个返回参数,一个指向GPIO基地址的指针base,一个整数pin表示要操作的引脚编号,以及一个指向gpio_pin_config_t结构体的指针config,这个函数当中有一个if判断语句,如果方向是输入,那么就执行if语句当中的对应引脚给初始化
那么反之如何不是输入,而是输出,那么就通过下面一个自定义的函数,老控制要输出的电平,所以接下来还要继续编写gpio_pinwrite
那么现在就开始编写引脚输出电平的函数,代码如下,这个代码不需要怎么解释,也就是0对应输出低电平,然后操作GPIOx_DR寄存器,如果不是0那么就输出高电平,同样也是控制GPIOx_DR寄存器,来输出高电平
void gpio_pinwrite(GPIO_Type *base ,int pin, int value)
{
if(value == 0u)
{
base->DR &= ~(1U << pin); //输出低电平
}
else
{
base->DR |= 1U << pin; //输出高电平
}
}
那么有了输出电平的控制函数,那么我们设计到按键,所以我们也需要一个读取GPIO电平的状态读取函数,所以下一个我们编写这个函数,然后代码如下,主要会解释这个return函数的含义
一个括号一个括号来解释,首先,通过指针base访问GPIO的寄存器(DR)的当前值
((base->DR) >> pin ),然后将这个值右移pin位,目的就是将我们需要读取的引脚移动到最低位进行读取,因为前面提到过,如果需要读取寄存器当中某个位的值,必须要将其移动到最右边。
然后下一步就是将移动到最右边的最低位进行保留,然后将其他的位全部清零
结合return就是将,最后引脚的结果返回,返回的是一个整数,要么是0也就是低电平,要么就是高电平也就是数据位1
简单来描述就是,gpio_pinread
函数通过读取GPIO模块的数据寄存器(DR),并将指定引脚的状态移动到最低位,然后通过与 0x1
进行按位与操作来提取这个状态,最后返回这个状态。
int gpio_pinread(GPIO_Type *base , int pin)
{
return (((base->DR) >> pin ) &0x1);
}
最后需要完成GPIO函数的封装就是将上面我们描写的函数然后在.h文件当中去申明一下,下面将展示一个完整gpio.c和gpio.h文件
//bsp_gpio.c
#include "bsp_gpio.h"
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
if(config->direction == kGPIO_DigitalInput)
{
base->GDIR &= ~(1 << pin);
}
else
{
base->GDIR |= 1 << pin;
gpio_pinwrite(base,pin, config->outputLogic);
}
}
int gpio_pinread(GPIO_Type *base , int pin)
{
return (((base->DR) >> pin ) &0x1);
}
void gpio_pinwrite(GPIO_Type *base ,int pin, int value)
{
if(value == 0u)
{
base->DR &= ~(1U << pin); //输出低电平
}
else
{
base->DR |= 1U << pin;
}
}
//_bsp_gpio.h
#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#include "imx6ul.h"
typedef enum _gpio_pin_direction
{
kGPIO_DigitalInput = 0U,
kGPIO_DigitalOutput = 1U,
}gpio_pin_direction_t;
typedef struct _gpio_pin_config
{
gpio_pin_direction_t direction; /*GPIO方向,输入还是输出*/
uint8_t outputLogic; /*如果是输出的话,默认输出电平*/
}gpio_pin_config_t;
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
int gpio_pinread(GPIO_Type *base, int pin);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
#endif
在完成了GPIO的封装后,下一步就要开始编写按键的初始化了,创建一个文件夹命名为key,下面创建两个文件分别是bsp_key.c和bap_key.h,然后我们先看原理图,key引脚接在UART1_CTS这个引脚上面
首先编写.h文件,创建一个枚举类型keyvalue,因为我们的开发板上只有一个按键,但是考虑到代码的移植性,所以这里枚举了三个成员变量分别为,KEY0,1,2,
#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "imx6ul.h"
enum keyvalue{
KEY_NONE =0,
Key0_Value,
Key1_Value,
key2_value,
};
#endif
然后开始编写.c文件,代码如下
/*初始化按键*/
void key_init()
{
gpio_pin_config_t key_config;
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); //IO引脚复用,将引脚复用为GPIO1_IO18
/* 2、、配置UART1_CTS_B的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 11 默认22K上拉
*bit [13]: 1 pull功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 000 关闭输出
*bit [0]: 0 低转换率
*/
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080); /*1111 0000 1000 0000*/
/*初始化GPIO*/
key_config.direction = kGPIO_DigitalInput;
gpio_init(GPIO1,18,&key_config);
}
首先IOMUXC_SetPinMux这个函数依然是为了设置IO引脚的复用,然后IOMUXC_SetPinConfig是为了设置对应引脚的电气属性,为什么是0xF080我已经注释出来了,直接参考就可以了
首先我们需要了解的就是下面的gpio_init这个函数的参数,主要是最后一个参数gpio_pin_config_t* config,意味着这个函数期望接受一个指向该结构体实例的指针作为参数,这个指针通常指向一个已经初始化好的gpio_pin_config_t实例
所以我们就需要在代码中创建一个结构体实例,这个实例也是一个具体的变量,包含了结构体定义的所有成员变量。所以代码为gpio_pin_config_t key_config;
然后接下里就要设置,结构体实例的成员变量,为结构体中的各个成员变量赋予适当的值,创建了实例过后,通过代码 key_config.direction = kGPIO_DigitalInput;访问key_config结构体中名为direction的成员方式,然后将kGPIO_DigitalInput
的值赋给 key_config.direction,那么按键的初始化就完成了
那么接下就要开始编写按键电平读取的函数了,这里定义一个ret变量将其初始化为0,用于存储函数的返回值,然后再定义一个静态的unsigned char
类型的变量release,将其初始化为1.为什么使用静态,因为可以保证在函数调用期间保持其值不变,在之后我们会使用这个变量来防止按键抖动,因为暂时没有涉及到寄存器,所以这里统一使用延时消抖的方法来避免
int getvalue()
{
int ret = 0;
static unsigned char release = 1;
if((release == 1)&&(gpio_pinread(GPIO1,18)==0))
{
delay(10);
release = 0;
if(gpio_pinread(GPIO1,18) == 0)
{
ret =Key0_Value;
}
}
else if(gpio_pinread(GPIO1,18) == 1)
{
ret = 0;
release = 1;
}
return ret;
}
首先,先检查release以及GPIO1的IO18是否为低电平,如果为低电平就说明按键被按下了,如果条件满足条件则,先执行去除抖动,然后将release设置为0,表示现在处于去抖动的状态,然后再去检查对应的GPIO1的IO18引脚是否为低电平,如果为低电平则则将ret设置为Key0_Value这个值,最终将ret的值返回
但是如果在刚开始的时候,if的条件判断错误,release 不为1,或者 GPIO1 的第18引脚为高电平(1),则将ret设置为0,表示按键未被按下,并且将release设置重置为1,为下一次检测做准备。最后的操作同样是将ret的值返回
然后最后就是在bsp_key.h申明一下我们在这里编写的函数,驱动以及初始化函数全部都已经编写完了,接下来就可以开始准备编写主函数
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
int main()
{
int keyvalue = 0;
unsigned char led_state = 0;
unsigned char beep_state = 0;
clk_enable();
led_init();
beep_init();
key_init();
while(1)
{
keyvalue = key_getvalue();
if(key_getvalue)
{
switch ((keyvalue))
{
case Key0_Value:
beep_state = !beep_state;
beep_switch(beep_switch);
break;
}
}
led_state =!led_state;
led_switch(LED0,led_state);
delay(10);
}
}
return 0;
}
上面的代码是主函数的代码,主要目的就是同过按键控制BSP蜂鸣器,通过key_getvalue,来判断按键是否按下,如果按下就直接通过switch语句,就可以使得蜂鸣器使能了
然后keyvalue循环执行完后,就开始执行下面的LED常亮的代码,通过反转前面的变量,再通过switch语句来控制LED0的亮状态,然后返回值,主函数的代码就执行完成了,最后将我们的代码烧写到板子当中,然后我们的这项实验就完成了
感谢各位的阅读,如果有任何问题的欢迎在评论区指出,或者与作者讨论