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

发布于:2025-07-24 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

前言

一、设备树配置

二、驱动编写

三、用户空间测试

总结


前言

        开发平台:全志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;
    }

    //请求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开发。


网站公告

今日签到

点亮在社区的每一天
去签到