RK3568笔记三十九:多个LED驱动开发测试(设备树)

news2024/9/20 22:30:40

若该文为原创文章,转载请注明原文出处。

通过设备树配置一个节点下两个子节点控制两个IO口,一个板载LED,一个外接LED。

一、介绍

通过学习设备树控制GPIO,发现有多种方式

一、直接通过寄存器控制

二、通过设备树,但不通过pinctrl子系统

三、通pinctrl的GPIO子系统。

正点原子三个方法都有测试代码,自行测试。

学习控制多个LED主要是想模拟I2C或SPI,不使用硬件方式处理。

但很多手册,只给了单个GPIO的设备树配置方式。

此篇记录,方便后面I2C或SPI模拟测试使用。

测试使用的是正点原子的ATK-DLRK3568板子,根据操作可以测试成功,环境需要自行搭建。

二、原理图

两个LED,一个板载LED,一个

LED1: GPIO0_C0  高电平亮,低电平灭

LED2: GPIO3_C4 高电平灭,低电平亮(没有硬件,简易搭建)

三、创建节点

1、在设备树中创建设备节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-atk-evb1-ddr4-v10.dtsi文件,添加gpios节点

gpios {
        	compatible = "gpio-led-test";
        	pinctrl-names = "default";
        	status = "okay";
        	led1 {
            		compatible = "led1-test";
            		pinctrl-0 = <&pinctrl_led>;
            		gpios-led = <&gpio0 RK_PC0 GPIO_ACTIVE_LOW>;
            		status = "okay";
        	};
        
        	beep {
            		compatible = "beep-test";
            		pinctrl-0 = <&pinctrl_beep>;
            		gpios-beep = <&gpio3 RK_PC4 GPIO_ACTIVE_HIGH>;
            		status = "okay";
       		};
   	};

意思是在设备树下创建节点gpios,gpios节点下又生成两个子节点led1,和beep

2、创建设备的 pinctrl 节点

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/目录下的rk3568-pinctrl.dtsi文件,在最后面增加节点

led-gpio {
                pinctrl_led: led-gpio-ctrl {
                        rockchip,pins = <0 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;
                };
        };

	beep-gpio {
                pinctrl_beep: beep-gpio-ctrl {
                        rockchip,pins = <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
                };
        };

修改后,重新编译内核,生成boot.img文件,重新烧写即可。

重启后会在/proc/device-tree下找到gpios节点,gpios下又有beep和led1节点

四、驱动编写

1、led_gpios.c

#include <linux/init.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/uaccess.h>


#define GPIOLED_CNT 2 /* 设备号个数 */
#define GPIOLED_NAME "ledtest" /* 名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* gpioled 设备结构体 */
struct gpioled_dev{
    dev_t devid; /* 设备号 */
    struct cdev cdev; /* cdev */
    struct class *class; /* 类 */
    struct device *device[2]; /* 设备 */
    int major; /* 主设备号 */
    int minor; /* 次设备号 */
    struct device_node *nd[GPIOLED_CNT]; /* 设备节点 */
    int gpios[GPIOLED_CNT]; /* led 所使用的 GPIO 编号 */
};

struct gpioled_dev gpioled; /* led 设备 */

/*
 * @description : 打开设备
 * @param – inode : 传递给驱动的 inode
 * @param – filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return : 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
  int minor = MINOR(inode->i_rdev);
    filp->private_data = &gpioled; /* 设置私有数据 */
    printk("open minor is %d\r\n",minor);

    return 0;
}


