RK3566编写GPIO驱动


前言

本篇章主要实现GPIO驱动,并控制led灯,同理可以实现类似的蜂鸣器或者继电器的控制。内容涉及到如何定制化/sys/xxx/xxx节点接口,以及字符设备的注册使用,并些出测试层序测试验证驱动。

一、如何定制化:/sys/xxx/xxx接口

1.kobject_create_and_add函数的使用

Linux2.6内核以后引入了sysfs文件系统。sysfs被看成是与proc同类型的文件系统。sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问。sysfs 文件系统总是被挂载在 /sys 挂载点上。kobject_create_and_add函数动态创建一个kobject结构并注册到sysfs。实际可以理解成在 /sys下新添加了一个文件夹。

1.1 在/sys下新添加一个文件:

在 /sys下新添加了一个名字为“test”文件夹

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>                                                                                                         
static struct kobject *kobj;
static int kobject_test_init(void){
    kobj = kobject_create_and_add("test", NULL);
    return 0;
}
static void kobject_test_exit(void){
    kobject_del(kobj);
}
module_init(kobject_test_init);
module_exit(kobject_test_exit);
MODULE_LICENSE("GPL v2");

测试结果:

rk3566_rgo:/sys # ls -l
drwxr-xr-x root     root              2023-06-13 09:31 test 
drwxr-xr-x root     root              2023-06-13 05:02 class

1.2 在/sys下新添加嵌套文件:

在/sys下新添加嵌套文件,目录下创建"test/test"目录,修改代码如下

#include <linux/module.h>                                                                                      
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>
static struct kobject *kobj;
static struct kobject *ckobj;
static int kobject_test_init(void){
    kobj = kobject_create_and_add("test", NULL);
    ckobj = kobject_create_and_add("test", kobj);
    return 0;
}
static void kobject_test_exit(void){
    kobject_del(ckobj);
    kobject_del(kobj);
}
module_init(kobject_test_init);
module_exit(kobject_test_exit);
MODULE_LICENSE("GPL v2");

测试结果:

rk3566_rgo:/sys/test # ls
test

上述只是测试一些demo,实际的我实现的部分代码如下:

struct kobject * gpio_obj;
gpio_obj = kobject_create_and_add("rk3566_led", NULL);
if(gpio_obj == NULL){
	DBG("kobject_create_and_add error\n");
	return -EINVAL;
}

最后在sys文件下新添加了rk3566_led的文件夹:

rk3566_rgo:ls /sys
rk3566_led 

我们现在只是添加了文件夹但是具体的接口上是没有可以控制的节点的,还需要借助于sysfs_create_file函数创建控制节点。

2. sysfs_create_file函数的使用

sysfs 操作表包括 store 和 show 两个函数,在用户态读取数据时,调用 show 函数将指定属性值存入 buffer 中返回给用户态,与之相反 store 存储用户态传入的属性值。在 /sys 文件系统中,通过 echo/cat 的操作,最终会调用到 show/store 函数,而这两个函数的具体实现可以是可以放置到驱动程序,调用情况如下:

获取该文件的句柄fd=open(“dev”)->vfs_open(“dev”)->sysfs_open(“dev”)
读操作read()->vfs_read()->sysfs_read_file()->sysfs_ops->show()
写操作write()->vfs_write()->sysfs_write_file()->sysfs_ops->store ()

/sys挂载了sysfs文件系统,因此所有对/sys目录下的文件或者目录的操作都会通过sysfs文件的接口进行访问。

2.1 部分代码实现

