目录
前言
开发平台:全志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;
}
//请求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 < 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_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_COMPAT
static long beep_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return beep_ioctl(file, cmd, arg);
}
#endif
static 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_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_COMPAT
static long beep_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return beep_ioctl(file, cmd, arg);
}
#endif
static 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 < 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.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.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开发。