第八章 按键输入实验 本章实验将介绍如何使用SDK编程让Kendryte K210获取板载按键的状态。通过本章的学习,读者将学习到用SDK编程技术读取Kendryte K210的GPIO上的高低电平状态。 本章分为如下几个小节: 8.1 GPIOHS介绍 8.2 硬件设计 8.3 程序设计 8.4 运行验证 8.1 GPIOHS介绍 本章实验用到的外设是GPIOHS,通用GPIO和GPIOHS除了中断功能有差异的话,其他都差不多,前面第六章实验我们已经介绍了GPIOHS外设的功能,接下来我们看下GPIOHS 的一些函数。 1,gpiohs_set_drive_mode函数 该函数用来配置指定管脚的工作模式,包括输入输出模式、上下拉等,如下代码所示: /* 函数原型 */ void gpiohs_set_drive_mode(uint8_t pin, gpio_drive_mode_t mode); /** * gpiohs_set_drive_mode功能的mode配置参数 */ typedef enum _gpio_drive_mode { GPIO_DM_INPUT, GPIO_DM_INPUT_PULL_DOWN, GPIO_DM_INPUT_PULL_UP, GPIO_DM_OUTPUT, } gpio_drive_mode_t; 下面我们来讲解一下这几个变量的作用。 ①:参数pin为引脚软件编号,最大值要小于8。 ②:参数mode为指定的配置模式,如下表所示: 表8.1.1 引脚配置的模式 该函数无返回值。 2,gpiohs_set_pin函数 该函数用来设置GPIO的电平状态,该函数原型及参数描述如下所示: void gpiohs_set_pin(uint8_t pin, gpio_pin_value_t value); typedef enum _gpio_pin_value { GPIO_PV_LOW, GPIO_PV_HIGH } gpio_pin_value_t; 下面我们讲解下这个函数的两个参数,第一个是引脚的软件编号,同上,第二个参数用于设置IO管脚的高低电平状态,GPIO_PV_LOW为指向IO输出低电平, GPIO_PV_HIGH为输出的高电平。该函数无返回值。 3,gpiohs_get_pin函数 该函数用来获取IO的电平状态,该函数原型如下所示: gpio_pin_value_t gpiohs_get_pin(uint8_t pin); pin用来指向获取那个IO的电平,该函数的返回值就是指定IO的电平状态0或1。 4,gpiohs_set_pin_edge函数 该函数用来配置指定管脚中断触发方式,如上升沿触发、低电平触发,如下代码所示: /* 函数原型 */ void gpiohs_set_pin_edge(uint8_t pin, gpio_pin_edge_t edge); /* gpiohs_set_pin_edge功能的edge配置参数 */ typedef enum _gpio_pin_edge { GPIO_PE_NONE, GPIO_PE_FALLING, GPIO_PE_RISING, GPIO_PE_BOTH, GPIO_PE_LOW, GPIO_PE_HIGH = 8, } gpio_pin_edge_t; 下面我们来讲解一下这几个变量的作用。 ② :参数pin为引脚软件编号,最大值要小于8。 ②:参数edge为配置引脚的中断方式,如下表所示: 表8.1.2 引脚中断触发方式配置 该函数无返回值。 5,gpiohs_irq_register函数 该函数用来注册指定引脚的中断,设置优先级、中断回调函数和回调参数,该函数原型如下所示: /* 函数原型 */ void gpiohs_irq_register(uint8_t pin, uint32_t priority, plic_irq_callback_t callback, void *ctx); /* gpiohs_irq_register功能的callback配置参数 */ typedef int (*plic_irq_callback_t)(void *ctx); pin用来指向注册中断的IO,priority是中断优先级,数字越小中断优先级越高,后面两个是设置中断回调函数和回调的参数,该函数无返回值。 可以看到,前几个函数用法基本和通用GPIO一样,本章实验也没有用到中断,无法区分明显差异,我们在下一章会讲解按键中断实验。 8.2 硬件设计 8.2.1 例程功能 1. 当KEY0按键被按下后,双色LED的两个灯均熄灭 2. 当KEY1按键被按下后,双色LED的蓝灯亮起,红灯熄灭 3. 当KEY2按键被按下后,双色LED的红灯亮起,蓝灯熄灭 8.2.2 硬件资源 1. 双色LED LEDR - IO24 LEDB - IO25 2. 独立按键 KEY0按键 - IO18 KEY1按键 - IO19 KEY2按键 - IO16 8.2.3 原理图 本章实验内容,需要读取独立按键连接IO上的电平状态,正点原子DNK210开发板上独立按键的连接原理图,如下图所示: 图8.2.3.1 独立按键连接原理图 通过以上原理图可以看出,KEY0按键、KEY1按键和KEY2按键对应的IO编号分别为IO18、IO19和IO16,当独立按键没有被按下时,其对应的IO将处于悬空状态,此时读取到的电平将由IO的上下拉决定,当独立按键被按下后,其对应IO的电平将被拉低。 8.3 程序设计 8.3.1 按键驱动代码 按键驱动源码包括两个文件:key.c和key.h,我们先介绍key.h。 /* 硬件IO口,与原理图对应 */ #define PIN_KEY_0 (18) #define PIN_KEY_1 (19) #define PIN_KEY_2 (16) /*****************************SOFTWARE-GPIO********************************/ /* 软件GPIO口,与程序对应 */ #define KEY0_GPIONUM (0) #define KEY1_GPIONUM (1) #define KEY2_GPIONUM (2) /*****************************FUNC-GPIO************************************/ /* GPIO口的功能,绑定到硬件IO口 */ #define FUNC_KEY0 (FUNC_GPIOHS0 + KEY0_GPIONUM) #define FUNC_KEY1 (FUNC_GPIOHS0 + KEY1_GPIONUM) #define FUNC_KEY2 (FUNC_GPIOHS0 + KEY2_GPIONUM) /******************************************************************************************/ 首先是硬件管脚和GPIOHS的功能号绑定,我们这里用到的是GPIOHS0、GPIOHS1和GPIOHS2。 #define KEY0 gpiohs_get_pin(KEY0_GPIONUM) /* 读取KEY0引脚 */ #define KEY1 gpiohs_get_pin(KEY1_GPIONUM) /* 读取KEY1引脚 */ #define KEY2 gpiohs_get_pin(KEY2_GPIONUM) /* 读取KEY2引脚 */ #define KEY0_PRES 1 /* KEY0按下 */ #define KEY1_PRES 2 /* KEY1按下 */ #define KEY2_PRES 3 /* KEY2按下 */ 这里KEY0、KEY1和KEY2是对应按键状态的宏定义,用来接收对应引脚的电平信号,gpiohs_get_pin函数返回值就是IO口的状态,取值0或者1。 KEY0_PRES、KEY1_PRES和KEY2_PRES则是按键对应的三个键值宏定义标识符。 接下来我们看key.c文件,主要包括初始化函数key_init()和按键读取函数key_scan(),我们先看key_init()函数。 void key_init(void) { fpioa_set_function(PIN_KEY_0, FUNC_KEY0); fpioa_set_function(PIN_KEY_1, FUNC_KEY1); fpioa_set_function(PIN_KEY_2, FUNC_KEY2); gpiohs_set_drive_mode(KEY0_GPIONUM, GPIO_DM_INPUT_PULL_UP); /*输入上拉*/ gpiohs_set_drive_mode(KEY1_GPIONUM, GPIO_DM_INPUT_PULL_UP); /*输入上拉*/ gpiohs_set_drive_mode(KEY2_GPIONUM, GPIO_DM_INPUT_PULL_UP); /*输入上拉*/ } 可以看到,GPIOHS外设使用并不需要使用GPIO口使能,关联硬件管脚后直接设置模式即可,因为独立按键默认悬空,按下后接入低电平,所以悬空状态我们要将引脚拉高,使用输入上拉模式,接下来我们看另外个函数。 uint8_t key_scan(uint8_t mode) { static uint8_t key_up = 1; /* 按键按松开标志 */ uint8_t keyval = 0; if (mode) key_up = 1; /* 支持连按 */ if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0)) { msleep(50); /* 去抖动 */ key_up = 0; if (KEY0 == 0) keyval = KEY0_PRES; if (KEY1 == 0) keyval = KEY1_PRES; if (KEY2 == 0) keyval = KEY2_PRES; } else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1) { key_up = 1; } return keyval; /* 返回键值 */ } key_scan函数用于扫描这3个IO口是否有按键按下。它支持两种扫描方式,通过mode参数来设置。 当mode为0的时候,key_scan函数将不支持连续按,扫描某个按键,该按键按下之后必须要松开,才能第二次触发,否则不会再响应这个按键,这样的好处就是可以防止按一次多次触发,而坏处就是在需要长按的时候比较不合适。 当mode为1的时候,key_scan函数将支持连续按,如果某个按键一直按下,则会一直返回这个按键的键值,这样可以方便的实现长按检测。 有了mode这个参数,大家就可以根据自己的需要,选择不同的方式。这里要提醒大家,因为该函数里面有static变量,所以该函数不是一个可重入函数,在有OS的情况下,这个大家要留意下。msleep()是毫秒级延时函数,可以看到该函数的消抖延时是50ms。同时还有一点要注意的是,该函数的按键扫描是有优先级的,最高优先级的是KEY2,第二优先的是KEY1, 最后是按键KEY0。该函数有返回值,如果有按键按下,则返回非0值,如果没有或者按键不正确,则返回0。 8.3.2 main.c代码 main.c中的代码如下所示: #include <stdio.h> #include <unistd.h> #include <sleep.h> #include "./BSP/KEY/key.h" #include "./BSP/LED/led.h" int main(void) { uint8_t key; led_init(); /* LED初始化 */ key_init(); /* 按键初始化 */ while (1) { key = key_scan(0); /* 得到键值 */ if (key) { switch (key) { case KEY2_PRES: /* 控制红灯亮 */ LEDR(0); LEDB(1); break; case KEY1_PRES: /* 控制蓝灯亮 */ LEDR(1); LEDB(0); break; case KEY0_PRES: /* 同时关闭红灯和蓝灯 */ LEDR(1); LEDB(1); break; } } else { msleep(10); } } } 可以看到,首先执行LED初始化和按键初始化,初始化完成之后就能实现LED的亮灭和按键的读取,最后在一个循环分别读取KEY0按键、KEY1按键和KEY2按键对应的GPIO输入电平,以判断独立按键是否被按下,若KEY0按键被按下,则控制对应的GPIO输出高电平以控制两个LED灯熄灭,KEY1按键和KEY2按键的读取和按键解释同理。 8.4 运行验证 将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,此时,若按下板载的KEY0按键,则能看到双色LED的两个灯熄灭,若按下KEY1按键,则能看到双色LED的蓝色灯亮起,红灯熄灭,若按下KEY2按键,则能看到双色LED的红色灯亮起,蓝灯熄灭,这与理论推断的结果一致。
|