/*
 * @description : 从设备读取数据
 * @param – filp : 要打开的设备文件(文件描述符)
 * @param - buf : 返回给用户空间的数据缓冲区
 * @param - cnt : 要读取的数据长度
 * @param – offt : 相对于文件首地址的偏移
 * @return : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf,
        size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description : 向设备写数据
 * @param - filp : 设备文件,表示打开的文件描述符
 * @param - buf : 要写给设备写入的数据
 * @param - cnt : 要写入的数据长度
 * @param – offt : 相对于文件首地址的偏移
 * @return : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf,
        size_t cnt, loff_t *offt)
{

    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;

  int minor = MINOR(file_inode(filp)->i_rdev);/*获取设备的子节点号,在4.x内核必须用file_inode(filp)代替filp->f_dentry->inode,不然编译会出错*/

    printk("write inode minor is %d\r\n",minor);

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0){
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0]; /* 获取状态值 */
 printk("ledstat = %d\n", ledstat);
 
  switch(minor){
      case 0:
                if(ledstat == LEDON) {
                    gpio_set_value(dev->gpios[0], 0); /* 打开 LED 灯 */
			  printk("open led\n");
                } else if(ledstat == LEDOFF) {
                    gpio_set_value(dev->gpios[0], 1); /* 关闭 LED 灯 */
			  printk("close led\n");
                }
      break;
      case 1:
                if(ledstat == LEDON) {
                    gpio_set_value(dev->gpios[1], 1); /* 打开 LED 灯 */
			  printk("open beep\n");
                } else if(ledstat == LEDOFF) {
                    gpio_set_value(dev->gpios[1], 0); /* 关闭 LED 灯 */
			  printk("close beep\n");
                }
      break;
      default:
      break;

  }

    return 0;
}

/*
 * @description : 关闭/释放设备
 * @param – filp : 要关闭的设备文件(文件描述符)
 * @return : 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

/*
 * @description : 驱动入口函数
 * @param : 无
 * @return : 无
 */
