按鍵就兩個狀態:按下或彈起,將按鍵連接到一個 IO 上,通過讀取這個 IO 的值就知道按鍵是按下的還是彈起的。至於按鍵按下的時候是高電平還是低電平要根據實際電路來判斷。當 GPIO 連接按鍵的時候就要做為輸入使用。我們的主要工作就是配置按鍵所連接的 IO 為輸入功能,然後讀取這個 IO 的值來判斷按鍵是否按下。
I.MX6U-ALPHA 開發板上有一個按鍵KEY0,我們將會編寫代碼通過這個 KEY0 按鍵來控制開發板上的蜂鳴器,按一下KEY0 蜂鳴器打開,再按一下蜂鳴器就關閉。
本試驗我們用到的硬件有:
1) LED 燈 LED0。
2)蜂鳴器。
3)1 個按鍵KEY0。
按鍵KEY0 的原理圖如圖所示:
從圖中可以看出,按鍵 KEY0 是連接到 I.MX6U 的 UART1_CTS 這個 IO 上的,KEY0接了一個 10K 的上拉電阻,因此 KEY0 沒有按下的時候UART1_CTS 應該是高電平,當 KEY0按下以後UART1_CTS 就是低電平。
本試驗在上一篇文章《 》中例程的基礎上完成,重新創建 VSCode 工程,工作區名字為“key”,在工程目錄的bsp 文件夾中創建名為“key”和“gpio”兩個文件夾。按鍵相關的驅動文件都放到“key”文件夾中,本章試驗我們對 GPIO 的操作編寫一個函數集合,也就是編寫一個 GPIO驅動文件,GPIO 的驅動文件放到“gpio”文件夾裡面。
新建 bsp_gpio.c 和 bsp_gpio.h 這兩個文件,將這兩個文件都保存到剛剛創建的 bsp/gpio 文件夾裡面,然後在bsp_gpio.h 文件夾裡面輸入如下內容:
11 #ifndef _BSP_GPIO_H
12 #define _BSP_GPIO_H
13 #define _BSP_KEY_H
14 #include "imx6ul.h"
15
16 /* 枚舉類型和結構體定義 */
17 typedef enum _gpio_pin_direction
18 {
19 kGPIO_DigitalInput = 0U, /* 輸入 */
20 kGPIO_DigitalOutput = 1U, /* 輸出 */
21 } gpio_pin_direction_t;
22
23 /* GPIO 配置結構體 */
24 typedef struct _gpio_pin_config
25 {
26 gpio_pin_direction_t direction; /* GPIO 方向:輸入還是輸出 */
27 uint8_t outputLogic; /* 如果是輸出的話,默認輸出電平 */
28 } gpio_pin_config_t;
29
30
31 /* 函數聲明 */
32 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
33 int gpio_pinread(GPIO_Type *base, int pin);
34 void gpio_pinwrite(GPIO_Type *base, int pin, int value);
35
36 #endif
bsp_gpio.h 中定義了一個枚舉類型 gpio_pin_direction_t 和結構體 gpio_pin_config_t,枚舉類型 gpio_pin_direction_t 表示 GPIO 方向,輸入或輸出。結構體 gpio_pin_config_t 是 GPIO 的配置結構體,裡面有 GPIO 的方向和默認輸出電平兩個成員變量。在 bsp_gpio.c 中輸入如下所示內容:
11 #include "bsp_gpio.h"
12
13 /*
14 * @description : GPIO 初始化。
15 * @param - base : 要初始化的 GPIO 組。
16 * @param - pin : 要初始化 GPIO 在組內的編號。
17 * @param - config : GPIO 配置結構體。
18 * @return : 無
19 */
20 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
21 {
22 if(config->direction == kGPIO_DigitalInput) /* 輸入 */
23 {
24 base->GDIR &= ~( 1 << pin);
25 }
26 else /* 輸出 */
27 {
28 base->GDIR |= 1 << pin;
29 gpio_pinwrite(base,pin, config->outputLogic);/* 默認輸出電平 */
30 }
31 }
32
33 /*
34 * @description : 讀取指定 GPIO 的電平值 。
35 * @param – base : 要讀取的 GPIO 組。
36 * @param - pin : 要讀取的 GPIO 腳號。
37 * @return : 無
38 */
39 int gpio_pinread(GPIO_Type *base, int pin)
40 {
50 return (((base->DR) >> pin) & 0x1);
42 }
51 void gpio_pinwrite(GPIO_Type *base, int pin, int value)
52 {
53 if (value == 0U)
54 {
55 base->DR &= ~(1U << pin); /* 輸出低電平 */
56 }
57 else
58 {
59 base->DR |= (1U << pin); /* 輸出高電平 */
60 }
61 }
文件 bsp_gpio.c 中有三個函數:gpio_init、gpio_pinread 和 gpio_pinwrite,函數 gpio_init 用於初始化指定的 GPIO 引腳,最終配置的是 GDIR 寄存器,此函數有三個參數,這三個參數的含義如下:
base: 要初始化的GPIO 所屬於的 GPIO 組,比如 GPIO1_IO18 就屬於GPIO1 組。
pin:要初始化 GPIO 在組內的標號,比如GPIO1_IO18 在組內的編號就是 18。
config: 要初始化的GPIO 配置結構體,用來指定GPIO 配置為輸出還是輸入。
函數 gpio_pinread 是讀取指定的GPIO 值,也就是讀取 DR 寄存器的指定位,此函數有兩個參數和一個返回值,參數含義如下:
base: 要讀取的GPIO 所屬於的GPIO 組,比如GPIO1_IO18 就屬於GPIO1 組。
pin:要讀取的 GPIO 在組內的標號,比如GPIO1_IO18 在組內的編號就是 18。返回值:讀取到的GPIO 值,為 0 或者 1。
函數 gpio_pinwrite 是控制指定的GPIO 引腳輸入高電平(1)或者低電平(0),就是設置 DR 寄存器的指定位,此函數有三個參數,參數含義如下:
base: 要設置的GPIO 所屬於的GPIO 組,比如GPIO1_IO18 就屬於GPIO1 組。
pin:要設置的 GPIO 在組內的標號,比如GPIO1_IO18 在組內的編號就是 18。
value: 要設置的值,1(高電平)或者 0(低電平)。
我們以後就可以使用函數gpio_init 設置指定GPIO 為輸入還是輸出,使用函數 gpio_pinread和 gpio_pinwrite 來讀寫指定的GPIO,文件 bsp_gpio.c 文件就講解到這裡。
接下來編寫按鍵驅動文件,新建 bsp_key.c 和 bsp_key.h 這兩個文件,將這兩個文件都保存到剛剛創建的bsp/key 文件夾裡面,然後在bsp_key.h 文件夾裡面輸入如下內容:
11 #ifndef _BSP_KEY_H
12 #define _BSP_KEY_H
13 #include "imx6ul.h"
14
15 /* 定義按鍵值 */
16 enum keyvalue{
17 KEY_NONE = 0,
18 KEY0_VALUE,
19 };
20
21 /* 函數聲明 */
22 void key_init(void);
23 int key_getvalue(void);
24
25 #endif
bsp_key.h 文件中定義了一個枚舉類型:keyvalue,此枚舉類型表示按鍵值,因為開發板上只有一個按鍵,因此枚舉類型裡面只到 KEY0_VALUE。在 bsp_key.c 中輸入如下所示內容:
11 #include "bsp_key.h"
12 #include "bsp_gpio.h"
13 #include "bsp_delay.h"
14
15 /*
16 * @description : 初始化按鍵
17 * @param : 無
18 * @return : 無
19 */
20 void key_init(void)
21 {
22 gpio_pin_config_t key_config;
23
24 /* 1、初始化 IO 複用, 複用為 GPIO1_IO18 */
25 IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
26
27 /* 2、、配置 UART1_CTS_B 的 IO 屬性
28 *bit 16:0 HYS 關閉
29 *bit [15:14]: 11 默認 22K 上拉
30 *bit [13]: 1 pull 功能
31 *bit [12]: 1 pull/keeper 使能
32 *bit [11]: 0 關閉開路輸出 33 *bit [7:6]: 10 速度 100Mhz 34 *bit [5:3]: 000 關閉輸出
35 *bit [0]: 0 低轉換率
36 */
37 IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
38
39 /* 3、初始化 GPIO GPIO1_IO18 設置為輸入*/
40 key_config.direction = kGPIO_DigitalInput;
41 gpio_init(GPIO1,18, &key_config);
42
43 }
44
45 /*
46 * @description : 獲取按鍵值
47 * @param : 無
48 * @return : 0 沒有按鍵按下,其他值:對應的按鍵值
49 */
50 int key_getvalue(void)
51 {
52 int ret = 0;
53 static unsigned char release = 1; /* 按鍵鬆開 */
54
55 if((release==1)&&(gpio_pinread(GPIO1, 18) == 0)) /* KEY0 按下 */
56 {
57 delay(10); /* 延時消抖 */
58 release = 0; /* 標記按鍵按下 */
59 if(gpio_pinread(GPIO1, 18) == 0)
60 ret = KEY0_VALUE; 61 }
62 else if(gpio_pinread(GPIO1, 18) == 1) /* KEY0 未按下 */
63 {
64 ret = 0;
65 release = 1; /* 標記按鍵釋放 */
66 }
67
68 return ret;
69 }
bsp_key.c 中一共有兩個函數:key_init 和 key_getvalue,key_init 是按鍵初始化函數,用來初始化按鍵所使用的 UART1_CTS 這個 IO 。函數 key_init 先設置 UART1_CTS 複用為GPIO1_IO18,然後配置 UART1_CTS 這個 IO 為速度為 100MHz,默認 22K 上拉。最後調用函數 gpio_init 來設置GPIO1_IO18 為輸入功能。
函數 key_getvalue 用於獲取按鍵值,此函數沒有參數,只有一個返回值,返回值表示按鍵值,返回值為 0 的話就表示沒有按鍵按下,如果返回其他值的話就表示對應的按鍵按下了。獲取按鍵值其實就是不斷的讀取 GPIO1_IO18 的值,如果按鍵按下的話相應的 IO 被拉低,那麼GPIO1_IO18 值就為 0,如果按鍵未按下的話GPIO1_IO18 的值就為 1。此函數中靜態局部變量release 表示按鍵是否釋放。
示例代碼中的 57 行是按鍵消抖延時函數,延時時間大約為 10ms,用於消除按鍵抖動。理想型的按鍵電壓變化過程如圖所示:
按鍵按下
在圖中,按鍵沒有按下的時候按鍵值為 1,當按鍵在 t1 時刻按鍵被按下以後按鍵值就變為 0,這是最理想的狀態。但是實際的按鍵是機械結構,加上剛按下去的一瞬間人手可能也有抖動,實際的按鍵電壓變化過程如圖所示:
按鍵按下
在圖中 t1 時刻按鍵被按下,但是由於抖動的原因,直到 t2 時刻才穩定下來,t1 到t2 這段時間就是抖動。一般這段時間就是十幾 ms 左右,從圖中可以看出在抖動期間會有多次觸發,如果不消除這段抖動的話軟件就會誤判,本來按鍵就按下了一次,結果軟件讀取IO 值發現電平多次跳變以為按下了多次。所以我們需要跳過這段抖動時間再去讀取按鍵的 IO值,也就是至少要在 t2 時刻以後再去讀IO 值。在示例代碼中的 57 行是延時了大約10ms 後再去讀取 GPIO1_IO18 的 IO 值,如果此時按鍵的值依舊是 0,那麼就表示這是一次有效的按鍵觸發。
按鍵驅動就講解到這裡,最後就是 main.c 文件的內容了,在 main.c 中輸入如下代碼:
1 #include "bsp_clk.h"
2 #include "bsp_delay.h"
3 #include "bsp_led.h"
4 #include "bsp_beep.h"
5 #include "bsp_key.h" 6
7 /*
8 * @description : main 函數
9 * @param : 無
10 * @return : 無
11 */
12 int main(void)
13 {
14 int i = 0;
15 int keyvalue = 0;
16 unsigned char led_state = OFF;
17 unsigned char beep_state = OFF;
18
19 clk_enable(); /* 使能所有的時鐘 */
20 led_init(); /* 初始化 led */
21 beep_init(); /* 初始化 beep */
22 key_init(); /* 初始化 key */
23
24 while(1)
25 {
26 keyvalue = key_getvalue();
27 if(keyvalue)
28 {
29 switch (keyvalue)
30 {
31 case KEY0_VALUE:
32 beep_state = !beep_state;
33 beep_switch(beep_state);
34 break;
35 }
36 }
37 i++;
38 if(i==50)
39 {
40 i = 0;
41 led_state = !led_state;
42 led_switch(LED0, led_state);
43 }
44 delay(10);
45 }
46 return 0;
47 }
main.c 函數先初始化 led 燈、蜂鳴器和按鍵,然後在while(1)循環中不斷的調用函數key_getvalue 來讀取按鍵值,如果 KEY0 按下的話就打開/關閉蜂鳴器。LED0 作為系統提示指示燈閃爍,閃爍週期大約為 500ms。例程的軟件編寫就到這裡結束了,接下來就是編譯下載驗證了。
Makefile 使用第十三章編寫的通用 Makefile,修改變量 TARGET 為 beep,在變量 INCDIRS和 SRCDIRS 中追加“bsp/beep”,修改完成以後如下所示:
1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= key
3
4 /* 省略掉其它代碼...... */
5
6 INCDIRS := imx6ul \\
7 bsp/clk \\
8 bsp/led \\
9 bsp/delay \\
10 bsp/beep \\
11 bsp/gpio \\
12 bsp/key
13
14 SRCDIRS := project \\
15 bsp/clk \\
16 bsp/led \\
17 bsp/delay \\
18 bsp/beep \\
19 bsp/gpio \\
20 bsp/key
21
22 /* 省略掉其它代碼...... */
23
24 clean:
25 rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
第 2 行修改變量 TARGET 為“key”,也就是目標名稱為“key”。
第 11、12 行在變量 INCDIRS 中添加 GPIO 和按鍵驅動頭文件(.h)路徑。第 19、20 行在變量SRCDIRS 中添加 GPIO 和按鍵驅動文件(.c)路徑。
鏈接腳本就使用上一篇文章《 》中的鏈接腳本文件 imx6ul.lds 即可。
閱讀更多 小平頭 的文章