Linux驱动开发(7):使用设备树实现RGB 灯驱动

通过上一小节的学习,我们已经能够编写简单的设备树节点,并且使用常用的of函数从设备树中获取我们想要的节点资源。 这一小节我们带领大家使用设备树编写一个简单的RGB灯驱动程序,加深对设备树的理解。

1. 实验说明

本节实验使用到 EBF6ULL-PRO 开发板上的 RGB 彩灯

1.1. 硬件原理图分析

参考”字符设备驱动–点亮LED灯”章节

2. 实验代码讲解

本章的示例代码目录为:linux_driver/device_tree_rgb_led

2.1. 编程思路

程序编写的主要内容为添加RGB灯的设备树节点、在驱动程序中使用of函数获取设备节点中的属性,编写测试应用程序。

  • 首先向设备树添加RGB设备节点

  • 其次编写平台设备驱动框架,主要包驱动入口函数、驱动注销函数、平台设备结构体定义三部分内容。

  • 实现.probe函数,对rgb进行设备注册和初始化。

  • 实现字符设备操作函数集,这里主要实现.write操作。

  • 编写测试应用程序,对于输入不同的值控制rgb颜色。

2.2. 代码分析

2.2.1. 添加RGB设备节点

RGB灯实际使用的是一个IO口,控制它所需要的资源几个控制寄存器,所以它的设备树节点也非常简单如下所示。

添加RGB设备节点:

/*
*CCM_CCGR1                         0x020C406C
*IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04  0x020E006C
*IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04  0x020E02F8
*GPIO1_GD                          0x0209C000
*GPIO1_GDIR                        0x0209C004
*/

/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC   0x020E01E0
*IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC   0x020E046C
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/

/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC   0x020E01DC
*IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC   0x020E0468
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/