static int __init led_init(void)
{
    int ret = 0;
    struct property *proper;
    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点:gpioled */
    gpioled.nd[0] = of_find_node_by_path("/gpios/led1");
    if(gpioled.nd[0] == NULL) {
        printk("gpioled node cant not found!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.gpios[0] = of_get_named_gpio(gpioled.nd[0], "gpios-led", 0);
    if(gpioled.gpios[0] < 0) {
        printk("can't get led-gpio!\r\n");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.gpios[0]);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(gpioled.gpios[0], 0);
    if(ret < 0) {
        printk("can't set le-gpio!\r\n");
    }
  /*获取字节点的compatible属性*/
    proper = of_find_property(gpioled.nd[0], "compatible", NULL);
  if(proper == NULL) {
      printk("compatible property find failed\r\n");
  } else {
      printk("led compatible = %s\r\n", (char*)proper->value);
  }


    /*  设置 BEEP 所使用的 GPIO */
      /* 1、获取设备节点:gpioled */
    gpioled.nd[1] = of_find_node_by_path("/gpios/beep");
    if(gpioled.nd[1] == NULL) {
        printk("gpiobeep node cant not found!\r\n");
        return -EINVAL;
    } else {
        printk("gpiobeep node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.gpios[1] = of_get_named_gpio(gpioled.nd[1], "gpios-beep", 0);
    if(gpioled.gpios[1] < 0) {
        printk("can't get beep-gpio!\r\n");
        return -EINVAL;
    }
    printk("beep-gpio num = %d\r\n", gpioled.gpios[1]);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(gpioled.gpios[1], 1);
    if(ret < 0) {
        printk("can't set beep-gpio!\r\n");
    }
  /*获取字节点的compatible属性*/
    proper = of_find_property(gpioled.nd[0], "compatible", NULL);
  if(proper == NULL) {
      printk("beep compatible property find failed\r\n");
  } else {
      printk("beep compatible = %s\r\n", (char*)proper->value);
  }


  /*符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) { /* 定义了设备号 */
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT,
                GPIOLED_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT,
                GPIOLED_NAME); /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d,minor=%d\r\n",gpioled.major,
            gpioled.minor);

    /* 2、初始化 cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    /* 3、添加一个 cdev */
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
  if(ret)
  {
      printk("cdev_add erro!\r\n");
      goto cdevadd_erro;

  }
    printk("cdev_add ok!\r\n");
    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, "my-led");
    if (IS_ERR(gpioled.class)) {
      printk("class_create erro!\r\n");
        ret=PTR_ERR(gpioled.class);
      goto class_create_erro;

    }

    /* 5、创建设备 */
    gpioled.device[0] = device_create(gpioled.class, NULL,
            MKDEV(gpioled.major,0), NULL, "led1");
    if (IS_ERR(gpioled.device[0])) {
        ret =  PTR_ERR(gpioled.device[1]);
      goto device_create1_erro;
    }

    /* 5、创建设备 */
    gpioled.device[1] = device_create(gpioled.class, NULL,
            MKDEV(gpioled.major,1), NULL, "beep");
    if (IS_ERR(gpioled.device[1])) {
      
        ret = PTR_ERR(gpioled.device[1]);
    }
 
    printk("led_init ok\n");
    return 0;
evice_create1_erro:
     class_destroy(gpioled.class);
lass_create_erro:
   cdev_del(&gpioled.cdev); /* 删除 cdev */
devadd_erro:
     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); 
   return ret;
 }
 
 /*
    189 * @description : 驱动出口函数
  * @param : 无
  * @return : 无
  */
 static void __exit led_exit(void)
 {
     /* 注销字符设备驱动 */
     cdev_del(&gpioled.cdev); /* 删除 cdev */
     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */
 
     device_destroy(gpioled.class, MKDEV(gpioled.major,0));
     device_destroy(gpioled.class, MKDEV(gpioled.major,1));
     class_destroy(gpioled.class);
 }
 
 module_init(led_init);
 module_exit(led_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("yifeng");

代码有个注意事项,

1、获取设备节点,节点不要填错,为设备树的节点:

gpioled.nd[0] = of_find_node_by_path("/gpios/led1");

gpioled.nd[1] = of_find_node_by_path("/gpios/beep");

2、创建设备,会在/dev/下生成节点

/* 5、创建设备 */
    gpioled.device[0] = device_create(gpioled.class, NULL, MKDEV(gpioled.major,0), NULL, "led1");

    /* 5、创建设备 */
    gpioled.device[1] = device_create(gpioled.class, NULL, MKDEV(gpioled.major,1), NULL, "beep");

2、makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-

export  ARCH  CROSS_COMPILE

CURRENT_PATH := $(shell pwd)
obj-m := led_gpios.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译生成ko文件,拷贝到开发板。

五、App编写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv)
{
  int fd;
  char status;
 
  if (argc != 3)
  {
    printf("Usage: %s <dev> <on | off>\\n", argv[0]);
    return -1;
  }
 
  fd = open(argv[1], O_RDWR);
  if (fd == -1)
  {
    printf("can not open file %s\\n", argv[1]);
    return -1;
  }
 
  if (0 == strcmp(argv[2], "on"))
  {
    status = 1;
    write(fd, &status, 1);
  }
  else
  {
    status = 0;
    write(fd, &status, 1);
  }
 
  close(fd);
 
  return 0;
}
 

编译,生成的可执行文件拷贝到开发板测试。

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp

六、测试

1、关闭 LED 的心跳灯

echo none > /sys/class/leds/work/trigger

2、加载和卸载驱动模块

insmod led_gpios.ko

加载成功

3、测试

./ledApp /dev/led1 on
./ledApp /dev/led1 off

./ledApp /dev/beep on
 ./ledApp /dev/beep off

实现状态是相反的,需要根据实际的修改,但控制是正常的。

如有侵权,或需要完整代码,请及时联系博主。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【中项】系统集成项目管理工程师-第一模块:IT技术和管理-1.5数字化转型与元宇宙

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 备注&#xff1a;IT技术和管理-1.4章节涉及敏感&#xff0c;无法发送&#xff0c;故跳过。 软考同样是国家人社部和工信部组织的国家级考试…

工业三防平板适用于各种工业场景

