目錄
前言
一、設備樹配置
二、驅動編寫
三、用戶空間測試
總結
前言
? ? ? ? 開發平臺:全志A133,開發環境:linux4.9+andrio10,開發板:HelperBoard A133_V2.5。
一、設備樹配置
? ? ? ? 打開板級設備樹配置文件,路徑:
vim ~/share/linux_source/device/config/chips/a133/configs/c4/board.dts
? ? ? ? 添加新節點beep對應GPIO:GPIOB8。
beep: beep@0 {compatible = "murongbai,beep";status = "okay";/* PB8: <&pio PB 8 1 0 3 0> (function=output, pull=none, drive=3, data=0) */gpios = <&pio PB 8 1 0 3 0>;label = "beep_gpio";
};
? ? ? ? 重新編譯linux源碼并下載到開發板中
~/share/linux_source/build.sh
二、驅動編寫
? ? ? ? 采用模塊驅動,未寫入內核,創建驅動文件及Makefile。Makefile參考如下
#內核架構
ARCH=arm64
#當前工作路徑
PWD = $(shell pwd)
#內核路徑
KDIR := /home/murongbai/share/linux_source/kernel/linux-4.9
#編譯器路徑
CROSS_COMPILE= /home/murongbai/share/linux_source/out/gcc-linaro-5.3.1-2016.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
#模塊名稱
obj-m += beep_init.o.PHONY : all
all:make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
.PHONY : clean
clean:make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
????????編寫驅動代碼,使用平臺驅動結構體完成初始化配置
//設備樹匹配表
static const struct of_device_id beep_of_match[] = {{ .compatible = "murongbai,beep", },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, beep_of_match);//平臺驅動結構體(驅動加載、驅動卸載、設備匹配等)
static struct platform_driver beep_platform_driver = {.probe = beep_probe,.remove = beep_remove,.driver = {.name = BEEP_NAME,.of_match_table = beep_of_match,},
};
????????根據上方設備樹節點中定義的gpios屬性獲取實際的GPIO編號,再根據GPIO編號注冊GPIO設備,并將設備配置為輸出模式。然后將設備注冊為字符設備并在/dev/beep路徑生成設備文件,方便用戶空間進行訪問。
//設備驅動初始化函數
static int beep_probe(struct platform_device *pdev)
{int ret;//獲取設備樹中定義的GPIO編號beep_gpio = of_get_named_gpio(pdev->dev.of_node, "gpios", 0);if (!gpio_is_valid(beep_gpio)) {dev_err(&pdev->dev, "Invalid beep gpio\n");return -EINVAL;}//請求GPIOret = gpio_request(beep_gpio, "beep_gpio");if (ret) {dev_err(&pdev->dev, "Failed to request GPIO %d\n", beep_gpio);return ret;}//設置GPIO為輸出模式gpio_direction_output(beep_gpio, 0);//注冊字符設備beep_major = register_chrdev(0, BEEP_NAME, &beep_fops);if (beep_major < 0) {dev_err(&pdev->dev, "Failed to register chrdev\n");gpio_free(beep_gpio);return beep_major;}//創建設備類beep_class = class_create(THIS_MODULE, BEEP_NAME);if (IS_ERR(beep_class)) {unregister_chrdev(beep_major, BEEP_NAME);gpio_free(beep_gpio);return PTR_ERR(beep_class);}//創建設備節點beep_dev = device_create(beep_class, NULL, MKDEV(beep_major, 0), NULL, BEEP_NAME);//檢查設備創建是否成功dev_info(&pdev->dev, "Beep driver init, gpio=%d\n", beep_gpio);return 0;
}//設備驅動卸載函數
static int beep_remove(struct platform_device *pdev)
{device_destroy(beep_class, MKDEV(beep_major, 0));class_destroy(beep_class);unregister_chrdev(beep_major, BEEP_NAME);if (gpio_is_valid(beep_gpio)) {gpio_set_value(beep_gpio, 0);gpio_free(beep_gpio);}pr_info("Beep driver exit\n");return 0;
}
????????實現字符設備的open,close,ioctl三個接口。
//打開設備
static int beep_open(struct inode *inode, struct file *file)
{return 0;
}//關閉設備
static int beep_release(struct inode *inode, struct file *file)
{return 0;
}//ioctl控制蜂鳴器開關
static long beep_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{int beep_value;//用戶空間傳遞的是指針,需使用copy_from_userswitch(cmd){case BEEP_IOWRITE:if(copy_from_user(&beep_value, (int __user *)arg, sizeof(int)))return -EFAULT;gpio_set_value(beep_gpio, beep_value ? 1 : 0);break;default:return -EINVAL;}return 0;
}//兼容32位用戶空間
#ifdef CONFIG_COMPAT
static long beep_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{return beep_ioctl(file, cmd, arg);
}
#endifstatic struct file_operations beep_fops = {.owner = THIS_MODULE,.open = beep_open,.release = beep_release,.unlocked_ioctl = beep_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl = beep_compat_ioctl,
#endif
};
? ? ? ? 完整代碼如下
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>#define BEEP_NAME "beep"#define DEVICE_TYPE 'B'
#define BEEP_IOWRITE _IOW(DEVICE_TYPE, 0, int)//主設備號
static int beep_major = 0;
//設備類
static struct class *beep_class = NULL;
//蜂鳴器GPIO編號
static int beep_gpio = -1;
//設備結構體
static struct device *beep_dev = NULL;//打開設備
static int beep_open(struct inode *inode, struct file *file)
{return 0;
}//關閉設備
static int beep_release(struct inode *inode, struct file *file)
{return 0;
}//ioctl控制蜂鳴器開關
static long beep_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{int beep_value;//用戶空間傳遞的是指針,需使用copy_from_userswitch(cmd){case BEEP_IOWRITE:if(copy_from_user(&beep_value, (int __user *)arg, sizeof(int)))return -EFAULT;gpio_set_value(beep_gpio, beep_value ? 1 : 0);break;default:return -EINVAL;}return 0;
}//兼容32位用戶空間
#ifdef CONFIG_COMPAT
static long beep_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{return beep_ioctl(file, cmd, arg);
}
#endifstatic struct file_operations beep_fops = {.owner = THIS_MODULE,.open = beep_open,.release = beep_release,.unlocked_ioctl = beep_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl = beep_compat_ioctl,
#endif
};//設備驅動初始化函數
static int beep_probe(struct platform_device *pdev)
{int ret;//獲取設備樹中定義的GPIO編號beep_gpio = of_get_named_gpio(pdev->dev.of_node, "gpios", 0);if (!gpio_is_valid(beep_gpio)) {dev_err(&pdev->dev, "Invalid beep gpio\n");return -EINVAL;}//請求GPIOret = gpio_request(beep_gpio, "beep_gpio");if (ret) {dev_err(&pdev->dev, "Failed to request GPIO %d\n", beep_gpio);return ret;}//設置GPIO為輸出模式gpio_direction_output(beep_gpio, 0);//注冊字符設備beep_major = register_chrdev(0, BEEP_NAME, &beep_fops);if (beep_major < 0) {dev_err(&pdev->dev, "Failed to register chrdev\n");gpio_free(beep_gpio);return beep_major;}//創建設備類beep_class = class_create(THIS_MODULE, BEEP_NAME);if (IS_ERR(beep_class)) {unregister_chrdev(beep_major, BEEP_NAME);gpio_free(beep_gpio);return PTR_ERR(beep_class);}//創建設備節點beep_dev = device_create(beep_class, NULL, MKDEV(beep_major, 0), NULL, BEEP_NAME);//檢查設備創建是否成功dev_info(&pdev->dev, "Beep driver init, gpio=%d\n", beep_gpio);return 0;
}//設備驅動卸載函數
static int beep_remove(struct platform_device *pdev)
{device_destroy(beep_class, MKDEV(beep_major, 0));class_destroy(beep_class);unregister_chrdev(beep_major, BEEP_NAME);if (gpio_is_valid(beep_gpio)) {gpio_set_value(beep_gpio, 0);gpio_free(beep_gpio);}pr_info("Beep driver exit\n");return 0;
}//設備樹匹配表
static const struct of_device_id beep_of_match[] = {{ .compatible = "murongbai,beep", },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, beep_of_match);//平臺驅動結構體(驅動加載、驅動卸載、設備匹配等)
static struct platform_driver beep_platform_driver = {.probe = beep_probe,.remove = beep_remove,.driver = {.name = BEEP_NAME,.of_match_table = beep_of_match,},
};static int __init beep_init(void)
{return platform_driver_register(&beep_platform_driver);
}static void __exit beep_exit(void)
{platform_driver_unregister(&beep_platform_driver);
}module_init(beep_init);
module_exit(beep_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("murongbai");
MODULE_DESCRIPTION("Simple Beep Driver for GPIOB8 (DT version)");
? ? ? ? 使用make命令編譯生成.ko文件
murongbai@murongbai-B760I-Snow-Dream:~/share/my_drivers/beep_driver$ make
make -C /home/murongbai/share/linux_source/kernel/linux-4.9 ARCH=arm64 CROSS_COMPILE=/home/murongbai/share/linux_source/out/gcc-linaro-5.3.1-2016.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu- M=/home/murongbai/share/my_drivers/beep_driver modules
make[1]: Entering directory '/home/murongbai/share/linux_source/kernel/linux-4.9'CC [M] /home/murongbai/share/my_drivers/beep_driver/beep_init.oBuilding modules, stage 2.MODPOST 1 modulesCC /home/murongbai/share/my_drivers/beep_driver/beep_init.mod.oLD [M] /home/murongbai/share/my_drivers/beep_driver/beep_init.ko
make[1]: Leaving directory '/home/murongbai/share/linux_source/kernel/linux-4.9'#### build completed successfully (1 seconds) ####
三、用戶空間測試
? ? ? ? 測試開啟蜂鳴器設備文件,并控制蜂鳴器每隔5s短鳴一次。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>#define DEVICE_TYPE 'B'
#define BEEP_IOWRITE _IOW(DEVICE_TYPE, 0, int)volatile int beep_on_time = 0;
volatile int running = 1;
int fd = -1;void beep_on(void) {beep_on_time = 100; // 設置蜂鳴器響100ms
}//Ctrl+C信號處理
void handle_sigint(int sig) {running = 0;
}int main() {int beep_value;signal(SIGINT, handle_sigint);fd = open("/dev/beep", O_RDWR);if (fd < 0) {perror("Failed to open device");return -1;}//無限循環,直到接收到退出信號while(running){// //如果蜂鳴器在運行時間,且蜂鳴器沒有打開// if(beep_on_time && beep_value == 0)// {// beep_value = 1;// ioctl(fd, BEEP_IOWRITE, &beep_value);// beep_on_time -= 10;// }// //如果蜂鳴器不在運行時間,但蜂鳴器打開// else if(beep_value == 1)// {// beep_value = 0;// ioctl(fd, BEEP_IOWRITE, &beep_value);// }// usleep(10*1000);beep_value = 1;ioctl(fd, BEEP_IOWRITE, &beep_value);usleep(100*1000);beep_value = 0;ioctl(fd, BEEP_IOWRITE, &beep_value);sleep(5);}close(fd);printf("Exit and release beep device\n");return 0;
}
? ? ? ? 通過這條命令編譯生成beep_demo.o文件。
armv7a-linux-androideabi21-clang beep_demo.c -o beep_demo.o
? ? ? ? 將驅動文件和測試文件都推送到開發板上測試
adb push .\beep\beep_demo.o /data/local/tmp
adb push .\beep_driver\beep_init.ko /data/local/tmp
? ? ? ? 加載驅動并查看內核輸出
insmod /data/local/tmp/beep_init.ko
dmesg
? ? ? ? 看到此輸出語句表示驅動加載成功
? ? ? ? 最后修改beep_demo.o文件的權限,并運行。即可觀察到蜂鳴器每隔5s短鳴一次
chmod 777 /data/local/tmp/beep_demo.o
/data/local/tmp/beep_demo.o
總結
? ? ? ? 單片機開發中,寫一個GPIO拉高的函數,只需要一條語句即可完成,找到GPIO狀態寄存器,然后將對應GPIO設為1即可。但是在linux驅動開發中就需要配置設備樹,注冊GPIO設備,注冊字符設備生成設備文件,完成三個基本控制接口,完成驅動加載卸載函數,再編寫一個用戶空間的測試代碼才能實現。工作量實在是太多了,好處是分工很明確。驅動代碼編寫交給驅動開發工程師,用戶代碼編寫交給應用工程師,還可以再封裝一層交給安卓開發工程師進行APP開發。