/*添加led节点*/
    rgb_led{
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "fire,rgb_led";

    /*红灯节点*/
    ranges;
    rgb_led_red@0x020C406C{
            compatible = "fire,rgb_led_red";
            reg = <0x020C406C 0x00000004
                            0x020E006C 0x00000004
                            0x020E02F8 0x00000004
                            0x0209C000 0x00000004
                            0x0209C004 0x00000004>;
            status = "okay";
    };

    /*绿灯节点*/
    rgb_led_green@0x020C4074{
            compatible = "fire,rgb_led_green";
            reg = <0x020C4074 0x00000004
                            0x020E01E0 0x00000004
                            0x020E046C 0x00000004
                            0x020A8000 0x00000004
                            0x020A8004 0x00000004>;
            status = "okay";
    };

    /*蓝灯节点*/
    rgb_led_blue@0x020C4074{
            compatible = "fire,rgb_led_blue";
            reg = <0x020C4074 0x00000004
                            0x020E01DC 0x00000004
                            0x020E0468 0x00000004
                            0x020A8000 0x00000004
                            0x020A8004 0x00000004>;
            status = "okay";
    };

RGB灯的设备节点添加到了根节点的末尾,完整内容请参考本章配套代码linux_driver/device_tree_rgb_led/imx6ull-mmc-npi.dts。

  • 第1-23行: 这部分列出了控制RGB灯的三个引脚所使用的的寄存器,这些寄存器的作用以及用法已经在裸机部分详细介绍,这里不再赘述,如有疑问可以参考**字符设备驱动——点亮LED灯**章节。

  • 第25-29行: 这里就是RGB灯的设备树节点,节点名“rgb_led”由于在根节点下,很明显它的设备树路径为“/rgb_led”,在驱动程序中我们会用到这cells”定义了它的子节点的reg属性样式。“compatible”属性用于匹配驱动,在驱动我们会配置一个和“compatible”一样的参数,这样加载驱动是就可以自动和这个设备树节点匹配了。

  • 第31-63行: rgb_led节点的子节点。RGB灯使用了三个引脚,如上所示,它会用到15个寄存器,为方便管理,我们为每个引脚创建了一个子节点,从上到下依次为红灯控制引脚、绿灯控制引脚、蓝灯控制引脚。它们三个非常相似,我们这里只以第一个红灯控制引脚为例讲解。在红灯子节点中只定义了三个属性,“compatie = “fire,rgb_led_red””表示这是一个红灯子节点,对于本实验来说可有可无。“reg = < …>”定义红灯引脚使用到寄存器。一共有五个,排列顺序与注释中的一致。“status = “okay””定义子节点的状态,我们要用这个子节点所以设置为“okay”。

2.2.2. 编写驱动程序

基于设备树的驱动程序与平台总线驱动非常相似,差别是平台总线驱动中的平台驱动要和平台设备进行匹配, 使用设备树后设备树取代“平台设备”的作用,平台驱动只需要和与之对应的设备树节点匹配即可。

驱动程序主要内容包括编写平台设备驱动框架、编写.prob函数、实现字符设备操作函数集、驱动注销四部分内容。 源码linux_driver/device_tree_rgb_led/rgb_led.c。

驱动入口函数

驱动入口函数仅仅注册一个平台驱动,在整个入口函数中仅仅调用了“platform_driver_register”函数注册了一个平台驱动。参数是传入一个平台设备结构体。如下所示

/*
*驱动初始化函数
*/
static int __init led_platform_driver_init(void)
{
    int DriverState;
    DriverState = platform_driver_register(&led_platform_driver);
    printk(KERN_EMERG "\tDriverState is %d\n", DriverState);
    return 0;
}

定义平台设备结构体

注册平台驱动时会用到平台设备结构体,在平台设备结构体主要作用是指定平台驱动的.probe函数、指定与平台驱动匹配的平台设备, 使用了设备树后就是指定与平台驱动匹配的设备树节点。

static const struct of_device_id rgb_led[] = {
    {.compatible = "fire,rgb_led"},
    {/* sentinel */}
    };

/*定义平台设备结构体*/
struct platform_driver led_platform_driver = {
    .probe = led_probe,
    .driver = {
            .name = "rgb-leds-platform",
            .owner = THIS_MODULE,
            .of_match_table = rgb_led,
    }
};
  • 第1-4行: 定义匹配表

  • 第7-8行: 就是我们定义的平台设备结构体。其中“.probe =led_probe,”指定.probe函数。.probe函数比较特殊,当平台驱动和设备树节点匹配后会自动执行.probe函数,后面的RGB灯的初始化以及字符设备的注册都在这个函数中实现(当然也可以在其他函数中实现)。

  • 第9-14行:“.driver = { …}”定义driver的一些属性,包括名字、所有者等等,其中最需要注意的是“.of_match_table ”属性,它指定这个驱动的匹配表。这里只定义了一个匹配值“.compatible = “fire,rgb_led”,这个驱动将会和设备树中“compatible =“fire,rgb_led”的节点匹配”,准确的说是和““compatible = “fire,rgb_led””的相对根节点的子节点匹配。我们在根节点下定义了rgb_led子节点,并且设置“compatible = “fire,rgb_led”;所以正常情况下,驱动会和这个子节点匹配。

实现.probe函数

之前说过,当驱动和设备树节点匹配成功后会自动执行.probe函数,所以我们在.probe函数中实现一些初始化工作。 本实验将RGB初始化以及字符设备的初始化全部放到.probe函数中实现,.probe函数较长,但包含大量的简单、重复性的初始化代码,非常容易理解。

/*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/
struct led_resource
{
             //rgb_led_red的设备树节点
    struct device_node *device_node;
    void __iomem *virtual_CCM_CCGR;
    void __iomem *virtual_IOMUXC_SW_MUX_CTL_PAD;
    void __iomem *virtual_IOMUXC_SW_PAD_CTL_PAD;
    void __iomem *virtual_DR;
    void __iomem *virtual_GDIR;
};

/*定义 R G B 三个灯的led_resource 结构体,保存获取得到的节点信息*/
struct led_resource led_red;
struct led_resource led_green;
struct led_resource led_blue;

static int led_probe(struct platform_device *pdv)
{
    //保存错误状态码
    int ret = -1;
    unsigned int register_data = 0;

    printk(KERN_EMERG "\t  match successed  \n");

    /*获取rgb_led的设备树节点*/
    rgb_led_device_node = of_find_node_by_path("/rgb_led");
    if (rgb_led_device_node == NULL)
    {
            printk(KERN_ERR "\t  get rgb_led failed!  \n");
            return -1;
    }

    /*获取rgb_led节点的红灯子节点*/
    led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red");
    if (led_red.device_node == NULL)
    {
            printk(KERN_ERR "\n get rgb_led_red_device_node failed ! \n");
            return -1;
    }

    /*获取 reg 属性并转化为虚拟地址*/
    led_red.virtual_CCM_CCGR = of_iomap(led_red.device_node, 0);
    led_red.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_red.device_node, 1);
    led_red.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_red.device_node, 2);
    led_red.virtual_DR = of_iomap(led_red.device_node, 3);
    led_red.virtual_GDIR = of_iomap(led_red.device_node, 4);

    /*初始化红灯*/
    register_data = readl(led_red.virtual_CCM_CCGR);
    register_data |= (0x03 << 26);
            //开启时钟
    writel(register_data, led_red.virtual_CCM_CCGR);

    register_data = readl(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);
    register_data &= ~(0xff << 0);
    register_data |= (0x05 << 0);
            //设置复用功能
    writel(register_data, led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);

    register_data = readl(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);
    register_data = (0x10B0);
            //设置PAD 属性
    writel(register_data, led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);

    register_data = readl(led_red.virtual_GDIR);
    register_data |= (0x01 << 4);
            //设置GPIO1_04 为输出模式
    writel(register_data, led_red.virtual_GDIR);

    register_data = readl(led_red.virtual_DR);
    register_data |= (0x01 << 4);
            //设置 GPIO1_04 默认输出高电平
    writel(register_data, led_red.virtual_DR);

    /*获取rgb_led节点的绿灯子节点*/
    led_green.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_green");
    if (led_green.device_node == NULL)
    {
            printk(KERN_ERR "\n get rgb_led_green_device_node failed ! \n");
            return -1;
    }

    /*获取 reg 属性并转化为虚拟地址*/
    led_green.virtual_CCM_CCGR = of_iomap(led_green.device_node, 0);
    led_green.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_green.device_node, 1);
    led_green.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_green.device_node, 2);
    led_green.virtual_DR = of_iomap(led_green.device_node, 3);
    led_green.virtual_GDIR = of_iomap(led_green.device_node, 4);

    /*初始化绿灯*/
    register_data = readl(led_green.virtual_CCM_CCGR);
    register_data |= (0x03 << 12);
            //开启时钟
    writel(register_data, led_green.virtual_CCM_CCGR);

    register_data = readl(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
    register_data &= ~(0xff << 0);
    register_data |= (0x05 << 0);
            //设置复用功能
    writel(register_data, led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);

    register_data = readl(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
    register_data = (0x10B0);
            //设置PAD 属性
    writel(register_data, led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);

    register_data = readl(led_green.virtual_GDIR);
    register_data |= (0x01 << 20);
             //设置GPIO4_IO20 为输出模式
    writel(register_data, led_green.virtual_GDIR);

    register_data = readl(led_green.virtual_DR);
    register_data |= (0x01 << 20);
            //设置 GPIO4_IO20 默认输出高电平
    writel(register_data, led_green.virtual_DR);

    /*获取rgb_led节点的蓝灯子节点*/
    led_blue.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_blue");
    if (led_blue.device_node == NULL)
    {
            printk(KERN_ERR "\n get rgb_led_blue_device_node failed ! \n");
            return -1;
    }

    /*获取 reg 属性并转化为虚拟地址*/
    led_blue.virtual_CCM_CCGR = of_iomap(led_blue.device_node, 0);
    led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_blue.device_node, 1);
    led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_blue.device_node, 2);
    led_blue.virtual_DR = of_iomap(led_blue.device_node, 3);
    led_blue.virtual_GDIR = of_iomap(led_blue.device_node, 4);

    /*初始化绿灯*/
    register_data = readl(led_blue.virtual_CCM_CCGR);
    register_data |= (0x03 << 12);
            //开启时钟
    writel(register_data, led_blue.virtual_CCM_CCGR);

    register_data = readl(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);
    register_data &= ~(0xff << 0);
    register_data |= (0x05 << 0);
            //设置复用功能
    writel(register_data, led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);

    register_data = readl(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);
    register_data = (0x10B0);
             //设置PAD 属性
    writel(register_data, led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);

    register_data = readl(led_blue.virtual_GDIR);
    register_data |= (0x01 << 19);
            //设置GPIO4_IO19 为输出模式
    writel(register_data, led_blue.virtual_GDIR);

    register_data = readl(led_blue.virtual_DR);
    register_data |= (0x01 << 19);
            //设置 GPIO4_IO19 默认输出高电平
    writel(register_data, led_blue.virtual_DR);

    /*注册 字符设备部分*/
    //采用动态分配的方式,获取设备编号,次设备号为0,
    //设备名称为rgb-leds,可通过命令cat  /proc/devices查看
    //DEV_CNT为1,当前只申请一个设备编号
    ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
    if (ret < 0)
    {
            printk("fail to alloc led_devno\n");
            goto alloc_err;
    }

    //关联字符设备结构体cdev与文件操作结构体file_operations
    led_chr_dev.owner = THIS_MODULE;
    cdev_init(&led_chr_dev, &led_chr_dev_fops);

    //添加设备至cdev_map散列表中
    ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
    if (ret < 0)
    {
            printk("fail to add cdev\n");
            goto add_err;
    }

    /*创建类 */
    class_led = class_create(THIS_MODULE, DEV_NAME);

    /*创建设备*/
    device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);

    return 0;

