文章目錄
一、設備節點添加
二、創建驅動文件代碼
2.1 核心數據結構
2.2?按鍵值定義
2.3?關鍵函數實現
三、創建測試文件
四、測試
一、設備節點添加
????????首先在設備樹文件中添加pinctrl以及在根目錄下添加設備節點。如下:
//創建按鍵輸入的pinctrlpinctrl_key: keygrp {fsl,pins = <MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */>;};
//創建按鍵節點key {#address-cells = <1>;#size-cells = <1>;compatible = "atkalpha-key";pinctrl-names = "default";pinctrl-0 = <&pinctrl_key>;key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */status = "okay";};
};
二、創建驅動文件代碼
2.1 核心數據結構
定義結構體,其中包含按鍵驅動所需的信息,使用atomic_t類型保證按鍵值的原子操作。
struct key_dev {dev_t devid; /* 設備號 */struct cdev cdev; /* cdev結構體 */struct class *class; /* 類 */struct device *device; /* 設備 */int major; /* 主設備號 */int minor; /* 次設備號 */struct device_node *nd; /* 設備樹節點 */int key_gpio; /* 按鍵GPIO編號 */atomic_t keyvalue; /* 按鍵值 */
};
2.2?按鍵值定義
驅動中定義了兩個按鍵狀態:按下(1)和未按下/無效(0)。
#define KEY0VALUE 1 /* 按鍵值 */
#define INVAKEY 0 /* 無效的按鍵值 */
2.3?關鍵函數實現
首先是GPIO初始化:從設備樹獲取按鍵GPIO信息,并配置為輸入
static int keyio_init(void)
{keydev.nd = of_find_node_by_path("/key");if (keydev.nd == NULL) {return -EINVAL;}keydev.key_gpio = of_get_named_gpio(keydev.nd, "key-gpio", 0);if (keydev.key_gpio < 0) {printk("can't get key0\r\n");return -EINVAL;}printk("key_gpio=%d\r\n", keydev.key_gpio);/* 初始化key所使用的IO */gpio_request(keydev.key_gpio, "key0"); /* 請求IO */gpio_direction_input(keydev.key_gpio); /* 設置為輸入 */return 0;
}
?按鍵讀取:驅動會阻塞等待按鍵釋放后才返回,進而實現了一次完整按鍵周期的檢測。
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int ret = 0;int value;struct key_dev *dev = filp->private_data;if (gpio_get_value(dev->key_gpio) == 0) { /* key0按下 */while(!gpio_get_value(dev->key_gpio)); /* 等待按鍵釋放 */atomic_set(&dev->keyvalue, KEY0VALUE); } else { atomic_set(&dev->keyvalue, INVAKEY); /* 無效的按鍵值 */}value = atomic_read(&dev->keyvalue);ret = copy_to_user(buf, &value, sizeof(value));return ret;
}
三、創建測試文件
? ? ? ? 在測試文件中,通過對字符設備文件(/dev/key)進行標準文件操作實現與內核驅動層的交互。程序結構包括四個關鍵函數:信號處理函數sig_handler()、資源清理函數cleanup_resources()、幫助顯示函數show_usage()及主函數main()。在主函數中,程序首先檢查命令行參數格式,注冊SIGINT信號處理確保可通過Ctrl+C優雅退出,然后打開設備文件獲取文件描述符fd,隨后進入核心監測循環,通過read()系統調用讀取按鍵狀態并使用前后狀態比較算法(prev_keyvalue與keyvalue對比)檢測按鍵事件邊緣變化,實時輸出中文提示信息反饋按鍵狀態。
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "signal.h"/* 定義按鍵值 */
#define KEY0VALUE 1
#define INVAKEY 0/* 全局變量 */
static int fd = -1; /* 文件描述符 */
static int running = 1; /* 程序運行標志 *//** @description : 信號處理函數* @param - signum : 信號編號* @return : 無*/
void sig_handler(int signum)
{if (signum == SIGINT) {printf("\n程序接收到中斷信號,正在退出...\n");running = 0;}
}/** @description : 釋放資源* @param - filename: 設備文件名* @return : 無*/
void cleanup_resources(const char *filename)
{if (fd >= 0) {if (close(fd) < 0) {printf("文件 %s 關閉失敗!\n", filename);} else {printf("已關閉設備文件 %s\n", filename);}}
}/** @description : 顯示使用幫助* @param - name : 程序名* @return : 無*/
void show_usage(const char *name)
{printf("使用方法: %s <設備文件>\n", name);printf("示例: %s /dev/key\n", name);
}/** @description : main主程序* @param - argc : argv數組元素個數* @param - argv : 具體參數* @return : 0 成功;其他 失敗*/
int main(int argc, char *argv[])
{char *filename;int keyvalue;int prev_keyvalue = INVAKEY;/* 參數檢查 */if (argc != 2) {printf("參數錯誤!\n");show_usage(argv[0]);return -1;}filename = argv[1];/* 注冊信號處理函數,捕獲Ctrl+C */signal(SIGINT, sig_handler);/* 打開按鍵設備 */fd = open(filename, O_RDWR);if (fd < 0) {printf("無法打開設備文件 %s!\n", filename);return -1;}printf("按鍵測試程序已啟動\n");printf("按下按鍵進行測試,按 Ctrl+C 退出程序\n");/* 循環讀取按鍵值數據 */while (running) {if (read(fd, &keyvalue, sizeof(keyvalue)) < 0) {printf("讀取按鍵數據失敗\n");break;}/* 按鍵狀態變化檢測 */if (keyvalue == KEY0VALUE && prev_keyvalue != KEY0VALUE) {printf("按鍵被按下,鍵值 = %d\n", keyvalue);} else if (keyvalue == INVAKEY && prev_keyvalue == KEY0VALUE) {printf("按鍵已釋放\n");}prev_keyvalue = keyvalue;usleep(50000); /* 短暫延時,降低CPU占用 */}/* 清理資源 */cleanup_resources(filename);printf("程序已退出\n");return 0;
}