static ssize_t gpio_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) {
	unsigned long on = simple_strtoul(buf, NULL, 10);
	if(!strcmp(attr->attr.name, "rk3566_led_value")){
		if(on){
			gpio_direction_output(rk3566_led_ID, 1);
			DBG("gpio_direction_output is %d \n",rk3566_led_ID);
		}
		else{
			gpio_direction_output(rk3566_led_ID, 0);
			DBG("gpio_direction_output is 0\n");
		}
	}
	return count;
}
static ssize_t gpio_show(struct device *dev, struct device_attribute *attr,char *buf){
	int tmp = 0;
	if(!strcmp(attr->attr.name, "rk3566_led_value")){
		tmp = gpio_get_value(rk3566_led_ID);
		DBG("gpio_get_value is %d \n",tmp);
		if(tmp>0)
			return strlcpy(buf, "1\n", 3);
		else
			return strlcpy(buf, "0\n", 3);
	}
	return 0;
}
// S_IRWXU | S_IRWXG 读写权限
static DEVICE_ATTR(rk3566_led_value, S_IRWXU | S_IRWXG, gpio_show,gpio_store);
static int rk3566_led_probe(struct platform_device *pdev)
{
        int ret;
	    struct kobject * gpio_obj;
		rk3566_led_ID = get_gpio_num(rk3566_led);
		if(!gpio_is_valid(rk3566_led_ID)){
		dev_err(&pdev->dev, "invalid power gpio%d\n", rk3566_led_ID);
		}
		ret = devm_gpio_request(&pdev->dev, rk3566_led_ID, "rk3566_led");
		if (ret) {
			dev_err(&pdev->dev,"failed to request GPIO%d for rk3566_led \n",rk3566_led_ID);
			//return -EINVAL;
		}
		gpio_obj = kobject_create_and_add("rk3566_led", NULL);
		if(gpio_obj == NULL){
		  DBG("kobject_create_and_add error\n");
		  return -EINVAL;
		}	
		ret = sysfs_create_file(gpio_obj,&dev_attr_rk3566_led_value.attr);
		if (ret) {
		  DBG("sysfs_create_file error\n");
		  return ret;
		}
		CharDevInit("1234");
		return 0;
}

最后在sys文件下新添加了rk3566_led的文件夹并且有一个控制节点“rk3566_led_value”。

rk3566_rgo:ls /sys/rk3566_led
rk3566_led_value

通过echo和cat命令可以控制和获取gpio状态。

rk3566_rgo:echo 1 > /sys/rk3566_led/rk3566_led_value
rk3566_rgo:cat      /sys/rk3566_led/rk3566_led_value

二、字符设备注册:/dev/xxx

2.1 字符设备注册过程

2.1.1、为字符设备申请设备号,包括主设备号和次设备号:

a、使用内核自动分配设备号函数:

   alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name);

b、向内核注册自定义设备号

   register_chrdev_region(dev_t from,unsigned count,const char * name);

分配设备号函数alloc_chrdev_region与register_chrdev_region使用其中一个即可。
c、卸载模块时,注销设备号

   unregister_chrdev_region(dev_t from,unsigned count);

2.1.2、向内核申请cdev结构体空间,用于描述字符设备

   struct cdev *cdev_alloc(void)

2.1.3、初始化cdev结构体,初始化操作函数集

   void cdev_init(struct cdev *cdev, const struct file_operations *fops)

2.1.4、向内核注册字符设备,count表示注册的个数

   int cdev_add(struct cdev *p, dev_t dev, unsigned count)

2.1.5、驱动卸载时,注销字符cdev结构体,释放系统资源

   void cdev_del(struct cdev *p)

2.2 部分代码:

dev_t devNo;						 
struct cdev *pCDev;					 
struct class *pClass;
struct device *pDevice;
static int rk3566_led_ID;

 
static int Rk3566_led_Open(struct inode *inode, struct file *filp){
    DBG("CharDevOpen\n");
    return 0;
}
static ssize_t Rk3566_led_Read (struct file *file, char __user *buf,size_t cnt, loff_t *offt){
    int ret = 0;
	int len = 3;
	char Buf[len];
	memset(Buf, '0',len);//初始化数组元素全部为'0'
	Buf[0] = gpio_get_value(rk3566_led_ID)+'0';
	ret = copy_to_user(buf,Buf,len);
    if (!ret) { 
        DBG("kernel copy_to_user \n");
    } else {
        DBG("kernel copy_to_user fail, %d\n", ret);
    }
 
    return len;
}
 
