上一章節linux設備驅動程序--建立設備節點章節主要介紹了linux字符設備驅動程序的框架,從這一章節開始咱們講解各類外設的控制,包括gpio,i2c,dma等等,既然是外設,那就涉及到具體的目標板,博主在這裏使用的開發板是開源平臺beagle bone green,內核版本爲4.14.html
今天咱們來說解gpio的設備驅動程序。node
爲了linux的可移植性和統一,linux提供一套函數庫供用戶使用,內容涵蓋了GPIO/I2C/SPI等外設的控制,關於函數庫能夠參考官方網站linux
這一章咱們須要用到gpio相關的庫函數:api
//檢查gpio number是否合法 int gpio_to_irq(unsigned gpio) //根據gpio number申請gpio資源,label爲gpio名稱 int gpio_request(unsigned gpio, const char *label) //釋放gpio 資源 void gpio_free(unsigned gpio) //設置gpio 爲輸入 int gpio_direction_input(unsigned gpio) //設置gpio 爲輸出 int gpio_direction_output(unsigned gpio, int value) //設置gpio的值 gpio_set_value(unsigned gpio, int value) //設置gpio的消抖時間,主要用於按鍵消抖 int gpio_set_debounce(unsigned gpio, unsigned debounce) //獲取gpio對應的中斷線路 int gpio_to_irq(unsigned gpio) //gpio中斷,當產生中斷時調用handle函數 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
在前面的章節咱們知道了怎麼寫一個字符設備驅動程序的框架,如今咱們就只須要往框架裏面添加相應的處理代碼就能夠了。框架
如今嘗試實現這樣的需求:函數
下面就是咱們實現的代碼,gpio_led_control.c:測試
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/string.h> MODULE_AUTHOR("Downey"); MODULE_LICENSE("GPL"); static int majorNumber = 0; /*Class 名稱,對應/sys/class/下的目錄名稱*/ static const char *CLASS_NAME = "led_control_class"; /*Device 名稱,對應/dev下的目錄名稱*/ static const char *DEVICE_NAME = "led_control_demo"; static int led_control_open(struct inode *node, struct file *file); static ssize_t led_control_read(struct file *file,char *buf, size_t len,loff_t *offset); static ssize_t led_control_write(struct file *file,const char *buf,size_t len,loff_t* offset); static int led_control_release(struct inode *node,struct file *file); #define LED_PIN 26 static int gpio_status; static char recv_msg[20]; static struct class *led_control_class = NULL; static struct device *led_control_device = NULL; /*File opertion 結構體,咱們經過這個結構體創建應用程序到內核之間操做的映射*/ static struct file_operations file_oprts = { .open = led_control_open, .read = led_control_read, .write = led_control_write, .release = led_control_release, }; static void gpio_config(void) { if(!gpio_is_valid(LED_PIN)){ printk(KERN_ALERT "Error wrong gpio number\n"); return ; } gpio_request(LED_PIN,"led_ctr"); gpio_direction_output(LED_PIN,1); gpio_set_value(LED_PIN,1); gpio_status = 1; } static void gpio_deconfig(void) { gpio_free(LED_PIN); } static int __init led_control_init(void) { printk(KERN_ALERT "Driver init\r\n"); /*註冊一個新的字符設備,返回主設備號*/ majorNumber = register_chrdev(0,DEVICE_NAME,&file_oprts); if(majorNumber < 0 ){ printk(KERN_ALERT "Register failed!!\r\n"); return majorNumber; } printk(KERN_ALERT "Registe success,major number is %d\r\n",majorNumber); /*以CLASS_NAME建立一個class結構,這個動做將會在/sys/class目錄建立一個名爲CLASS_NAME的目錄*/ led_control_class = class_create(THIS_MODULE,CLASS_NAME); if(IS_ERR(led_control_class)) { unregister_chrdev(majorNumber,DEVICE_NAME); return PTR_ERR(led_control_class); } /*以DEVICE_NAME爲名,參考/sys/class/CLASS_NAME在/dev目錄下建立一個設備:/dev/DEVICE_NAME*/ led_control_device = device_create(led_control_class,NULL,MKDEV(majorNumber,0),NULL,DEVICE_NAME); if(IS_ERR(led_control_device)) { class_destroy(led_control_class); unregister_chrdev(majorNumber,DEVICE_NAME); return PTR_ERR(led_control_device); } printk(KERN_ALERT "led_control device init success!!\r\n"); return 0; } /*當用戶打開這個設備文件時,調用這個函數*/ static int led_control_open(struct inode *node, struct file *file) { printk(KERN_ALERT "GPIO init \n"); gpio_config(); return 0; } /*當用戶試圖從設備空間讀取數據時,調用這個函數*/ static ssize_t led_control_read(struct file *file,char *buf, size_t len,loff_t *offset) { int cnt = 0; /*將內核空間的數據copy到用戶空間*/ cnt = copy_to_user(buf,&gpio_status,1); if(0 == cnt){ return 0; } else{ printk(KERN_ALERT "ERROR occur when reading!!\n"); return -EFAULT; } return 1; } /*當用戶往設備文件寫數據時,調用這個函數*/ static ssize_t led_control_write(struct file *file,const char *buf,size_t len,loff_t *offset) { /*將用戶空間的數據copy到內核空間*/ int cnt = copy_from_user(recv_msg,buf,len); if(0 == cnt){ if(0 == memcmp(recv_msg,"on",2)) { printk(KERN_INFO "LED ON!\n"); gpio_set_value(LED_PIN,1); gpio_status = 1; } else { printk(KERN_INFO "LED OFF!\n"); gpio_set_value(LED_PIN,0); gpio_status = 0; } } else{ printk(KERN_ALERT "ERROR occur when writing!!\n"); return -EFAULT; } return len; } /*當用戶打開設備文件時,調用這個函數*/ static int led_control_release(struct inode *node,struct file *file) { printk(KERN_INFO "Release!!\n"); gpio_deconfig(); return 0; } /*銷燬註冊的全部資源,卸載模塊,這是保持linux內核穩定的重要一步*/ static void __exit led_control_exit(void) { device_destroy(led_control_class,MKDEV(majorNumber,0)); class_unregister(led_control_class); class_destroy(led_control_class); unregister_chrdev(majorNumber,DEVICE_NAME); } module_init(led_control_init); module_exit(led_control_exit);
在init函數中對gpio進行相應的初始化,當用戶在文件進行寫操做時,根據傳入的參數來判斷打開或者關閉燈,在用戶關閉文件時釋放資源。網站
爲此,須要添加一個用戶程序來對設備文件進行讀寫,gpio_led_contro_user.c:調試
#include <stdio.h> #include <stdlib.h> #include <error.h> #include <fcntl.h> #include <string.h> #include <unistd.h> static char buf[256] = {1}; int main(int argc,char *argv[]) { int fd = open("/dev/led_control_demo",O_RDWR); if(fd < 0) { perror("Open file failed!!!\r\n"); return -1; } while(1){ printf("Please input <on> or <off>:\n"); scanf("%s",buf); if(strlen(buf) > 3){ printf("Ivalid input!\n"); } else { int ret = write(fd,buf,strlen(buf)); if(ret < 0){ perror("Failed to write!!"); } } } close(fd); return 0; }
當用戶程序執行時,用戶程序一直獲取用戶的輸入,根據用戶輸入"on"或者"off",而後將其寫入設備文件,觸發系統調用,設備文件根據設備號找到內核中相應的file_operation結構體,相應write函數被調用,執行相應的點燈操做。code
在例程中咱們使用了gpio26做爲led引腳,因此咱們須要鏈接一個led(視狀況加一個電阻)到gpio26引腳上,具體引腳位置須要自行查看開發板手冊。
首先咱們能夠打開兩個終端窗口,一個爲查看log信息,一個用來進行相關指令操做。
在查看log信息終端,咱們須要循環查看/var/log/kern.log文件:
tail -f /var/log/kern.log
這樣內核printk()輸出的信息就能夠在這裏看到了,方便進行調試。
而後須要編譯內核驅動文件gpio_led_control.c,先修改Makefile(這裏就再也不展開,能夠參考前面章節)。
而後編譯:
make
編譯成功,在本目錄下生成相應的.ko文件,加載內核文件:
sudo insmod gpio_led_control.ko
查看log終端會顯示相應的信息。
再編譯用戶文件:
gcc gpio_led_contro_user.c -o user
執行用戶文件:
sudo ./user
窗口輸出:
Please input <on> or <off>:
這是咱們輸入on或者off就能夠控制led的亮滅了(其實就是控制gpio的高低電平)。
對於gpio而言,linux驅動庫實現了將gpio引腳信息映射到/sys目錄下,用戶能夠很方便地直接經過相關文件的操做來讀寫gpio的值,達到控制gpio的目的,這個接口API原型爲:
//第一個參數表示導出的引腳,第二個參數表示是否可改變IO的輸出方向。 int gpio_export(unsigned gpio, bool direction_may_change)
固然,相對應的釋放函數爲
void gpio_unexport(unsigned gpio)
在gpio初始化的函數中添加這個接口,在加載完成以後查看/sys/class/gpio/目錄下是否有相應的gpio$num(這裏是gpio26)文件(須要注意的是,在上例中,當用戶程序打開設備時才進行gpio的初始化,關閉文件時釋放gpio的資源,因此須要打開文件再操做)。
若是有相應的文件,試試下面的指令:
echo 0 > /sys/class/gpio/gpio26/value echo 1 > /sys/class/gpio/gpio26/value
若是你有興趣也能夠研究研究裏面其餘的文件,這裏不過多描述,留做家庭做業。
上文實現了經過操做設備文件來控制開發板的gpio,接下來咱們看看gpio中斷的實現,一個按鍵點燈程序,當加載模塊後,按鍵反轉燈的狀態:
接下來就是按鍵中斷的示例代碼 gpio_key_led_control.c:
#include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/interrupt.h> #define BUTTON 27 #define LED 26 MODULE_LICENSE("GPL"); MODULE_AUTHOR("Downey"); MODULE_DESCRIPTION("Gpio irq test!!\n"); int button_irq_num = 0; bool led_status = 1; static irqreturn_t button_irq_handle(int irq, void *dev_id) { printk(KERN_INFO "Enter irq!!\n"); if(0 == led_status) { gpio_set_value(LED,1); led_status = 1; } else { led_status = 0; gpio_set_value(LED,0); } return (irqreturn_t)IRQ_HANDLED; } static int gpio_config(void) { int ret = 0; if(!gpio_is_valid(BUTTON) || !gpio_is_valid(LED)) { printk(KERN_ALERT "Gpio is invalid!\n"); return -ENODEV; } gpio_request(BUTTON,"button"); gpio_direction_input(BUTTON); gpio_set_debounce(BUTTON,20); button_irq_num = gpio_to_irq(BUTTON); printk(KERN_INFO "NUM = %d",button_irq_num); ret = request_irq(button_irq_num, (irq_handler_t)button_irq_handle, IRQF_TRIGGER_RISING, "BUTTON1", NULL); printk(KERN_INFO "GPIO_TEST: The interrupt request result is: %d\n", ret); gpio_request(LED,"LED"); gpio_direction_output(LED,1); gpio_set_value(LED,1); return 0; } static void gpio_deconfig(void) { gpio_direction_output(LED,0); free_irq(button_irq_num,NULL); gpio_free(BUTTON); gpio_free(LED); } int __init gpio_irq_init(void) { gpio_config(); printk(KERN_INFO "gpio_irq_init!\n"); return 0; } void __exit gpio_irq_exit(void) { gpio_deconfig(); printk(KERN_INFO "gpio_irq_exit!\n"); } module_init(gpio_irq_init); module_exit(gpio_irq_exit);
首先爲了試驗,咱們須要將按鍵鏈接在gpio27上,將led鏈接在gpio26上。(視狀況添加電阻)
修改Makefile,而後編譯:
make
加載:
sudo insmod gpio_key_led_control.ko
這部分例程並無註冊文件接口,而是直接在內核中經過硬件中斷檢測是否有按鍵時間產生,來執行點亮和熄滅指示燈的操做。
如今就能夠測試按鍵是否有效了,若是出現什麼問題,可能須要調試代碼,別忘了根據printk()輸出的log信息來判斷錯誤。
好了,關於linux驅動程序-gpio控制就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言
原創博客,轉載請註明出處!
祝各位早日實現項目叢中過,bug不沾身.