【.NET 与树莓派】WS28XX 灯带的颜色渐变动画
在上一篇水文中,老周演示了 WS28XX 的基本使用。在文末老周说了本篇介绍颜色渐变动画的简单实现。
在正式开始前,说一下题外话。
第一件事,最近树莓派的价格猛涨,相信有关注的朋友都知道了。所以,如果你不是急着用,可以先别买。或者,可以选择 Raspberry Pi 400,这个配置比 4B 高一点,这个目前价格比较正常。Pi 400 就是那个藏在键盘里的树莓派。其实,官网上面的价格已经调回原来的价格了,只是某宝上的那些 Jian 商,还在涨价。
第二件事,树莓派上的应用是不是可以用 C 来写?这是废话了。树莓派上运行的是 Linux 系统,当然可以了。有伙伴会说,用.NET体验如何?老周可以告诉你:完全没问题,这个库大部分API老周都做过实验。.net Iot 库的性能你不用担心,因为最近几年.NET的性能提升很大,更何况.NET只是封装了底层API的调用,当指令传递到系统驱动层,其效率和 C 是一样的。你不妨想想,连 Python 这种性能差得没有天敌的编程语言都能玩物联网,.NET 你还怕啥呢。尽管目前开源的库不多,但官方给的 Devices 也基本覆盖各种传感器模块。
-------------------------------------------------------------------------------------
好了,F话就聊到这儿,接下来正片开始。
让WS28XX 控制灯带产生动画,其本质上就是每隔一段时间更新一下每个灯珠的颜色。由于人眼的反应速度和处理能力比不上猫,所以我们会看到动画。咱们看到的是动画,但老周估计喵喵们看到的是PPT。
所以,所谓颜色渐变动画,首先,你要确定两种颜色——起始色和最终色,比如从绿色变成红色,绿色是起始,红色是终点。
然后,我们要算出起始色与终点色之间,R、G、B 各值间的差值。
假设,我们的延时 d = 40 ms(精确到毫秒就够,不用考虑微秒纳秒,反正你眼睛看不到),然后咱们要从红色变成蓝色。
红:R=255, G=0, B=0
蓝:R=0, G=0, B=255
计算差距,终点减去起点,不管正负。
dif_R = 0-255 = -255
dif_G = 0-0 = 0
dif_B = 255-0 = 255
这样我们就看到,从红到蓝,R的值是递减的,G不变,B的值是递增的。我们先不去想算法对不对,不妨继续推算:
第一轮循环,R=255-1=254, G=0,B=0+1=1,Sleep 40;
第二轮循环,R=255-2=253,G=0,B=0+2=2,Sleep 40;
第三轮循环,R=255-3=252,G=0,B=0+3=3,Sleep 40
……
直到把目标值变成 R=0,G=0,B=255。每一轮循环之间,会暂停 40 ms。
可是,算法还真不能这么简单,咱们忽略了一个问题,请看下面的举例:
假设要从 R=120,G=200,B=10 变成 R=255,G=100,B=60
计算差值:difR = 255-120=135,difG=100-200=-100,difB=60-10=50。RGB之间的差值并不相等,如果我们每轮循环都 +1 或 -1,那么会存在一个问题:有的值可能早已到达终值,而有的值还没到达终值。这种情况灯光的渐变过程会看起来不太顺畅。
所以,我们必须解决的问题就是要在 N 轮循环之后,RGB三个值要同时到达终值。这么一来,差值大的要渐变得快一些,差值小的要渐变得慢一些。跑得快的等一下跑得慢的,形成统一战线,同时到达终点。
因此,渐变过程中循环的次数必须统一,但每次循环里面,RGB改变的量不同,但N轮循环过后会同时到达终值。
举例,从 R1=100,G1=0,B1=230 变为 R2=20,G2=72,B2=57
那么,差值:
dR = 20-100=-80
dG = 72-0=72
dB = 57-230=-173
假如循环次数为80次,可以理解为分 80 个步长来完成,设 step = 80。接下来就得算出这80步中,每一步里RGB各值要变化多少(单位步长)。
pR = dR / 80=-80/80 = -1
pG = dG / 80 = 72 / 80 = 0.9
pB = dB / 80 = -173 / 80 = -2.16
再设某一轮循环(某一步)为 i ,于是
for i = 0; i <= 80; i++
R = R1 + i * -1;
G = G1 + i * 0.9;
B = B1 + i * -2.16;
R1、G1、B1 指的是起始颜色的值,在一次循环中,让初始值加上 i 与单位步长(pR、pG、pB)的乘积。
这么一搞,就能保证在 N 个循环后,三个值能同时到达终值。
------------------------------------------------------------------------------------------
OK,有了上面的推演过程,我们可以把它翻译成代码。我直接封装为一个类。
public class GradLeds
{
Ws28xx _leds;
public GradLeds(Ws28xx ws) => _leds = ws; public void Run(Color start, Color end, int steps = 120, int delay_ms = 30)
{
if (steps <= 0)
throw new Exception("steps 不能小于/等于0");
if (delay_ms <= 0)
throw new Exception("延时必须大于0"); // 计算RGB的差值,不论正负
float dR = (float)end.R - start.R;
float dG = (float)end.G - start.G;
float dB = (float)end.B - start.B;
// 计算每一个步长(step)要增长的值
float ir = dR / steps;
float ig = dG / steps;
float ib = dB / steps; // 通过宽度获取灯珠数
int ledNum = _leds.Image.Width;
for (var a = 0; a <= steps; a++)
{
// 如果运行状态为false,退出循环
if(AppContext.TryGetSwitch("running",out bool b) && !b)
{
break;
}
Color tc = Color.FromArgb(
(int)(start.R + a * ir),
(int)(start.G + a * ig),
(int)(start.B + a * ib)
);
// 填充所有灯珠
for (var n = 0; n < ledNum; n++)
{
_leds.Image.SetPixel(n, 0, tc);
}
_leds.Update();
// 延时
Thread.Sleep(delay_ms);
}
}
}
在这个类中,我用到了 AppContext,如果你看过老周在几千年前写的博文,应该会记得这个 AppContext类,它可以用来设置一些全局开关,开关名是字符串,值是布尔值。直接用这个类,我们不需要刻意去写个类,再弄个静态字段来当全局变量了,更何况静态成员是不能跨 AppDomain 共享值的,如果多线程还得考虑同步。
在 AppContext 中老周会设置一个开关,名为 running,如果是 true,说明程序在运行;若为 false,则说明程序要退出了,就不会再渐变了。
因为这个渐变过程会持续几秒时间甚至更长,如果程序要退出,就不要再循环了,而是赶紧终止操作。
start 和 end 表示起始颜色和终点颜色,steps 表示要进行多少步(循环数),delay_ms 参数表示每一轮循环之间的延时。
回到主程序,调用测试。
using System.Device.Spi;
using Iot.Device.Ws28xx;
using Grdtest;
using System.Drawing; // 初始化SPI总线
SpiConnectionSettings settings = new(0)
{
Mode = SpiMode.Mode0,
DataBitLength = 8,
ClockFrequency = 2400_000
};
using SpiDevice device = SpiDevice.Create(settings); // WS28XX,30个灯珠
Ws28xx ws = new Ws2812b(device, 30);
GradLeds grdled = new(ws); int steps = 90; //90个循环
int delay = 25; //延时(毫秒)
// 设置运行状态
AppContext.SetSwitch("running", true); // 按Ctrl+C时程序要退出,处理一下
Console.CancelKeyPress += async (_, e) =>
{
e.Cancel = true; //阻上程序马上退出
// 关闭开关,表示程序不再运行了
AppContext.SetSwitch("running", false);
await Task.Delay(150); //保险一点,等一会儿
e.Cancel = false; //告诉系统,可以退出了
}; // 主循环
while (AppContext.TryGetSwitch("running", out bool b) && b)
{
// 从红变蓝
grdled.Run(Color.Red, Color.Blue, steps, delay);
// 从蓝变黄
grdled.Run(Color.Blue, Color.Yellow, steps, delay);
// 从黄变深粉色
grdled.Run(Color.Yellow, Color.DeepPink, steps, delay);
// 从深粉色变白色
grdled.Run(Color.DeepPink, Color.White, steps, delay);
// 从白变回红
grdled.Run(Color.White, Color.Red, steps, delay);
} // 黑灯收工
ws.Image.Clear(Color.Black);
ws.Update();
最后这两句是当退出 while 循环后,让所有灯珠熄灯(黑色表示灯灭)。
ws.Image.Clear(Color.Black);
ws.Update();
好了,咱们来看看效果,这个效果应该能接受。
其他动画算法,大伙伴们不妨自己动手去试试。算法不一定要从网上抄,可以根据自己的理解去设计。可以做出自己的创意,你爱咋玩就咋玩。
【.NET 与树莓派】WS28XX 灯带的颜色渐变动画的更多相关文章
- 平行四边形导航,背景颜色渐变动画(不支持IE6/7/8)
body{ font-size: 14px; } ul ,li{ margin:0px; padding:0px; list-style: none; } .box{ width: 1000px; h ...
- 【.NET 与树莓派】控制彩色灯带(WS28XX)
彩色灯带,相信不用老周多说,大家都知道,没准你家里的灯墙里面就有.老周的茅屋是早期建造的,所以没有预留的灯槽,明灯的话是不好看的,因此老周家里没使用灯带.不过,像柜子后面,显示器后面,书桌边沿这些地方 ...
- Homekit_Dohome_智能灯带
简介:本款产品支持音乐律动控制,可以随音乐改换颜色及频率,可以使用Homekit或者Dohome或者遥控器进行有效控制,同时Dohome App已经对接了各大智能音箱,下载Dohome App后就可以 ...
- RBG灯颜色渐变(颜色要尽可能多)程序分析
相信很多调过RBG灯的朋友都是通过分别改变R.B.G的占空比来改变颜色的,但是不是发现了一个问题,那就是不管怎样调都很难实现几十种颜色的变化,一般只有是7种颜色的渐变.下面给朋友们分享一个可以实现几十 ...
- 用树莓派实现RGB LED的颜色控制——C语言版本号
用树莓派实现RGB LED的颜色控制 RGB色彩模式是工业界的一种颜色标准.是通过对红(R).绿(G).蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代 表红.绿 ...
- IOS导航栏颜色渐变与常用属性
(转:http://www.cnblogs.com/Lingchen-start/archive/2015/10/23/4904361.html) 今年很忙,忙的写日志的时间都很少. 少的可怜. 自 ...
- 仿饿了么增加购物车旋转控件 - 自带闪转腾挪动画 的button
本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/54235736 本文出 ...
- iOS 动画绘制线条颜色渐变的折线图
效果图 .................... 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图.折线图可以更加直观的表示数据的变化.网络上有很多绘制折线图的demo,有 ...
- CSS3颜色渐变模式
1.线性渐变:linear-gradient 语法:<linear-gradient> = linear-gradient([ [ <angle> | to <si ...
随机推荐
- 菜鸡的Java笔记 数据表与简单java类映射
利用实际的数据表实现表与类的操作转换 简单java类是整个项目开发中的灵魂所在,它有自己严格的开发标准,而最为重要的是它需要于数据表是完全对应的 不过考虑到现在没有接触到过 ...
- Node http
要开发HTTP服务器程序,从头处理TCP连接,解析HTTP是不现实的.这些工作实际上已经由Node.js自带的http模块完成了.应用程序并不直接和HTTP协议打交道,而是操作http模块提供的req ...
- Node.js实现前后端交换——用户登陆
最近学习了一点Node.js的后端知识,于是作为一个学习前端方向的我开始了解后端,话不多说,开始介绍.首先,如果你想要更好的理解这篇博客,你需要具备html,css,javascript和Node.j ...
- [cf1103E]Radix sum
类似于uoj272,即$B=10$的情况,然后有以下几个细节问题: 1.答案对$2^{58}$取模可以先使用自然溢出模$2^{64}$,最后对$2^{58}$取模即可 2.为了避免实数,令$\omeg ...
- [cf1217F]Forced Online Queries Problem
可以用并查集维护连通性,删除可以用按置合并并查集,但删掉一条边后无法再维护两点的联通性了(因为产生环的边是不加入的)暴力思路是, 考虑前i个操作后边的集合,暴力加入即可,但复杂度是$o(n^2)$的用 ...
- js--迭代器总结
前言 我们已经熟练使用set.map.array几种集合类型了,掌握了map(),for..of..,filter()等迭代集合的方法,你是否思考过,js引擎是怎么迭代的,怎么判断迭代是否结束,本文来 ...
- No 'Access-Control-Allow-Origin' header: 跨域问题踩坑记录
前言 前两周在服务器上部署一个系统时,遇到了跨域问题,这也不是第一次遇到跨域问题了,本来以为解决起来会很顺利,没想到解决过程中遇到了很多坑,所以觉得有必要写一篇博客记录一下这个坑. 问题产生原因 本来 ...
- SpringSecurity过滤器原理
SpringSecurity原理 主要过滤器链 SpringSecurity的功能主要是由一系列的过滤器链相互配合完成的.验证一个过滤器之后放行到下一个过滤器链,然后到最后. 认证流程 过滤器作用 S ...
- Atcoder Grand Contest 020 E - Encoding Subsets(记忆化搜索+复杂度分析)
Atcoder 题面传送门 & 洛谷题面传送门 首先先考虑如果没有什么子集的限制怎样计算方案数.明显就是一个区间 \(dp\),这个恰好一年前就做过类似的题目了.我们设 \(f_{l,r}\) ...
- curl实现SFTP上传下载文件
摘自:https://blog.csdn.net/swj9099/article/details/85292444 #include <stdio.h> #include <stdl ...