目录
前言
一、设备树配置
二、驱动编写
三、用户空间测试
总结
前言
开发平台:全志A133,开发环境:linux4.9+andrio10,开发板:HelperBoard A133_V2.5。
一、设备树配置
打开板级设备树配置文件,路径:
vim ~/share/linux_source/device/config/chips/a133/configs/c4/board.dts
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开发。