Adafruit WS2812B NeoPixels 与 Freescale FRDM-K64F 板

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

这是迷你系列的 第 3 部分 。在第 2 部分中,我描述了如何设置开发工具和调试第一个项目(参见“ 教程:Adafruit WS2812B NeoPixels 与 Freescale FRDM-K64F 板 - 第 2 部分:软件工具 ”)。现在是研究软件概念的时候了。目标是使用 Freescale FRDM-K64F 板驱动 Adafruit 的 NeoPixel (WS2812B):

Adafruit 8×8 NeoPixel Shield 与 Freescale FRDM-K64F 板


迷你系列教程列表

大纲

本文介绍了与 WS2812B(或 Adafruit NeoPixel)LED 对话的协议和时序。它解释了一种使用定时器和 DMA 来满足硬件要求的方法。我将 GNU ARM Embedded(launchpad)工具与基于 Eclipse 的 IDE(Freescale Kinetis Design Studio v3.0.0)一起使用。作为软件驱动程序,我使用的是 Freescale Kinetis SDK v1.2。

WS2812 链接

WS2812(B) LED 具有带移位寄存器的内置电流驱动器:将数据写入移位寄存器将锁存位,恒流驱动器将电流“驱动”到 LED 的红色、绿色和蓝色部分引领。

数据写入单线(DIN 引脚)链中的第一个 WS2812。第一个设备将“保留”前 24 位,其他所有内容都移出到 DOUT 引脚。因此,为了向 2 个 LED 提供数据,我移动了 2x24 位:前 24 位用于链中的第一个设备,接下来的 24 位用于链中的下一个设备,依此类推。

WS2812 LED链(来源: https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/


WS2812颜色信息

每个 LED 需要 24 位,用 8 位编码绿色、红色和蓝色。所以是GRB,不是RGB!每个颜色 8bit 决定该颜色的亮度,从 0x00(关闭)到 0xff(全亮度)。这些位首先发送 MSB(最高有效位)。

要以全亮度打开 LED 的红色部分,同时关闭蓝色和绿色,我必须发送以下位:


 0 0 0 0 0 0 0 0  1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0

要让所有 (GRB) LED 的亮度减半,我发送


 0 0 0 0 0 0 0 0  1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0

WS2812时序

WS2812/B 需要这些位的特殊时序。我强烈推荐阅读 Tim 关于 WS2812 时序的文章(“ Light_WS2812 library V2.0 – Part I: Understanding the WS2812 ”):

WS2812时序(来源: https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/


WS2812B 是永远不变的,并且时序略有不同。较新的 Adafruit NeoPixels 都是 WS2812B。由于时序有一些公差,我使用以下“0”和“1”位的时序来驱动它:

WS2812B时序






生成比特流

有几种可能的方式来生成这样的比特流。一种方法是使用具有 Bit-Banging 的 GPIO(通用 I/O)引脚(通过正常的 GPIO 端口操作切换输出引脚):


 0 0 0 0 0 0 0 0  1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0


一个甚至改进的版本是避免检查位:


 0 0 0 0 0 0 0 0  1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0


写入端口位以生成信号


现在将所有位放入一个数组中,然后像这样将其流出:


 0 0 0 0 0 0 0 0  1 1 1 1 1 1 1 1  0 0 0 0 0 0 0 0


但是信号频率是800 kHz!所以 Bit-Banging 不会工作,除非你使用手工制作的汇编代码来达到速度和时间。此外,我还必须禁用中断,因为任何中断也会搞砸时间!关闭一些 LED 的中断会起作用,但 LED 链越长,中断必须关闭的时间就越长。绝对不够好。

使用定时器

与其在循环中完成所有事情,为什么不使用定时器中断呢?基本上设置三个定时器中断,每个中断以 800 kHz 的频率触发,并且定时偏移以匹配 WS2812 定时。所以我可以使用一个定时器和三个通道:在相应的中断中,我可以写入 DATA 以生成位时序:

使用定时器中断驱动位


现在这比在循环中做事要好得多。这仍然意味着中断以 3×800 kHz 的频率发生,或者每 ~0.35 µs 发生一次中断!哎呀!我的系统几乎一直忙于中断:对于每个中断,CPU 都需要堆栈寄存器,将连接更改为中断服务例程,在例程中对 DATA 寄存器执行操作并返回原来的语境。很多操作!

使用 DMA

因此,如果切换一个引脚太慢,使用中断会产生太多中断,为什么不找到一种让它更快的方法呢?对此的解决方案是 DMA(直接内存访问) 。 DMA 允许在不使用 CPU 的情况下进行 todo 操作。 DMA 允许我读/写内存(以及更多),就像一种协处理器。

我还在用那个定时器。但是我会在后台触发“直接内存操作”,而不是触发中断,要求将我的数据直接写入(内存映射的)DATA 寄存器:

使用 DMA 驱动位


这样操作就可以在后台进行,无需 CPU 参与 :-) 。好消息是:许多现代微控制器都具有 DMA 功能,并且 FRDM-K64F 板上的 Kinetis K64F 也具有它 :-)

概括

在这篇文章中,我解释了生成正确的比特流以驱动 WS2812B NeoPixels 背后的概念。由于信号是高频信号,我必须使用带 DMA 的定时器,否则 CPU 会负载过多。

在下一篇文章中,我将使用 Kinetis SDK 实现定时器。敬请期待……

链接