当前位置: 首页 > article >正文

[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配置:

# CONFIG_SPI_PXA2XX is not set
+CONFIG_SPI_ROCKCHIP=y
+CONFIG_SPI_ROCKCHIP_TEST=y
# CONFIG_SPI_SC18IS602 is not set

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

&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默认的配置

	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改为我们需要控制的口

		/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开放了操作节点便于操作

		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部分的逻辑:

	} 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的第三个参数传输长度,在不带文件名的情况时,走

			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
也就是说用

			for (i = 0; i < size; i++){
				txbuf[i] = i % 256;
			}

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

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

	} 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自己在代码里面做逻辑转换。
可玩性很高,你可以控制某一颗灯的颜色,又或者做呼吸灯效果/渐变效果/网上的随音量大小或者音乐律动变化的效果等等。


http://www.kler.cn/news/365560.html

相关文章:

  • R5:天气预测-探索式数据分析
  • 微信网页授权回调地址放多个参数的方法
  • upload靶场sql靶场一点点见解
  • 0基础学java之Day14
  • 文件上传漏洞及安全
  • Golang | Leetcode Golang题解之第507题完美数
  • 【云原生】Kubernets1.29部署StorageClass-NFS作为存储类,动态创建pvc(已存在NFS服务端)
  • Windows安装PM2 注意事项与错误查改
  • masm32下载及安装
  • TCP simultaneous open测试
  • 【多Agent协作论文解读】采用STORM模式更好利用LLM撰写长文章,基于Dify复现
  • Unity3D学习FPS游戏(3)玩家第一人称视角转动和移动
  • 通过rancher2.7管理k8s1.24及1.24以上版本的k8s集群
  • Jenkins和Gitlab整合构建CI/CD流水线
  • windows DLL技术-DLL加载器的重定向
  • Springboot 使用【过滤器】实现在请求到达 Controller 之前修改请求体参数和在结果返回之前修改响应体
  • 使用Panther进行爬虫时,如何优雅地处理登录和Cookies?
  • 2024爱分析·数据库厂商全景报告|爱分析报告
  • 每日OJ题_牛客_NC383主持人调度(一)_排序​_C++_Java
  • CentOS配置iptables规则并使其永久生效
  • 时间序列预测(十)——长短期记忆网络(LSTM)
  • scala trait多继承知识
  • 使用 xlrd 和 xlwt 库进行 Excel 文件操作
  • 【工具】新手礼包之git相关环境包括中文的一套流程{收集和整理},gitlab的使用
  • 【智能大数据分析 | 实验四】Spark实验:Spark Streaming
  • Python编程技巧:字符串排列组合与重复数字查找