static ssize_t Rk3566_led_Write (struct file *file, const char __user *buf,size_t cnt, loff_t *offt){
    int ret = 0;
	int len = 3;
	char kernelBuf[len];
	memset(kernelBuf, '0',len);//初始化数组元素全部为'0'
    ret = copy_from_user(kernelBuf, buf, len);
    if (!ret) {
		ret = len;
        DBG("kernel copy_from_user %s \n", kernelBuf);
    } else {
        ret = 0;
        DBG("kernel copy_from_user fail, %d\n", ret);
    }
    if (kernelBuf[0]=='o' && kernelBuf[1]=='n'){
			gpio_direction_output(rk3566_led_ID, 1);
			DBG("led on !!\n");
	}else{
			gpio_direction_output(rk3566_led_ID, 0);
			DBG("led off !!\n");
	} 
    return ret;
}
 
static int Rk3566_led_Release(struct inode *inode, struct file *filp){
    DBG("Rk3566 led Release");
    return 0;
}
 
static struct file_operations fops = {
    .owner      = THIS_MODULE,
    .open       = Rk3566_led_Open,
    .read       = Rk3566_led_Read,
    .write      = Rk3566_led_Write,
    .release    = Rk3566_led_Release,
};
static int  CharDevInit(char *DEV_NAME)
{
    int ret;
#if 1 //动态申请
    ret = alloc_chrdev_region(&devNo, 0, 1, DEV_NAME);
    if (0 > ret) {
        DBG( " alloc_chrdev_region fail\n");
        goto err1;
    }
#else //静态申请
#define CHRDEV_MAJOR 200
    devNo = MKDEV(CHRDEV_MAJOR, 0);
    ret = register_chrdev_region(devNo, 1, DEV_NAME);
    if (0 > ret) {
        DBG( " register_chrdev_region fail\n");
        goto err1;
    }
#endif
    pCDev = cdev_alloc();
    if (NULL == pCDev) {        
        DBG( " cdev_alloc fail\n");
        ret = (-ENOMEM);
        goto err2;
    }
    cdev_init(pCDev, &fops);
    pCDev->owner = THIS_MODULE;
    ret = cdev_add(pCDev, devNo, 1);
    if (0 > ret) {
        DBG( " cdev_alloc fail\n");
        goto err3;
    }
    pClass = class_create(THIS_MODULE, DEV_NAME);
    if (IS_ERR(pDevice)){
        DBG( " class_create fail\n");
        ret = PTR_ERR(pClass);
        goto err4;
    }
    pDevice = device_create(pClass, NULL, devNo, NULL, DEV_NAME);
    if (IS_ERR(pDevice)) {
        DBG( " device_create fail\n");
        ret = PTR_ERR(pDevice);
        goto err5;
    }
    DBG(" Init success dev:%s, MAJOR:%d, MINOR:%d.\n", 
        DEV_NAME, MAJOR(devNo), MINOR(devNo));
    return 0;
err5:
    class_destroy(pClass);
err4:
    cdev_del(pCDev);
err3:
    kfree(pCDev);
err2:
    unregister_chrdev_region(devNo, 1);
err1:
    return ret;
}

static int get_gpio_num(char *s){
	int gpio_num = 0;
	if(s[6]<91){
		 gpio_num = (s[4]-'0')*32+(s[6]-'A')*8+s[7]-'0';//大写
	}		 
	else{ 
		 gpio_num = (s[4]-'0')*32+(s[6]-'a')*8+s[7]-'0';//小写
    }
	DBG("gpio_num = %d\n",gpio_num);	
	return gpio_num;
}

卸载程序注销设备号

static void __exit rk3566_led_exit(void)
{
    class_destroy(pClass);
    cdev_del(pCDev);
    kfree(pCDev);
    unregister_chrdev_region(devNo, 1); 
    ***
    ***
}

字符设备中的名字通过函数参数的方式封装起来,比较方便使用。最后在probe函数中调用。

static int rk3566_led_probe(struct platform_device *pdev)
{
       ****
       ****
		CharDevInit("rk3566");
		return 0;
}

字符设备注册的节点:

rk3566_rgo: ls /dev/1234

2.3 完整驱动程序

