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

目录

前言

一、设备树配置

二、驱动编写

三、用户空间测试

总结


前言

开发平台:全志A133,开发环境:linux4.9+andrio10,开发板:HelperBoard A133_V2.5。

一、设备树配置

打开板级设备树配置文件,路径:

bash 复制代码
vim ~/share/linux_source/device/config/chips/a133/configs/c4/board.dts

添加新节点beep对应GPIO:GPIOB8。

cpp 复制代码
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源码并下载到开发板中

cpp 复制代码
~/share/linux_source/build.sh

二、驱动编写

采用模块驱动,未写入内核,创建驱动文件及Makefile。Makefile参考如下

cpp 复制代码
#内核架构
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

编写驱动代码,使用平台驱动结构体完成初始化配置

cs 复制代码
//设备树匹配表
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路径生成设备文件,方便用户空间进行访问。

cs 复制代码
//设备驱动初始化函数
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三个接口。

cs 复制代码
//打开设备
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
};

完整代码如下

cpp 复制代码
#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文件

bash 复制代码
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短鸣一次。

cpp 复制代码
#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文件。

bash 复制代码
armv7a-linux-androideabi21-clang beep_demo.c -o beep_demo.o

将驱动文件和测试文件都推送到开发板上测试

bash 复制代码
adb push .\beep\beep_demo.o /data/local/tmp
adb push .\beep_driver\beep_init.ko /data/local/tmp

加载驱动并查看内核输出

bash 复制代码
insmod /data/local/tmp/beep_init.ko
dmesg

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

最后修改beep_demo.o文件的权限,并运行。即可观察到蜂鸣器每隔5s短鸣一次

bash 复制代码
chmod 777 /data/local/tmp/beep_demo.o
/data/local/tmp/beep_demo.o

总结

单片机开发中,写一个GPIO拉高的函数,只需要一条语句即可完成,找到GPIO状态寄存器,然后将对应GPIO设为1即可。但是在linux驱动开发中就需要配置设备树,注册GPIO设备,注册字符设备生成设备文件,完成三个基本控制接口,完成驱动加载卸载函数,再编写一个用户空间的测试代码才能实现。工作量实在是太多了,好处是分工很明确。驱动代码编写交给驱动开发工程师,用户代码编写交给应用工程师,还可以再封装一层交给安卓开发工程师进行APP开发。

相关推荐
ECC&SM95 分钟前
Video_AVI_Packet(1)
笔记·音视频
壹Y.40 分钟前
MATLAB 绘图速查笔记
笔记·matlab
菜菜子爱学习3 小时前
Nginx学习笔记(八)—— Nginx缓存集成
笔记·学习·nginx·缓存·运维开发
岑梓铭5 小时前
考研408《计算机组成原理》复习笔记,第四章(1)——指令系统概念(指令字长、N地址指令、定长和变长操作码)
笔记·考研·408·计算机组成原理·计组
岑梓铭5 小时前
考研408《计算机组成原理》复习笔记,第四章(3)——指令集、汇编语言
笔记·考研·408·计算机组成原理·计组
yuxb735 小时前
Ansible 基础到实操笔记
linux·笔记·ansible
Qlittleboy7 小时前
tp5集成elasticsearch笔记
大数据·笔记·elasticsearch
rannn_1119 小时前
【Linux学习|黑马笔记|Day4】IP地址、主机名、网络请求、下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压
linux·笔记·后端·学习
麻雀无能为力9 小时前
python自学笔记8 二维和三维可视化
开发语言·笔记·python
Moonnnn.10 小时前
【51单片机学习】定时器、串口、LED点阵屏、DS1302实时时钟、蜂鸣器
笔记·单片机·学习·51单片机