[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自己在代码里面做逻辑转换。

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

相关推荐
郝学胜-神的一滴9 分钟前
罗德里格斯旋转公式(Rodrigues‘ Rotation Formula)完整推导
c++·unity·godot·图形渲染·three.js·unreal
lzh2004091917 分钟前
深入理解进程:从PCB内核结构到写时拷贝的底层实战
linux·c++
aseity42 分钟前
跨平台项目中QString 与 非Qt 跨平台动态库在字符集上的一个实用的互操作约定.
c++·经验分享
CN-Dust1 小时前
【C++】while语句例题专题
数据结构·c++·算法
用户805533698031 小时前
现代Qt开发教程(新手篇)1.11——定时器
c++·qt
澈2071 小时前
STL迭代器:容器遍历的万能钥匙
开发语言·c++
azoo1 小时前
emplace_back和push_back() 函数添加 cv::Point 类型数据
c++·opencv
样例过了就是过了2 小时前
LeetCode热题 不同路径
c++·算法·leetcode·动态规划
橙子也要努力变强3 小时前
信号的保存、阻塞与递达
linux·服务器·c++