/* SPDX-License-Identifier: GPL-2.0 */
#include <dt-bindings/gpio/gpio.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <linux/slab.h>  
#include <linux/fs.h>     //alloc_chrdev_region() file_operations()
#include <linux/cdev.h>   //cdev_alloc()、cdev_init()、cdev_add()、cdev_del()
#include <linux/device.h> //class_create()
#include <linux/kdev_t.h> //MKDEV MINOR MAJORs
#include <linux/ide.h>	//copy_to_user()、copy_from_user()、kfree()

#define rk3566_led     "GPIO4_A6"
#define DEBUG
#ifdef DEBUG
#define DBG(fmt,arg...) printk("rk3566gpio: "fmt,##arg)
#else
#define DBG(fmt,arg...) do { } while (0)
#endif

dev_t devNo;						//设备号 由主设备号12bit + 20bit次设备组成
struct cdev *pCDev;					//字符设备
struct class *pClass;
struct device *pDevice;
static int rk3566_led_ID;

 
static int Rk3566_led_Open(struct inode *inode, struct file *filp){
    DBG("CharDevOpen\n");
    return 0;
}
static ssize_t Rk3566_led_Read (struct file *file, char __user *buf,size_t cnt, loff_t *offt){
    int ret = 0;
	int len = 3;
	char Buf[len];
	memset(Buf, '0',len);//初始化数组元素全部为'0'
	Buf[0] = gpio_get_value(rk3566_led_ID)+'0';
	ret = copy_to_user(buf,Buf,len);
    if (!ret) { 
        DBG("kernel copy_to_user \n");
    } else {
        DBG("kernel copy_to_user fail, %d\n", ret);
    }
 
    return len;
}
 
static ssize_t Rk3566_led_Write (struct file *file, const char __user *buf,size_t cnt, loff_t *offt){
    int ret = 0;
	int len = 3;
	char kernelBuf[len];
	memset(kernelBuf, '0',len);//初始化数组元素全部为'0'
    ret = copy_from_user(kernelBuf, buf, len);
    if (!ret) {
		ret = len;
        DBG("kernel copy_from_user %s \n", kernelBuf);
    } else {
        ret = 0;
        DBG("kernel copy_from_user fail, %d\n", ret);
    }
    if (kernelBuf[0]=='o' && kernelBuf[1]=='n'){
			gpio_direction_output(rk3566_led_ID, 1);
			DBG("led on !!\n");
	}else{
			gpio_direction_output(rk3566_led_ID, 0);
			DBG("led off !!\n");
	} 
    return ret;
}
 
static int Rk3566_led_Release(struct inode *inode, struct file *filp){
    DBG("Rk3566 led Release");
    return 0;
}
 
static struct file_operations fops = {
    .owner      = THIS_MODULE,
    .open       = Rk3566_led_Open,
    .read       = Rk3566_led_Read,
    .write      = Rk3566_led_Write,
    .release    = Rk3566_led_Release,
};
static int  CharDevInit(char *DEV_NAME)
{
    int ret;
#if 1 //动态申请
    ret = alloc_chrdev_region(&devNo, 0, 1, DEV_NAME);
    if (0 > ret) {
        DBG( " alloc_chrdev_region fail\n");
        goto err1;
    }
#else //静态申请
     #define CHRDEV_MAJOR 200
    devNo = MKDEV(CHRDEV_MAJOR, 0);
    ret = register_chrdev_region(devNo, 1, DEV_NAME);
    if (0 > ret) {
        DBG( " register_chrdev_region fail\n");
        goto err1;
    }
#endif
    pCDev = cdev_alloc();
    if (NULL == pCDev) {        
        DBG( " cdev_alloc fail\n");
        ret = (-ENOMEM);
        goto err2;
    }
    cdev_init(pCDev, &fops);
    pCDev->owner = THIS_MODULE;
    ret = cdev_add(pCDev, devNo, 1);
    if (0 > ret) {
        DBG( " cdev_alloc fail\n");
        goto err3;
    }
    pClass = class_create(THIS_MODULE, DEV_NAME);
    if (IS_ERR(pDevice)){
        DBG( " class_create fail\n");
        ret = PTR_ERR(pClass);
        goto err4;
    }
    pDevice = device_create(pClass, NULL, devNo, NULL, DEV_NAME);
    if (IS_ERR(pDevice)) {
        DBG( " device_create fail\n");
        ret = PTR_ERR(pDevice);
        goto err5;
    }
    DBG(" Init success dev:%s, MAJOR:%d, MINOR:%d.\n", 
        DEV_NAME, MAJOR(devNo), MINOR(devNo));
    return 0;
err5:
    class_destroy(pClass);
err4:
    cdev_del(pCDev);
err3:
    kfree(pCDev);
err2:
    unregister_chrdev_region(devNo, 1);
err1:
    return ret;
}