在当今高度工业化的时代&#xff0c;工业三防平板作为一种专为恶劣工业环境设计的设备&#xff0c;正逐渐成为各种工业场景中不可或缺的一部分。 工业三防平板具备出色的防水、防尘和防摔性能&#xff0c;这使得它能够在潮湿、多尘以及容易发生碰撞的环境中稳定运行。无论是在矿…

“富二代”用英语怎么说?真的不是“second rich”!成人英语学习柯桥学外语到蓝天广场

看了沈腾马丽新电影《抓娃娃》&#xff0c;笑得前仰后合。遇上“不靠谱”的爹妈硬是要穷养孩子&#xff0c;就算是“富二代”日子也不好过啊&#xff01; 想必很多人小时候都幻想过&#xff1a;自己的爸妈其实是大富豪&#xff0c;为了磨练自己才假装没钱的。随着逐渐长大才不得…

【内网Tesla T4_16G为例】GPU安装NVIDIA Driver、CUDA、cuDNN、Python

这篇文章主要记录下在内网(无法连接外网)服务器安装NVIDIA Driver、CUDA、cuDNN、Python的过程&#xff0c;机器配置GPU&#xff1a;1*NVIDIA T4 16G&#xff0c;CPU&#xff1a;8C42G&#xff0c;操作系统&#xff1a;GPU-RHEL7.9-x86-64。 想了解如何内网部署ollama&#xf…

数据结构(双向链表)

链表的分类 链表的结构⾮常多样&#xff0c;以下情况组合起来就有8种&#xff08;2 x 2 x 2&#xff09;链表结构&#xff1a; 虽然有这么多的链表的结构&#xff0c;但是我们实际中最常⽤还是两种结构&#xff1a;单链表和双向带头循环链表 1.⽆头单向⾮循环链表&#xff1a…

【ROS2】高级:解锁 Fast DDS 中间件的潜力 [社区贡献]

目标&#xff1a;本教程将展示如何在 ROS 2 中使用 Fast DDS 的扩展配置功能。 教程级别&#xff1a;高级 时间&#xff1a;20 分钟 目录 背景 先决条件在同一个节点中混合同步和异步发布 创建具有发布者的节点创建包含配置文件的 XML 文件执行发布者节点创建一个包含订阅者的节…

AI伦理挑战:构建未来信任的桥梁

在人工智能(AI)技术蓬勃发展的今天&#xff0c;其伦理挑战如同双刃剑的另一面&#xff0c;日益成为全球关注的焦点。面对隐私侵犯、算法偏见、信息真实性危机等伦理困境&#xff0c;我们需要构建全面而精细的应对策略&#xff0c;确保技术进步的同时&#xff0c;守护人类社会的…

MimicMotion-腾讯开源视频生成框架

腾讯宣布开源可控视频生成框架 MimicMotion&#xff0c;该框架可以通过提供参考人像及由骨骼序列表示的动作&#xff0c;来产生平滑的高质量人体动作视频 MimicMotion 具有以下几个亮点&#xff1a; 首先&#xff0c;通过引入了置信度感知的姿态引导信号&#xff0c;大幅提升了…

读书笔记:改善既有代码的设计

差不多两年都没写过博客了&#xff0c;好学的习惯差不多都落下了&#xff0c;两年里几乎也把学到的很多东西都应用了&#xff0c;但不学习好像就有点停步不前的感觉了&#xff0c;以后给自己定个目标每周写一遍博客。 写博客好处&#xff1a; 一是加深自己的印象&#xff08;能…

幽微之处见真章:数据类型与内存存储的内在联系

嘿嘿,家人们,今天咱们来深度剖析数据类型在内存中的存储,好啦,废话多不讲,开干! 1.:数据类型介绍 在前面呢,博主已经介绍了基本的数据类型: char //字符数据类型 ---->占据1个字节 short //短整型 …

51单片机STC89C52RC——18.1 HC-SR04超声波测距

