gpio驅動程序
上一章節linux設備驅動程序--創建設備節點章節主要介紹了linux字符設備驅動程序的框架,從這一章節開始我們講解各種外設的控制,包括gpio,i2c,dma等等,既然是外設,那就涉及到具體的目標板,博主在這里使用的開發板是開源平台beagle bone green,內核版本為4.14.
今天我們來講解gpio的設備驅動程序。
gpio相關的庫函數
為了linux的可移植性和統一,linux提供一套函數庫供用戶使用,內容涵蓋了GPIO/I2C/SPI等外設的控制,關於函數庫可以參考官方網站
這一章我們需要用到gpio相關的庫函數:
//檢查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)
linux gpio設備驅動程序
在前面的章節我們知道了怎么寫一個字符設備驅動程序的框架,現在我們就只需要往框架里面添加相應的處理代碼就可以了。
現在嘗試實現這樣的需求:
- 在beagle bone green開發板上的gpio上連接一個指示燈
- 當用戶打開/dev目錄下的設備文件時,完成對gpio的初始化
- 往文件中寫入OPEN實現打開燈,往文件中寫入CLOSE關閉燈
- 關閉設備文件時,釋放gpio資源
下面就是我們實現的代碼,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函數被調用,執行相應的點燈操作。
編譯加載運行
連接led
在例程中我們使用了gpio26作為led引腳,所以我們需要連接一個led(視情況加一個電阻)到gpio26引腳上,具體引腳位置需要自行查看開發板手冊。
查看log
首先我們可以打開兩個終端窗口,一個為查看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映射到/sys目錄下
對於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中斷的實現,一個按鍵點燈程序,當加載模塊后,按鍵反轉燈的狀態:
- 添加gpio按鍵中斷代碼。
- 不再需要創建設備文件節點,直接通過按鍵來操作led。
接下來就是按鍵中斷的示例代碼 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);
編譯加載執行
連接led和按鍵
首先為了試驗,我們需要將按鍵連接在gpio27上,將led連接在gpio26上。(視情況添加電阻)
編譯加載
修改Makefile,然后編譯:
make
加載:
sudo insmod gpio_key_led_control.ko
測試
這部分例程並沒有注冊文件接口,而是直接在內核中通過硬件中斷檢測是否有按鍵時間產生,來執行點亮和熄滅指示燈的操作。
現在就可以測試按鍵是否有效了,如果出現什么問題,可能需要調試代碼,別忘了根據printk()輸出的log信息來判斷錯誤。
好了,關於linux驅動程序-gpio控制就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言
原創博客,轉載請注明出處!
祝各位早日實現項目叢中過,bug不沾身.