> 技术文档 > linux驱动开发笔记--GPIO驱动开发

linux驱动开发笔记--GPIO驱动开发

目录

前言

一、设备树配置

二、驱动编写

三、用户空间测试

总结


前言

        开发平台:全志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:  (function=output, pull=none, drive=3, data=0) */gpios = ;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 : allall:make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules.PHONY : cleanclean: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; } //请求GPIO ret = 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 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_user switch(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_COMPATstatic 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 #include #include #include #include #include #include #include #include #include #include #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_user switch(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_COMPATstatic 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; } //请求GPIO ret = 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 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$ makemake -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 modulesmake[1]: Entering directory \'/home/murongbai/share/linux_source/kernel/linux-4.9\' CC [M] /home/murongbai/share/my_drivers/beep_driver/beep_init.o Building modules, stage 2. MODPOST 1 modules CC /home/murongbai/share/my_drivers/beep_driver/beep_init.mod.o LD [M] /home/murongbai/share/my_drivers/beep_driver/beep_init.komake[1]: Leaving directory \'/home/murongbai/share/linux_source/kernel/linux-4.9\'#### build completed successfully (1 seconds) ####

三、用户空间测试

        测试开启蜂鸣器设备文件,并控制蜂鸣器每隔5s短鸣一次。

#include #include #include #include #include #include #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/tmpadb push .\\beep_driver\\beep_init.ko /data/local/tmp

        加载驱动并查看内核输出

insmod /data/local/tmp/beep_init.kodmesg

        看到此输出语句表示驱动加载成功

        最后修改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开发。