add_err:
    //添加设备失败时,需要注销设备号
    unregister_chrdev_region(led_devno, DEV_CNT);
    printk("\n error! \n");
alloc_err:

    return -1;
  • 第2-11行: 自定义led资源结构体,用于保存获取得到的设备节点信息以及转换后的虚拟寄存器地址。

  • 第27-32行: 使用of_find_node_by_path函数获取设备树节点“/rgb_led”,获取成功后会返回“/rgb_led”节点的“设备节点结构体”后面的代码我们就可以根据这个“设备节点结构体”访问它的子节点。

  • 第32-158行: 依次初始化 红、绿、蓝灯,这三部分非常相似,这里仅介绍第三部分红灯初始化部分。初始化过程如下:

    • 第35-40行:获取红灯子节点,这里使用函数“of_find_node_by_name”,参数rgb_led_device_node指定从rgb_led节点开始搜索,参数“rgb_led_red”指定要获取那个节点,这里是rgb_led节点下的rgb_led_red子节点。

    • 第42-47行:获取并转换reg属性,我们知道reg属性保存的就是寄存器地址(物理地址),这里使用“of_iomap”函数,获取并完成物理地址到虚拟地址的转换。

    • 第49-74行:初始化寄存器,至于如何将初始化GPIO在裸机章节已经详细介绍这里不再赘述,需要注意的是这里只能用系统提供的API(例如这里读写的是32位数据,使用writel和readl),不能像裸机那样直接使用“=”、“&=”、“|=”等等那样直接修改寄存器。

  • 第160-189行: 注册一个字符设备。字符设备的注册过程与之前讲解的字符设备驱动非常相似,这部分代码就是从字符设备驱动拷贝得到的。这里仅仅做简单介绍。