static int get_gpio_num(char *s){
	int gpio_num = 0;
	if(s[6]<91){
		 gpio_num = (s[4]-'0')*32+(s[6]-'A')*8+s[7]-'0';//大写
	}		 
	else{ 
		 gpio_num = (s[4]-'0')*32+(s[6]-'a')*8+s[7]-'0';//小写
    }
	DBG("gpio_num = %d\n",gpio_num);	
	return gpio_num;
}

static ssize_t gpio_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) {
	unsigned long on = simple_strtoul(buf, NULL, 10);
	if(!strcmp(attr->attr.name, "rk3566_led_value")){
		if(on){
			gpio_direction_output(rk3566_led_ID, 1);
			DBG("gpio_direction_output is %d \n",rk3566_led_ID);
		}
		else{
			gpio_direction_output(rk3566_led_ID, 0);
			DBG("gpio_direction_output is 0\n");
		}
	}
	return count;
}
static ssize_t gpio_show(struct device *dev, struct device_attribute *attr,char *buf){
	int tmp = 0;
	if(!strcmp(attr->attr.name, "rk3566_led_value")){
		tmp = gpio_get_value(rk3566_led_ID);
		DBG("gpio_get_value is %d \n",tmp);
		if(tmp>0)
			return strlcpy(buf, "1\n", 3);
		else
			return strlcpy(buf, "0\n", 3);
	}
	return 0;
}
// S_IRWXU | S_IRWXG 读写权限
static DEVICE_ATTR(rk3566_led_value, S_IRWXU | S_IRWXG, gpio_show,gpio_store);

static int rk3566_led_probe(struct platform_device *pdev)
{
        int ret;
	    struct kobject * gpio_obj;
		rk3566_led_ID = get_gpio_num(rk3566_led);
		if(!gpio_is_valid(rk3566_led_ID)){
		dev_err(&pdev->dev, "invalid power gpio%d\n", rk3566_led_ID);
		}
		ret = devm_gpio_request(&pdev->dev, rk3566_led_ID, "rk3566_led");
		if (ret) {
			dev_err(&pdev->dev,"failed to request GPIO%d for rk3566_led \n",rk3566_led_ID);
			//return -EINVAL;
		}
		gpio_obj = kobject_create_and_add("rk3566_led", NULL);
		if(gpio_obj == NULL){
		  DBG("kobject_create_and_add error\n");
		  return -EINVAL;
		}	
		ret = sysfs_create_file(gpio_obj,&dev_attr_rk3566_led_value.attr);
		if (ret) {
		  DBG("sysfs_create_file error\n");
		  return ret;
		}
		CharDevInit("rk3566");
		return 0;
}

static struct platform_device rk3566_led_device = {//无设备树下使用
	.name = "led",
	.id = -1,
}; 

static int rk3566_led_remove(struct platform_device *dev)
{
	DBG("rk3566 led remove !\n");
	return 0;
}

static struct platform_driver rk3566_led_driver= {
	.driver = {
		.name = "led",
		.owner	= THIS_MODULE,
	},
	.probe = rk3566_led_probe,
	.remove = rk3566_led_remove,
};


static int __init rk3566_led_init(void)
{
    platform_driver_register(&rk3566_led_driver);
    platform_device_register(&rk3566_led_device);
    return 0;
}

static void __exit rk3566_led_exit(void)
{
    class_destroy(pClass);
    cdev_del(pCDev);
    kfree(pCDev);
    unregister_chrdev_region(devNo, 1); 
	platform_driver_unregister(&rk3566_led_driver);
	platform_device_unregister(&rk3566_led_device);
}

module_init(rk3566_led_init);
module_exit(rk3566_led_exit);

MODULE_AUTHOR("RK3566");
MODULE_DESCRIPTION("GPIO ctrl driver");
MODULE_LICENSE("GPL");



三、编写测试程序驱动功能

3.1 测试程序代码

rk3566ledtest.c

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> 
#include <stdlib.h>
#define DEV_NAME	"/dev/rk3566"
 
int main(int argc, char *argv[])
{
	int ret;
	int fd;
	char buf[256] = {0};
 
	fd = open(DEV_NAME, O_RDWR);
	if (0 > fd) {
		printf("open dev fail: %s\r\n", DEV_NAME);
		perror("cause:");
		return -1;
	} else {
		printf("open dev success: %s\r\n", DEV_NAME);
	}
 
	//1 读一下当前数据	
	ret = read(fd, buf, sizeof(buf));
	printf("1 read ret:%d, buf:[%s]\r\n", ret, buf);
 
	//2 写入自己的数据
	if(atoi(argv[1])==1)
	   snprintf(buf, sizeof(buf), "on");
    else
	   snprintf(buf, sizeof(buf), "off");
	ret = write(fd, buf, strlen(buf));
	printf("2 write ret:%d, len:%d, data:%s\r\n", ret, strlen(buf), buf);	
	
	//3 再次读数据,应该是步骤2写入的数据
	memset(buf, 0x0, sizeof(buf));
	ret = read(fd, buf, sizeof(buf));
	printf("3 read ret:%d, buf:[%s]\r\n", ret, buf);
 
	close(fd);
 
	return 0;
}

交叉编译可执行文件

ubuntu@ubuntu-desktop:~/rk/rk3566$ arm-linux-gnueabihf-gcc rk3566ledtest.c -o rk3566ledtest -static

将rk3566ledtest 文件p用adb工具push到vendor 分区,然后给权限 chmod 0777 rk3566ledtest
打开led:

./rk3566ledtest 1

关闭led:

./rk3566ledtest 0

3.2 测试结果

rk3566_rgo:/vendor # ./rk3566ledtest 1
open dev success: /dev/rk3566
1 read ret:3, buf:[000] //设置gpio前读取一次gpio状态与后面做对比
2 write ret:3, len:2, data:on//设置gpio的状态1
3 read ret:3, buf:[100]//从驱动读取回来的gpio的状态为1

rk3566_rgo:/vendor # ./1 0
open dev success: /dev/rk3566
1 read ret:3, buf:[100]   //设置gpio前读取一次gpio状态与后面做对比
2 write ret:3, len:3, data:off //设置gpio的状态0
3 read ret:3, buf:[000] //从驱动读取回来的gpio的状态为0
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
您好!要使用RK3566驱动控制灯带,您可以按照以下步骤进行操作: 1. 首先,确保您的RK3566开发板已正确连接到灯带。通常,灯带会有一个电源接口和一个数据接口。将电源接口连接到适当的电源供应器,并将数据接口连接到RK3566开发板的GPIO引脚。 2. 在RK3566开发板上安装适当的操作系统和驱动程序。您可以根据开发板的型号和供应商提供的文档来完成此步骤。确保驱动程序与您的操作系统版本兼容。 3. 打开RK3566开发板上的终端或命令行界面,并以管理员身份登录。 4. 使用适当的命令或工具来控制灯带。具体命令和工具取决于您使用的驱动程序和灯带类型。以下是一些常见的方法: - 使用GPIO控制:通过设置GPIO引脚的状态来控制灯带的开关和亮度。您可以使用GPIO库或命令行工具来设置引脚的状态。 - 使用SPI或I2C总线:如果您的灯带使用SPI或I2C总线进行通信,您可以使用相应的命令或库函数来发送数据包并控制灯带。 - 使用特定的驱动程序或库:某些灯带可能需要特定的驱动程序或库来进行控制。您可以查找供应商提供的文档或社区支持来获取更多信息。 请注意,具体的步骤和命令可能因您使用的开发板、操作系统和灯带类型而有所不同。建议您查阅相关文档和参考资料,以确保正确配置和操作。 希望对您有所帮助!如果您有任何其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那肯定是很多年以后!

你的鼓励就我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值