目的/效果 独立按键K1按下后开始测距&#xff0c;LCD显示距离&#xff08;mm&#xff09; 一&#xff0c;STC单片机模块 二&#xff0c;HC-SR04 超声波测距 2.1 HC-SR04 简介 HC-SR04超声波测距模块提供2cm~400cm的测距功能&#xff0c;精度达3mm。 2.2 时序 以上时序图表明…

前端面试题(JS篇五)

一、同步与异步的区别 同步指的是当一个进程在执行某一个请求的时候&#xff0c;如果这个请求需要等待一段时间才能返回&#xff0c;那么这个进程会一直等待下去&#xff0c;直到这个消息返回之后才会继续执行。 指的是当一个进程在执行某一个请求的时候&#xff0c;如果这个请…

Leetcode 1302.层数最深子叶结点的和

大家好&#xff0c;今天我给大家分享一下我关于这个题的想法&#xff0c;我这个题过程比较复杂&#xff0c;但大家如果觉得好的话&#xff0c;就请给个免费的赞吧&#xff0c;谢谢了^ _ ^ 1.题目要求: 给你一棵二叉树的根节点 root &#xff0c;请你返回 层数最深的叶子节点的…

初学者如何通过建立个人博客盈利

建立个人博客不仅能让你在网上表达自己&#xff0c;还能与他人建立联系。通过博客&#xff0c;可以创建自己的空间&#xff0c;分享想法和故事&#xff0c;并与有相似兴趣和经历的人交流。 本文将向你展示如何通过建立个人博客来实现盈利。你将学习如何选择博客主题、挑选合适…

【华为OD笔试】2024D卷命题规律解读【分析300+场OD笔试考点总结】

可上 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 od1441了解算法冲刺训练&#xff08;备注【CSDN】否则不通过&#xff09; 文章目录 相关推荐阅读华为OD笔试2024D卷命题规律解读华为OD算法/大厂面试高频题算法练习冲刺训练 相关推荐阅读 【华为OD笔试】2024D卷机考套题…

Android Framework学习笔记(4)----Zygote进程

Zygote的启动流程 Init进程启动后&#xff0c;会加载并执行init.rc文件。该.rc文件中&#xff0c;就包含启动Zygote进程的Action。详见“RC文件解析”章节。 根据Zygote对应的RC文件&#xff0c;可知Zygote进程是由/system/bin/app_process程序来创建的。 app_process大致处…

LLM 的储备知识

GPT一代 模型堆叠了12个解码器层。由于在这种设置中没有编码器&#xff0c;这些解码器层将不会有普通transformer解码器层所具有的编码器-解码器注意力子层。但是&#xff0c;它仍具有自注意力层。 训练过程 Transformer Decoder 结构 编码器&#xff08;6 layers&#xff09…

Template_C++

C模板 C提供了function template. function template&#xff1a;实际上是建立一个通用函数&#xff0c;其函数类型和形参类型不具体制定&#xff0c;用一个虚拟的类型来代表。这个通用的函数就称为函数模版。 是不是可以这样理解&#xff0c;函数模版就是给了一种功能&…

Linux 下 ElasticSearch 集群部署

目录 1. ElasticSearch下载 2. 环境准备 3. ElasticSearch部署 3.1 修改系统配置 3.2 开放端口 3.3 安装 ElasticSearch 4. 验证 本文将以三台服务器为例&#xff0c;介绍在 linux 系统下ElasticSearch的部署方式。 1. ElasticSearch下载 下载地址&#xff1a;Past Rel…

vue 如何做一个动态的 BreadCrumb 组件,el-breadcrumb ElementUI

vue 如何做一个动态的 BreadCrumb 组件 el-breadcrumb ElementUI 一、ElementUI 中的 BreadCrumb 定义 elementUI 中的 Breadcrumb 组件是这样定义的 <template><el-breadcrumb separator"/"><el-breadcrumb-item :to"{ path: / }">主…