注册字符设备使用到的结构体:

static dev_t led_devno;                                      //定义字符设备的设备号
static struct cdev led_chr_dev;                      //定义字符设备结构体chr_dev
struct class *class_led;                             //保存创建的类
struct device *device;                                       // 保存创建的设备

static struct file_operations led_chr_dev_fops ={
            .owner = THIS_MODULE,
            .open = led_chr_dev_open,
            .write = led_chr_dev_write,
};
  • 第164-169行: 使用“alloc_chrdev_region”动态申请主设备号,并保存到led_devno结构体中。

  • 第172-173行: 使用“cdev_init”初始化字符设备。

  • 第176-181行: 使用“cdev_add”将字符设备添加到系统。如果需要驱动自动创建设备节点,则还要创建类和设备。

  • 第184行: 使用“class_create”函数创建类。

  • 第187行: 使用“device_create”创建设备,其中参数“DEV_NAME”用于指定设备节点名,这个名字在应用程序中会用到。

如果驱动和设备树节点完成匹配,系统会自动执行.probe函数,从上方代码可知,.probe函数完成了RGB灯的初始化和字符设备的创建。 下一步我们只需要在字符设备的操作函数集中控制RGB灯即可。

实现字符设备操作函数集

为简化程序设计这里仅仅实现了字符设备操作函数集中的.write函数,.write函数根据收到的信息控制RGB灯的亮、灭,结合代码介绍如下:

