前言:本项目旨在设计一个基于Sparkroad开发板可以通过串口配置流水灯带WB2812B硬件电路,并可以通过开关和串口实现不同图案的流水灯效果配置和调试。
整体架构图:
首先将架构分三个小模块,其分别是接收传输部分,BRAM存储单元,以及相关的读写接口部分,并最后设计流水灯灯带的驱动模块,以实现流水灯的控制驱动,并可以通过串口和开关来配置流水灯输出的图案,我们将分别介绍以下各个模块以及以及提供其具体的实现代码。
![](https://verimake.com/assets/files/2024-01-25/1706176591-60435-a6dd27ed-09eb-452f-8255-bd496fd914a2.png)
图1:整体架构图
Uart传输模块
可以将此模块分解为uart接收模块以及uart发送模块,首先我们根据uart传输协议的时序设计状态机,并将波特率设计为9600 Buad,并加入采样滤波算法,以实现更为准确的信号的采样,并在一个字节完成传输之后,发送持续一个时钟周期done信号以表明信号传输完成。
代码思路:我们可以通过检测输入序列的下降沿来实现对启动位的检测,然后利用状态机转移至接受数据状态,并进行计数和通过移位寄存器进行数据的存储,在同时接收完成8个bit位并接收到停止位后,表示一次传输结束了,在这时我们便可以将uart_done信号拉高一个时钟周期,来表示本次字节传输完成。
同时为了提升采样的准确性,我们将时钟频率调整为Baud频率的8倍,并最终与均值进行比较来判断此位的电平高低,这很大程度上提升了传输的稳定性和准确性。
![](https://verimake.com/assets/files/2024-01-25/1706177233-742817-uart.png)
图2:Uart协议示意图
接口部分:
缓冲区Buffer为了方便灯带使用我们将Bram的位宽设置成为192位,可以表示每个刷新间隔8个像素,因此我们需要将输入的8 Bit的数据缓存进入一个Buffer中,当Buffer被填满之后,我们在将Bram的写使能端拉高,将缓冲区内的数据写入Bram中对应的地址里面,这里我们设计使用移位寄存器完成串并转换的功能。
模块示意图如下图所示:
![](https://verimake.com/assets/files/2024-01-26/1706253775-514145-buffer.png)
图3:Buffer 模块电路图
BRAM 模块设计
由于接收器与流水灯发送器之间的时钟频率并不一样,这里我们可以使用简单双端口BRAM以实现跨时钟域的数据读写,写clka连接接收器时钟,clkb连接流水灯发送器的时钟,为了方便读取,我们将BRAM的位宽设置为192位,深度设置为8,来表示某一时刻的8个像素的流水灯的状态。
并将读写的模式均设置为Write First Mode,并且取消输出寄存器(少打一拍由于在低速环境下),方便数据的读取,下图是BRAM的配置方法。
![](https://verimake.com/assets/files/2024-01-26/1706254113-918110-bram.png)
图4:Bram的整体配置图
![](https://verimake.com/assets/files/2024-01-26/1706254084-690606-brama.png)
图5:BRAM的写信号A端口配置图
![](https://verimake.com/assets/files/2024-01-26/1706254124-531090-bramb.png)
图6:BRAM读信号端口B端口的配置图
下图为我们设计的符合Write First的读取数据的时序图,并通过状态机实现了下面的时序。
![](https://verimake.com/assets/files/2024-01-26/1706254588-334264-write-first.png)
图7: WRITE First 的读写时序以及信号的输出关系
利用状态机进行信号的读取,地址周期持续两个时钟周期,在第二个时钟周期的上升沿进行数据的读取,将数据读入流水灯寄存器,当流水灯寄存器被写过后,在进行地址的递增,向第二个流水灯寄存器进行数据的写入。
![](https://verimake.com/assets/files/2024-01-26/1706254654-336076-cae53a86-fce1-4e82-a3ac-513a3e9ad057.png)
图8:本项目设计的读时序
在uart传输结束信号done来到后,使读使能信号to_wea拉高两拍,实现对地址的读取,在读取过后利用to_wea信号的下降沿,实现addr地址的递增。
![](https://verimake.com/assets/files/2024-01-26/1706254713-101683-92700309-1d6a-4dc3-a9b2-e3b35a3987fd.png)
图9:本项目设计的写时序
通过对对应的读写时序设计相对应的状态机,我们便可以实现对BRAM的读取和写入操作了。
LED控制器
此模块的作用主要是内置计数器以及数据寄存器以实现,为LED发送模块提供所需驱动信号的数据,并可以实现通过开关控制实现从BRAM中读取数据的功能,将开关置为1便可以实现将数据读入数据寄存器,再通过状态机,将数据发送给LED驱动模块。
LED驱动模块
![](https://verimake.com/assets/files/2024-01-26/1706254965-222926-1.png)
图10:分别表示LED像素0码的高低电平时序分布以及1码的高低电平时序分布,以及各个像素灯之间的级联方式
![](https://verimake.com/assets/files/2024-01-26/1706254971-946608-2.png)
图11:表示了各个部分的持续时间上限值和持续时间的下限值,以及刷新RES所需要的最小时间值。
![](https://verimake.com/assets/files/2024-01-26/1706254979-226967-3.png)
图12:表示了WS2812B数据传输方式
我们首先根据WB12B的数据手册,将FPGA开发板的24Mhz的时钟进行分频,本别计算得到LED驱动的时钟的上限频率以及下限频率,并将频率设置为符合WV12B的设计时序,这里我们将原始时钟进行了8分频,约为3Mhz,一个像素具有24位的串行控制信号,分成3个8位信号来表示GRB三个颜色的亮度,其中在本系统中每个信号可以通过四个码元来表示,4‘b1100表示正逻辑1,4’b1000表示正逻辑0,根据上述的逻辑我们便可以编写出相关的状态机来实现流水灯带的控制。
![](https://verimake.com/assets/files/2024-01-26/1706255225-764498-4.png)
图13:灯带协议的示意图,此处202020代表白色
下面举一个例子,并可以通过图片展示这种编码过程,假设我们控制四个像素的序列点阵,在第一帧中,对于第一个像素灯我们希望他发亮,而其他三个像素灯均不发光,这里我们采用16进制方便表示,我们可以将”202020“序列放在第一个要发送的位置,其他3个灯均发送“000000”。对于第二帧我们希望第二个灯珠发光于是我们将“202020”作为第二个要发送的序列,我们在重置后继续发送,对于第三帧来我们可以重复上述的方式。
我简要的绘制了一个示意图表示上述的过程逻辑。再拆解一下20(十六进制)即表示0010_0000,通过之前的描述0码元可以通过4‘b1000来表示,而1码元则可以通过4’b1100来表示,这样通过这种方式我们便可以实现对任意灯带和图案的控制了。
![](https://verimake.com/assets/files/2024-01-26/1706255231-777688-5.png)
图14:使用chipwatch抓取信号验证
通过chipwatch我们可以发现,实现了对应读取地址的跳转,某种程度上验证了电路的正确性。
![](https://verimake.com/assets/files/2024-01-26/1706255237-580558-6.png)
图15:使用串口配置流水灯图案
![](https://verimake.com/assets/files/2024-01-26/1706255242-179025-7.jpg)
图16:Sparkroad的上板调试结果