1、前言
在前面的文章《C語言裸機GPIO控制—基於I.MX6UL嵌入式SoC》中,鏈接如下:
https://www.cnblogs.com/Cqlismy/p/12445576.html
實現了I.MX6UL嵌入式SoC中通用輸入/輸出接口外設的輸出功能,我們都知道I.MX6UL芯片上的IO口除了能作為輸出,還能夠作為輸入,作為GPIO的輸入功能后,能夠讀取到當前引腳的電平狀態,最典型、最簡單的應用就是按鍵,按鍵一般就兩個狀態,分別為按下和彈起,當我們將按鍵的另一端接到I.MX6UL上的IO引腳上,就可以通過讀取這個IO引腳的電平值來判斷當前按鍵狀態是處於按下還是彈起狀態。
2、GPIO按鍵輸入原理
單個按鍵的硬件原理如下所示:

+E為目標板電源,為3.3V,按鍵K的另一端(Y)連接到I.MX6UL處理器的GPIO4_IO22這個IO引腳上面,GPIO4_IO22這個IO引腳復用功能為GPIO,方向為輸入,默認上拉,當按鍵K未按下時,IO口的引腳狀態為高電平,當按鍵K按下時,IO口直接和GND導通,所以此時IO口的引腳狀態為低電平,由於按鍵的機械結構,當按鍵按下或者松開期間,會產生一定的抖動,所謂的抖動就是IO的電平會出現多次電平跳動,實際的按鍵波形圖如上所示,如果我們不進行按鍵抖動消除處理的話,可能會產生誤判的現象,有可能按鍵只按了一次,結果使用程序對IO口電平讀取發現按鍵按下了多次。
使用軟件進行消抖的最簡單處理就是進行延時處理,延時跳過抖動時間后,再去讀引腳的IO口電平,如果此時的IO電平為低,則說明按鍵確實是被按下了,有事件觸發了,需要進行事件處理,一般的延時消抖時間大約10ms即可。
3、GPIO按鍵輸入程序
對GPIO按鍵輸入的實現原理有一定的了解后,接下來看看如何編程實現,編程思路如下:
- 使能相應的按鍵IO時鍾;
- 設置IO口的復用模式為GPIO,設置GPIO方向為輸入並設置IO引腳的電氣屬性;
- 主函數中讀取GPIO的電平狀態,判斷是否有按鍵事件觸發,如果有的話,進行相應的事件處理。
先修改gpio驅動模塊的相關函數,bsp_gpio.h文件內容如下:
#ifndef __BSP_GPIO_H #define __BSP_GPIO_H #include "imx6ul.h" /* GPIO方向定義 */ typedef enum _gpio_pin_direction { kGPIO_DigitalInput = 0U, /* 表示GPIO方向輸入 */ kGPIO_DigitalOutput = 1U, /* 表示GPIO方向輸出 */ } gpio_pin_direction_t; typedef struct _gpio_pin_config { gpio_pin_direction_t direction; /* GPIO的方向 */ unsigned char value; /* GPIO輸出時默認引腳電平值 */ } gpio_pin_config_t; /* GPIO操作函數相關聲明 */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config); int gpio_pin_read(GPIO_Type *base, int gpio); void gpio_pin_write(GPIO_Type *base, int gpio, int value); #endif
gpio操作的相關函數定義在bsp_gpio.c文件中,內容如下所示:
#include "bsp_gpio.h" /** * gpio_init() - GPIO初始化函數 * * @base: 要初始化的GPIO組,例如:GPIO1、GPIO2 * @pin: 要初始化的GPIO組的pin編號 * @config: gpio引腳配置結構體 * * @return: 無 */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config) { if (config->direction == kGPIO_DigitalInput) /* GPIO方向為輸入 */ base->GDIR &= ~(1 << pin); else { base->GDIR |= (1 << pin); gpio_pin_write(base, pin, config->value); } } /** * gpio_pin_read() - 讀取GPIO引腳的電平 * * @base: 要讀取的GPIO組,例如:GPIO1、GPIO2 * @pin: 要讀取的GPIO組的pin編號 * * @return: 0表示低電平,1表示高電平 */ int gpio_pin_read(GPIO_Type *base, int pin) { return (((base->DR) >> pin) & 0x1); } /** * gpio_pin_write() - 設置GPIO引腳的電平 * * @base: 要設置的GPIO組,例如:GPIO1、GPIO2 * @pin: 要設置的GPIO組的pin編號 * @value: 引腳要設置的電平值:0->低電平,1->高電平 * * @return: 無 */ void gpio_pin_write(GPIO_Type *base, int pin, int value) { if (0 == value) base->DR &= ~(1 << pin); /* 引腳輸出低電平 */ else base->DR |= (1 << pin); /* 引腳輸出高電平 */ }
gpio_init()函數用完成GPIO的初始化,主要是設置GPIO的方向,輸入或者輸出,如果GPIO的方向是輸出的話,還要設置GPIO的默認輸出電平,gpio_pin_read()函數用來讀取相應的IO引腳的當前電平狀態,是通過讀取GPIOx_DR這個寄存器來實現的,gpio_pin_write()函數用來設置GPIO引腳的電平值,也是通過設置GPIOx_DR這個寄存器來實現的。
接下來,進入到bsp目錄下,新創建key子目錄,用來存放和按鍵功能相關的驅動文件:
$ cd bsp/bsp $ mkdir key $ cd key $ touch bsp_key.h $ touch bsp_key.c
bsp_key.h文件用來定義按鍵的鍵值以及一些函數聲明,內容如下:
#ifndef __BSP_KEY_H #define __BSP_KEY_H #include "imx6ul.h" #include "bsp_gpio.h" #include "bsp_delay.h" /* 定義按鍵值 */ enum _key_value { KEY_NONE = 0, KEY0_VALUE, } key_value; /* 和按鍵操作相關函數聲明 */ void key_init(void); int key_get_value(void); #endif
按鍵驅動函數的實現在bsp_key.c中,內容如下:
#include "bsp_key.h" /** * key_init() - 按鍵初始化函數 * * @return: 無 */ void key_init(void) { gpio_pin_config_t key_config; /* 設置CSI_DATA01引腳IO復用為GPIO4_IO22 */ IOMUXC_SetPinMux(IOMUXC_CSI_DATA01_GPIO4_IO22, 0); /* 配置GPIO4_IO22引腳電氣屬性 * bit[16]: 0 關閉HYS * bit[15:14]: 11 pull up 22k * bit[13]: 1 pull * bit[12]: 1 pull/keeper使能 * bit[11]: 0 禁止開路輸出 * bit[10:8] 000 reserved * bit[7:6]: 10 速度為100MHz * bit[5:3]: 000 關閉輸出 * bit[2:1]: 00 reserved
* bit[0]: 0 低擺率 */ IOMUXC_SetPinConfig(IOMUXC_CSI_DATA01_GPIO4_IO22, 0xF080); /* 將按鍵相關的GPIO方向設置為輸入 */ key_config.direction = kGPIO_DigitalInput;
key_config.value = 1; gpio_init(GPIO4, 22, &key_config); } /** * key_get_value() - 獲取按鍵的鍵值 * * @return: 0表示沒有按鍵按下,1表示按鍵按下 */ int key_get_value(void) { int ret = KEY_NONE; static unsigned char release = 1; /* 表示按鍵處於釋放狀態 */ if ((release == 1) && (gpio_pin_read(GPIO4, 22) == 0)) { /* 按鍵按下 */ delay(10); /* 延時消抖 */ if (gpio_pin_read(GPIO4, 22) == 0) { /* 再次判斷按鍵是否按下 */ release = 0; ret = KEY0_VALUE; } } else if (gpio_pin_read(GPIO4, 22) == 1) { /* 按鍵未按下 */ release = 1; /* 標記按鍵處於釋放狀態 */ ret = KEY_NONE; } return ret; }
key_init()函數用來完成按鍵IO口引腳的初始化,主要是完成IO口引腳的復用功能配置以及IO引腳的電氣屬性配置,最后,需要設置GPIO的方向為輸入,key_get_value()函數則是用來獲取按鍵的鍵值,當按鍵按下后,該函數返回1,當按鍵處於松開狀態時,該函數返回0,主要是通過讀取IO口引腳的電平狀態來進行判斷的,這就是按鍵的驅動函數。
app.c文件內容如下:
#include "bsp_clk.h" #include "bsp_delay.h" #include "bsp_gpio.h" #include "bsp_led.h" #include "bsp_key.h" /** * main() - 主函數 */ int main(void) { int key_value = KEY_NONE; unsigned char led_state = OFF; system_clk_enable(); /* 外設時鍾使能 */ led_init(); /* LED燈初始化 */ key_init(); /* 按鍵初始化 */ while (1) { key_value = key_get_value(); /* 獲取按鍵狀態 */ if (key_value == KEY0_VALUE) { led_state = !led_state; led_switch(led_state); key_value = KEY_NONE; } delay(10); } return 0; }
在循環里面不斷獲取按鍵的狀態,也就是獲取GPIO引腳的電平狀態,如果按鍵按下,對應的LED燈狀態會進行相應的翻轉。
4、小結
本文主要簡單介紹了I.MX6UL嵌入式SoC中的GPIO外設作為輸入時,如何進行IO口電平狀態的讀取,並以一個簡單的按鍵輸入實例進行介紹。