/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
    printk("\n open form driver \n");
    return 0;
}

/*字符设备操作函数集,write函数*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{

    unsigned int register_data = 0; //暂存读取得到的寄存器数据
    unsigned char write_data; //用于保存接收到的数据

    int error = copy_from_user(&write_data, buf, cnt);
    if (error < 0)
    {
            return -1;
    }

    /*设置 GPIO1_04 输出电平*/
    if (write_data & 0x04)
    {
            register_data = readl(led_red.virtual_DR);
            register_data &= ~(0x01 << 4);
            writel(register_data, led_red.virtual_DR); // GPIO1_04引脚输出低电平,红灯亮
    }
    else
    {
            register_data = readl(led_red.virtual_DR);
            register_data |= (0x01 << 4);
            writel(register_data, led_red.virtual_DR); // GPIO1_04引脚输出高电平,红灯灭
    }

    /*设置 GPIO4_20 输出电平*/
    if (write_data & 0x02)
    {
            register_data = readl(led_green.virtual_DR);
            register_data &= ~(0x01 << 20);
            writel(register_data, led_green.virtual_DR); // GPIO4_20引脚输出低电平,绿灯亮
    }
    else
    {
            register_data = readl(led_green.virtual_DR);
            register_data |= (0x01 << 20);
            writel(register_data, led_green.virtual_DR); // GPIO4_20引脚输出高电平,绿灯灭
    }

    /*设置 GPIO4_19 输出电平*/
    if (write_data & 0x01)
    {
            register_data = readl(led_blue.virtual_DR);
            register_data &= ~(0x01 << 19);
            writel(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出低电平,蓝灯亮
    }
    else
    {
            register_data = readl(led_blue.virtual_DR);
            register_data |= (0x01 << 19);
            writel(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出高电平,蓝灯灭
    }

    return 0;
}

/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
    {
            .owner = THIS_MODULE,
            .open = led_chr_dev_open,
            .write = led_chr_dev_write,
};

我们仅实现了两个字符设备操作函数,open 对应led_chr_dev_open函数这是一个空函数。 .write对应led_chr_dev_write函数,这个函数接收应用程序传回的命令,根据命令控制RGB三个灯的亮、灭。

  • 第14-19行: 使用copy_from_user函数将用户空间的数据拷贝到内核空间。这里传递的数据是一个无符号整型数据。

  • 第21-61行: 解析命令,如何解析由我们自己决定。本例中使用数字的后三位从高到低依次对应红灯、绿灯、蓝灯,对应位是1则亮灯否则熄灭。例如0x03表示红灯灭,绿灯和蓝灯亮。0x07 表示全亮。具体实现过程很简单这里不再赘述。

2.2.3. 编写测试应用程序

在驱动程序中我采用自动创建设备节点的方式创建了字符设备的设备节点文件,文件名可自定义,写测试应用程序时记得文件名即可。本例程设备节点名为“rgb_led”。测试程序很简单,源码如下所示。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{

    printf("led_tiny test\n");
    /*判断输入的命令是否合法*/
    if(argc != 2)
    {
        printf(" commend error ! \n");
        return -1;
    }

    /*打开文件*/
    int fd = open("/dev/rgb_led", O_RDWR);
    if(fd < 0)
    {
            printf("open file : %s failed !\n", argv[0]);
            return -1;
    }

            //将受到的命令值转化为数字;
    unsigned char commend = atoi(argv[1]);

    /*判断命令的有效性*/

    /*写入命令*/
    int error = write(fd,&commend,sizeof(commend));
    if(error < 0)
    {
        printf("write file error! \n");
        close(fd);
        /*判断是否关闭成功*/
    }

    /*关闭文件*/
    error = close(fd);
    if(error < 0)
    {
        printf("close file error! \n");
    }

    return 0;
}
  • 第10-14行: 简单判断输入是否合法,运行本测试应用程序时argc应该为2。它由应用程序文件名和命令组成例如“./test_app <命令值>”。

  • 第16-22行: 打开设备文件。

  • 第25-30行: 将终端输入的命令值转化为数字最终使用write函数

  • 第39-43行: 关闭设备文件。

3. 编译驱动程序

3.1. 编译设备树

将rgb_led节点添加到设备树中,并在内核源码目录执行如下命令。

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig

make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs

最终会在内核源码/arch/arm/boot/dts下生成 imx6ull-mmc-npi.dtb 这个设备树文件。

3.2. 编译驱动和应用程序

执行 make 命令,Makefile和前面大致相同。 最终会生成rgb_led.ko和test_app应用程序

4. 程序运行结果

在板卡上的部分GPIO可能会被系统占用,在使用前请根据需要修改 /boot/uEnv.txt 文件, 可注释掉某些设备树插件的加载,重启系统,释放相应的GPIO引脚。

如本节实验中,可能在鲁班猫系统中默认使能了 LED 的设备功能, 用在了LED子系统。引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。

方法参考如下:

取消 LED 设备树插件,以释放系统对应LED资源,操作如下:

broken

如若运行代码时出现“Device or resource busy”或者运行代码卡死等等现象, 请按上述情况检查并按上述步骤操作。

如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。

4.1. 实验操作

将设备树、驱动程序和应用程序通过NFS或SCP等方式拷贝到开发板中。

替换原来的设备树/usr/lib/linux-image-4.19.35-imx6/imx6ull-mmc-npi.dtb,并输入 

sudo reboot 

命令重启开发板。

执行如下命令加载驱动:

sudo insmod ./rgb_led.ko

驱动加载成功后直接运行应用程序如下所示。 

更改设备的权限:

sudo chmod 666 /dev/rgb_led

命令:

 ./test_app <命令> 

执行结果如下:

与此同时,你还会看到LED呈现不同颜色的光。

命令是一个“unsigned char”型数据,只有后三位有效,每一位代表一个灯,从高到低依次代表红、绿、蓝,1表示亮,0表示灭。例如命令=4 则亮红灯,命令=7则三个灯全亮。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/920186.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无人机的激光雷达避障系统阐述!

一、材料 激光二极管基底材料&#xff1a;激光二极管是激光雷达的核心组件之一&#xff0c;其基底材料通常采用硅或砷化镓。硅材料成本低、易于加工&#xff0c;但发光效率相对较低&#xff1b;而砷化镓材料发光效率高&#xff0c;但成本较高。 光学镜片材料&#xff1a;激光…

OpenTelemetry 赋能DevOps流程的可观测性革命

原作者&#xff1a;天颇 原出处&#xff1a;微信公众号 乘云数字DATABUFF 原文地址&#xff1a;https://mp.weixin.qq.com/s/D_f31EBtLu7Rr0gahuF-bw 引言 在当今快节奏的软件开发和运维环境中&#xff0c;DevOps 已经成为主流&#xff0c;它通过整合开发和运维流程&#xff0…

蓝桥杯每日真题 - 第18天

题目&#xff1a;&#xff08;出差&#xff09; 题目描述&#xff08;13届 C&C B组E题&#xff09; 解题思路&#xff1a; 问题分析 问题实质是一个带权图的最短路径问题&#xff0c;但路径的权重包含两个部分&#xff1a; 从当前城市到下一个城市的路程时间。 当前城市的…

如何对AWS进行节省

AWS 云服务器的费用确实可能会让人感到高昂&#xff0c;尤其是在资源使用不当或配置过多的情况下。不过&#xff0c;通过一些策略的合理应用和优化&#xff0c;完全可以降低云服务的使用成本&#xff0c;实现高效节省。以下是九河云总结的几种主要的优化方法&#xff0c;帮助你…

Elasticsearch:更好的二进制量化(BBQ)对比乘积量化(PQ)

作者&#xff1a;来自 Elastic Benjamin Trent 为什么我们选择花时间研究更好的二进制量化而不是在 Lucene 和 Elasticsearch 中进行生产量化。 我们一直在逐步使 Elasticsearch 和 Lucene 的向量搜索变得更快、更实惠。我们的主要重点不仅是通过 SIMD 提高搜索速度&#xff0…

绿光一字线激光模组:工业制造与科技创新的得力助手

在现代工业制造和科技创新领域&#xff0c;绿光一字线激光模组以其独特的性能和广泛的应用前景&#xff0c;成为了不可或缺的关键设备。这种激光模组能够发射出一条明亮且精确的绿色激光线&#xff0c;具有高精度、高稳定性和长寿命的特点&#xff0c;为各种精密加工和测量需求…

使用SaaS化的Aurora应用快速搭建私人ChatGPT助手

使用SaaS化的Aurora应用快速搭建私人ChatGPT助手 简介&#xff1a; Aurora是一个带UI且免费的GPT私人聊天助手&#xff0c;可切换GPT-3.5&#xff0c;4&#xff0c;4o等常用版本。用户可通过部署Aurora&#xff0c;快速打造自己专属的AI助手。阿里云计算巢已将Aurora打包为SaaS…

购物街项目TabBar的封装

1.TabBar介绍 在购物街项目中 不论页面如何滚动 始终存在一个TabBar固定在该项目的底部 他在该项目中 扮演者选项卡栏的角色 内部存在若干选项 而选项中 固定存在两部分(图片文本) 其中主要涉及到TabBar/TabBarItem这些和业务无关的共享组件(建议存放于components/common中)、…

折叠光腔衰荡高反射率测量技术的matlab模拟理论分析

折叠光腔衰荡高反射率测量技术的matlab模拟理论分析 1. 前言2. 光腔模型3. 光腔衰荡过程4. 衰荡时间与反射率的关系5. 测量步骤①. 光腔调节&#xff1a;②. 光腔衰荡测量&#xff1a;③. 计算衰荡时间常数&#xff1a;④. 反射率计算&#xff1a; 6. 实际应用中的调整7. 技术优…

NIO 与传统 IO:深入理解与应用场景

在 Java 编程中&#xff0c;IO&#xff08;输入/输出&#xff09;操作是不可或缺的一部分。Java 提供了两种主要的 IO 机制&#xff1a;传统的阻塞式 IO&#xff08;Blocking IO&#xff09;和非阻塞式 IO&#xff08;Non-blocking IO&#xff09;&#xff0c;后者通常被称为 N…

VMware高危漏洞VMSA-2024-0019修复堆溢出和权限提升漏洞

一、概述 VMware vCenter Server 高危漏洞&#xff08;CVE-2024-38812、CVE-2024-38813&#xff09;再次受到攻击&#xff0c;需要升级补丁&#xff0c;详情查看之前文章紧急通告VMware vCenter高危漏洞CVE-2024-38812和CVE-2024-38813修复方案 再次更新了漏洞 二、漏洞影像描…

什么是Hadoop

Hadoop 介绍 Hadoop 是由 Apache 开发的开源框架&#xff0c;用于处理分布式环境中的海量数据。Hadoop 使用 Java 编写&#xff0c;通过简单的编程模型允许在集群中进行大规模数据集的存储和计算。它具备高可靠性、容错性和扩展性。 分布式存储&#xff1a;Hadoop 支持跨集群…

038集——quadtree(CAD—C#二次开发入门)

效果如下&#xff1a; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using System; using System.Collections.Generic; using System.Linq; using System.T…

ISUP协议视频平台EasyCVR私有化视频平台新能源汽车充电停车管理方案的创新与实践

在环保意识提升和能源转型的大背景下&#xff0c;新能源汽车作为低碳出行的选择&#xff0c;正在全球迅速推广。但这种快速增长也引发了充电基础设施短缺和停车秩序混乱等挑战&#xff0c;特别是在城市中心和人口密集的居住区&#xff0c;这些问题更加明显。因此&#xff0c;开…

Spring Boot中使用AOP和反射机制设计一个的幂等注解(两种持久化模式),简单易懂教程

该帖子介绍如何设计利用AOP设计幂等注解&#xff0c;且可设置两种持久化模式 1、普通模式&#xff1a;基于redis的幂等注解&#xff0c;持久化程度较低 2、增强模式&#xff1a;基于数据库&#xff08;MySQL&#xff09;的幂等注解&#xff0c;持久化程度高 如果只需要具有re…

算法编程题-网格中的最短路径

算法编程题-网格中的最短路径 原题描述思路简述代码实现[^1]复杂度分析 原题描述 LeetCode 1293 网格中的最短路径&#xff1a;给定一个m * n的网格&#xff0c;网格中的每一个点的值为0&#xff08;无障碍&#xff09;&#xff0c;为1&#xff08;有障碍&#xff09;&#xf…

Xcode 项目内 OC 混编 Python,调用 Python 函数,并获取返回值(基于 python 的 c函数库)

1:新建 Xcode 工程 2:工程添加 Python.framework 1597052861430.jpg 3:在当前工程下新建一个名字为 googleT 的 python 文件(googleT.py) 1597052584962.jpg 在 googleT.py 文件内写入一个测试 python 函数 def lgf_translate( str ):var1 Hello World!print (str var1)retu…

蓝桥杯每日真题 - 第16天

题目&#xff1a;&#xff08;卡牌&#xff09; 题目描述&#xff08;13届 C&C B组C题&#xff09; 解题思路&#xff1a; 题目分析&#xff1a; 有 n 种卡牌&#xff0c;每种卡牌的现有数量为 a[i]&#xff0c;所需的最大数量为 b[i]&#xff0c;还有 m 张空白卡牌。 每…

计算机网络——路由选择算法

路由算法 路由的计算都是以子网为单位计算的——找到从原子网到目标子网的路径 链路状态算法 序号——&#xff08;源路由器&#xff0c;序号&#xff09;——如果发现这个序号重复或者老了——就不扩散 先测量——再泛洪获得路由 路由转发情况 若S——>W是21则不更改——…

Android - Pixel 6a 手机OS 由 Android 15 降级到 Android 14 操作记录

Pixel 6a 手机由 Android 14 升级到 Android 15了&#xff0c;但是由于一些原因又想降级回 Android 14&#xff0c; 能降吗&#xff1f;该怎么降级呢&#xff1f;本篇文章来记述实际操作过程&#xff0c;希望能给想做相同操作的人一些帮助。 答案当然是能降&#xff0c;而且我…