IMX6UL裸機實現C語言按鍵輸入實驗

按鍵就兩個狀態:按下或彈起,將按鍵連接到一個 IO 上,通過讀取這個 IO 的值就知道按鍵是按下的還是彈起的。至於按鍵按下的時候是高電平還是低電平要根據實際電路來判斷。當 GPIO 連接按鍵的時候就要做為輸入使用。我們的主要工作就是配置按鍵所連接的 IO 為輸入功能,然後讀取這個 IO 的值來判斷按鍵是否按下。

I.MX6U-ALPHA 開發板上有一個按鍵KEY0,我們將會編寫代碼通過這個 KEY0 按鍵來控制開發板上的蜂鳴器,按一下KEY0 蜂鳴器打開,再按一下蜂鳴器就關閉。

本試驗我們用到的硬件有:

1) LED 燈 LED0。

2)蜂鳴器。

3)1 個按鍵KEY0。

按鍵KEY0 的原理圖如圖所示:


IMX6UL裸機實現C語言按鍵輸入實驗

按鍵原理圖

從圖中可以看出,按鍵 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,用於消除按鍵抖動。理想型的按鍵電壓變化過程如圖所示:

按鍵按下


IMX6UL裸機實現C語言按鍵輸入實驗

理想的按鍵電壓變化過程

在圖中,按鍵沒有按下的時候按鍵值為 1,當按鍵在 t1 時刻按鍵被按下以後按鍵值就變為 0,這是最理想的狀態。但是實際的按鍵是機械結構,加上剛按下去的一瞬間人手可能也有抖動,實際的按鍵電壓變化過程如圖所示:

按鍵按下


IMX6UL裸機實現C語言按鍵輸入實驗

實際的按鍵電壓變化過程

在圖中 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 即可。


分享到:


相關文章: