[RK3566-Android11] 使用SPI方式点LED灯带-JE2815/WS2812,实现呼吸/渐变/随音量变化等效果

问题描述

之前写了一篇使用GPIO方式点亮LED灯带的文章

https://blog.csdn.net/jay547063443/article/details/134688745?fromshare=blogdetail\&sharetype=blogdetail\&sharerId=134688745\&sharerefer=PC\&sharesource=jay547063443\&sharefrom=from_link

使用GPIO有一个问题是,在系统开机或者内存占用过大时,在做呼吸灯这种颜色变化较快的效果时,会出现显示乱颜色,或者显示的颜色不准确的问题。这还是由于内存占用高时操作GPIO控制纳秒级的高低电平宽度时会导致延时高,导致乱色。RK在RK3308_Linux_PartyBox_SDK音频方案点亮灯带使用的是SPI的方式。但这部分代码未开放,只知道原理是使用spi data模拟输出,调整好clk频率,用0和1去控制输出高低电平的宽度。使用SPI的好处是,类似PWM可以发送持续稳定的波形,可以更精确的控制纳秒级的高低电平。

下图为成品图:


在附上一个呼吸灯效果视频:

led


解决方案:

1...config打开SPI配置:

c 复制代码
# CONFIG_SPI_PXA2XX is not set
+CONFIG_SPI_ROCKCHIP=y
+CONFIG_SPI_ROCKCHIP_TEST=y
# CONFIG_SPI_SC18IS602 is not set

2.dts配置spi具体使用哪个可以根据自己的原理图配置

c 复制代码
&spi0 {
	status = "okay";
	// max-freq = <48000000>; 
	// 默认不用配置,SPI 设备工作时钟值,io 时钟由工作时钟分频获取
	// assigned-clock-rates = <200000000>; 
	// 使能DMA模式,通讯长度少于32字节不建议用,置空赋值去掉使能,如 "dma-names;";
	// dma-names = "tx","rx";
	// 默认不用配置,读采样延时,详细参考 "常见问题""延时采样时钟配置方案" 章节
	// rx-sample-delay-ns = <10>; 
	spi_test@00 {
		compatible ="rockchip,spi_test_bus1_cs0";
		// 片选0或者1
		reg = <0>;
		id = <0>;
		// 不配置则为 0,配置为1
		// spi-cpol;
		// 不配置则为 0,配置为1
		// spi-cpha;
		// IO 先传输 lsb
		// spi-lsb-first;
		// spi clk输出的时钟频率,不超过50M
		spi-max-frequency = <1000000>;
	};
};

3.配置spi-gpio口,以spi0为例子:

这些都是RK默认的配置

c 复制代码
	spi0: spi@fe610000 {
		compatible = "rockchip,rk3066-spi";
		reg = <0x0 0xfe610000 0x0 0x1000>;
		interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
		#address-cells = <1>;
		#size-cells = <0>;
		clocks = <&cru CLK_SPI0>, <&cru PCLK_SPI0>;
		clock-names = "spiclk", "apb_pclk";
		dmas = <&dmac0 20>, <&dmac0 21>;
		dma-names = "tx", "rx";
		pinctrl-names = "default", "high_speed";
		pinctrl-0 = <&spi0m0_cs0 &spi0m0_cs1 &spi0m0_pins>;
		pinctrl-1 = <&spi0m0_cs0 &spi0m0_cs1 &spi0m0_pins_hs>;
		status = "disabled";
	};

4.修改rkxxxx-pinctrl.dtsi里的spi0m0_pins和spi0m0_pins_hs改为我们需要控制的口

c 复制代码
		/omit-if-no-ref/
		spi0m0_pins: spi0m0-pins {
			rockchip,pins =
				/* spi0_clkm0 */
				<0 RK_PB5 2 &pcfg_pull_none>,
				/* spi0_misom0 */
				<0 RK_PC5 2 &pcfg_pull_none>,
				/* spi0_mosim0 */
				<0 RK_PB6 2 &pcfg_pull_none>;
		};
		
		spi0m0_pins_hs: spi0m0-pins {
			rockchip,pins =
				/* spi0_clkm0 */
				<0 RK_PB5 2 &pcfg_pull_up_drv_level_1>,
				/* spi0_misom0 */
				<0 RK_PC5 2 &pcfg_pull_up_drv_level_1>,
				/* spi0_mosim0 */
				<0 RK_PB6 2 &pcfg_pull_up_drv_level_1>;
		};

将spi0_mosim0 改为自己需要控制的GPIO口,可以配置上spi0_clkm0便于使用逻辑分析仪抓取发送的数据结果,spi0_misom0/cs0/csi都不用管他。检查GPIO不要出现复用的情况。

5.spi-test驱动kernel\drivers\spi\spi-rockchip-test.c

查看spi_test_write函数,可以看到RK开放了操作节点便于操作

c 复制代码
		printk("echo id number size > /dev/spi_misc_test\n");
		printk("echo write 0 10 255 > /dev/spi_misc_test\n");
		printk("echo write 0 10 255 init.rc > /dev/spi_misc_test\n");
		printk("echo read 0 10 255 > /dev/spi_misc_test\n");
		printk("echo loop 0 10 255 > /dev/spi_misc_test\n");
		printk("echo setspeed 0 1000000 > /dev/spi_misc_test\n");
		printk("echo config 8 > /dev/spi_misc_test\n");

这里我们主要看write部分的逻辑:

c 复制代码
	} else if (!strcmp(cmd, "write")) {
		char name[64];
		int fd;
    	mm_segment_t old_fs = get_fs();

		sscanf(argv[0], "%d", &id);
		sscanf(argv[1], "%d", &times);
		sscanf(argv[2], "%d", &size);
		if (argc > 3) {
			sscanf(argv[3], "%s", name);
			set_fs(KERNEL_DS);
		}

		txbuf = kzalloc(size, GFP_KERNEL);
		if (!txbuf) {
			printk("spi write alloc buf size %d fail\n", size);
			return n;
		}

		if (argc > 3) {
			fd = ksys_open(name, O_RDONLY, 0);
			if (fd < 0) {
				printk("open %s fail\n", name);
			} else {
				ksys_read(fd, (char __user *)txbuf, size);
				ksys_close(fd);
			}
			set_fs(old_fs);
		} else {
			for (i = 0; i < size; i++){
				txbuf[i] = i % 256;
			}
			printk(" txbuf %d \n", i);
		}

		start_time = ktime_get();
		for (i = 0; i < times; i++)
			spi_write_slt(id, txbuf, size);
		end_time = ktime_get();
		cost_time = ktime_sub(end_time, start_time);
		us = ktime_to_us(cost_time);

		bytes = size * times * 1;
		bytes = bytes * 1000 / us;
		printk("spi write %d*%d cost %ldus speed:%ldKB/S\n", size, times, us, bytes);

		kfree(txbuf);

按照文档说明 echo 类型 id 循环次数 传输长度>/dev/spi_misc_test

从函数逻辑可以看到,经由echo write 0 10 255 > /dev/spi_misc_test的第三个参数传输长度,在不带文件名的情况时,走

c 复制代码
			for (i = 0; i < size; i++){
				txbuf[i] = i % 256;
			}

根据输入的第三个参数传输长度,会取低八位连续发送。一个字节是8位。

5.我们来看一下RGB色的转换逻辑,以255,0,0为例子如下图:

原理类似于我们拼接波形,255 0 0转化为二进制为

1111 1111 0000 0000 0000 0000,也就是

cc cc cc cc 88 88 88 88 88 88 88 88。

204 204 204 204 136 136 136 136 136 136 136 136

也就是说用

c 复制代码
			for (i = 0; i < size; i++){
				txbuf[i] = i % 256;
			}

取低八位的逻辑需要发出。

205 205 205 205 137 137 137 137 137 137 137 137。

6.由于不可知原因,附上简单版关键代码逻辑:

c 复制代码
	} else if (!strcmp(cmd, "rgb")) {
	...
	...
		offset = 0;
		for (repeat = 0; repeat < 15; repeat++) { // 外层循环,重复15次
			for (i = 0; i < 12; i++) {
				txbuf[i] = kzalloc(size[i], GFP_KERNEL);
				if (!txbuf[i]) {
					printk("Wi:spi write alloc buf size %d fail\n", size[i]);
					kfree(full_txbuf);
					return -1;
				}

				// 填充当前缓冲区
				for (j = 0; j < size[i]; j++) {
					txbuf[i][j] = j % 256; // 根据需求填充
				}
				printk("Wi:txbuf[%d] filled, size %d\n", i, size[i]);

				// 获取当前缓冲区的最后一个字节,并填充到合并缓冲区
				full_txbuf[offset] = txbuf[i][size[i] - 1];
				offset++;
			}
		}

		// 记录开始时间
		start_time = ktime_get();

		// 发送合并后的缓冲区
		for (j = 0; j < times; j++) {
			spi_write_slt(id, full_txbuf, total_size); // 发送所有数据
		}
	...
	...

额外填入12组参数,取低八位的最后一个,循环15次填入buf。最后由spi_write_slt发出。

这里的repeat < 15,就是指灯珠数,一条灯带是15颗灯。

spi-max-frequency = <3300000>;

如下命令:

echo rgb 0 1 205 205 205 205 137 137 137 137 137 137 137 137 > /dev/spi_misc_test关闭灯光

echo rgb 0 1 137 205 137 205 137 205 137 205 137 205 137 205 > /dev/spi_misc_test调整红光亮度

echo rgb 0 1 205 205 205 205 137 137 137 137 137 137 137 137 > /dev/spi_misc_test为红光

echo rgb 0 1 137 137 137 137 205 205 205 205 137 137 137 137 > /dev/spi_misc_test为绿光

echo rgb 0 1 137 137 137 137 137 137 137 137 205 205 205 205 > /dev/spi_misc_test为蓝光

7.知道原理其实代码很好写,比如取低八位可以改成取低4位。又或者直接写入

echo rgb 0 1 15 255 0 0 > /dev/spi_misc_test。RGB色,将255 0 0自己在代码里面做逻辑转换。

可玩性很高,你可以控制某一颗灯的颜色,又或者做呼吸灯效果/渐变效果/网上的随音量大小或者音乐律动变化的效果等等。

相关推荐
唐诺3 分钟前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨1 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客1 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin1 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos3 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室3 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0013 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我583 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc4 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很4 小时前
C++ 集合 list 使用
c++