目录
1 WS2812B
1.1 简介
AWC_C4 FPGA开发套件上板载8行8列共64个WS2812灯珠,构成一个8*8的LED点阵,主要用于静态或动态显示彩色数字、字符以及特效等。该部分电路由64个RGB三色发光二极管采用串行级联的方式组成,仅通过一根信号线即可完成数据的接收与 解码。RGB三色发光二极管采用的是WORLDSEMI公司的发光二极管,型号为WS2812C-2020。此外,由于信 号线IO电平标准为3.3V,为匹配RGB三色发光二极管的驱动电平要求(5V),采用TI公司的单位双电源 总线收发器芯片进行配置电压转换,型号为SN74LVC1T45DBVR。
1.2 WS2812B原理
1.2.1 WS2812B内部结构
WS2812 是将控制电路与发光电路集于一体的智能外控 LED 光源,外观和 5050 LED 灯珠一样,并且每个元件就是一个像素点。其像素点内部包含了智能数字接口、数据锁存、信号整形放大驱动电路,还有高精度的内部振荡器以及可编程定电流控制部分。这些内部电路模块协同工作,能有效确保像素点光的颜色高度一致,使得最终呈现出的灯光色彩效果更加均匀、准确。
1.2.2 数据传输方式
数据量与点亮方式:每个灯珠需要 24bit 数据来点亮,这意味着要精确控制每个灯珠所发出的颜色等显示状态,就需要向其准确传输特定的 24bit 数据组合。
数据传输协议:采用单线归零码的通讯方式。在像素点上电复位后,从控制器经 DIN 端传输过来的数据开始起作用。首先传输过来的 24bit 数据会被第一个像素点提取,然后送入其内部的数据锁存器,以确定该像素点的显示状态。而剩余的数据会经过内部整形处理电路进行整形放大,再通过 DO 端口转发输出给下一个级联的像素点,并且每经过一个像素点传输,信号就会相应减少 24bit,按照这样的规则依次向后传递,最终实现对整个串行连接的多个灯珠的分别控制,从而让整个 8×8 的 LED 点阵能够按预期显示出各种灯光图案或色彩组合等效果。
像素点采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅受 限信号传输速度要求。高达2KHz的端口扫描频率, 在高清摄像头的捕捉下都不会出现闪 烁现象,非常适合高速移动产品的使用。280μs以上的RESET时间,所以即使出现中断 也不会引起误复位,可以支持更低频率、价格便宜的MCU。LED具有低电压驱动、环保节 能、亮度高、散射角度大、一致性好、超低功率及超长寿命等优点。将控制电路集成于 LED 上面,电路变得更加简单,体积小。每个LED灯珠有4个引脚,见下表。
FPGA 驱动WS2812 灯珠点亮,是通过DIN引脚以串行的方式输入单bit数据(0或1), 需要遵循WS2812的通信时序,每驱动一个灯珠FPGA需要发送24bit(GRB888格式)数据, 每一bit数据都是由一段时间高电平和一段时间低电平组成,如下图所示。
然后一个点阵由多少个灯珠构成,那每次刷新就需要传输多少个24bit数据,并且在 每两次刷新之间需要280us的复位时间,复位状态下输入为低电平0即可;需要注意的是, 每个灯珠对应的数据需要按照如下图所示的顺序发送,按照G、B、R从高位到低位发送。
2 基于8*8图像显示
2.1 思路
WS2812B是一种常见的RGB LED灯带,每个灯珠内部都有一个芯片控制,通过发送特定的时序数据来控制其亮灭。发送数据时,需要按照一定的时序发送24位RGB数据,其中高位在前低位在后,格式为GRB。并且1个LED需要24bit数据,其复位时间需要280us以上(8*8矩阵需要64led:64*24)。数据采用单总线,归零码的通讯方式;一个比特数据时,逻辑 0对应的高电平时间约为(400)220-380,低电平时间约为(800)580-1000;逻辑 1 对应的高电平时间约为(800)580-1000,低电平时间约为(400)220-380。1bit传输时钟时间大概就是1200=T_H+T_L;
/**************************************************************
@File : ws2812b_driver.v
@Time : 2024/12/09 19:49:10
@Author : 劉劉
@EditTool: VS Code
@Font : UTF-8
@Function: WS2812B驱动模块(WS2812B属于单工)[GRB]
WS2812B 只有一个数据输入引脚 DI
**************************************************************/
module ws2812b_driver(
input clk ,
input rst_n ,
output reg dq
);
parameter TIME_0_H = 400/20,//220—380
TIME_0_L = 800/20,//580—1000
TIME_1_H = 800/20,//580—1000
TIME_1_L = 400/20,//220—380
TIME_1BIT = 1200/20,//0_H+0_L与1_H+1_L:1200
TIME_RES = 300_000_000/20;//RES > 280us 1张图像到下一张图像的延时
parameter IDLE = 3'b001,
DATA = 3'b010,
DONE = 3'b100;
wire idle_to_data;
wire data_to_done;
wire done_to_idle;
reg [2:0] steat_c,steat_n;
reg [6:0] cnt_64;
wire add_64_cnt,end_64_cnt;
reg [4:0] cnt_24;
wire add_24_cnt,end_24_cnt;
reg [$clog2(TIME_1BIT)-1:0] cnt_1_bit;
wire add_1_bit_cnt,end_1_bit_cnt;
reg [1:0] cnt_picture;
wire add_picture_cnt,end_picture_cnt;
reg [$clog2(TIME_RES)-1:0] cnt;
wire add_cnt,end_cnt;
reg [23:0] data_1;
reg [23:0] data_2;
wire [23:0] data [191:0]; //寄存器组 192个24位宽的寄存器
/**************************************************************
状态机
**************************************************************/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
steat_c <= IDLE;
else
steat_c <= steat_n;
end
always @(*) begin
case (steat_c)
IDLE :if(idle_to_data)
steat_n = DATA;
else
steat_n = steat_c;
DATA :if(data_to_done)
steat_n = DONE;
else
steat_n = steat_c;
DONE :if(done_to_idle)
steat_n = IDLE;
else
steat_n = steat_c;
default : steat_n = IDLE;
endcase
end
assign idle_to_data = (steat_c == IDLE) && rst_n;
assign data_to_done = (steat_c == DATA) && end_64_cnt;
assign done_to_idle = (steat_c == DONE) && end_cnt;
/**************************************************************
计数器:64=8*8 led
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_64 <= 'd0;
else if(add_64_cnt) begin
if(end_64_cnt)
cnt_64 <= 'd0;
else
cnt_64 <= cnt_64 + 1'b1;
end
assign add_64_cnt = end_24_cnt ;
assign end_64_cnt = add_64_cnt && cnt_64 == 64 - 1 ;
/**************************************************************
计数器:1个led灯亮24bit计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_24 <= 'd0;
else if(add_24_cnt) begin
if(end_24_cnt)
cnt_24 <= 'd0;
else
cnt_24 <= cnt_24 + 1'b1;
end
assign add_24_cnt = end_1_bit_cnt ;
assign end_24_cnt = add_24_cnt && cnt_24 == 24 - 1 ;
/**************************************************************
计数器:1bit发送的时间
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_1_bit <= 'd0;
else if(add_1_bit_cnt) begin
if(end_1_bit_cnt)
cnt_1_bit <= 'd0;
else
cnt_1_bit <= cnt_1_bit + 1'b1;
end
assign add_1_bit_cnt = steat_c == DATA ;
assign end_1_bit_cnt = add_1_bit_cnt && cnt_1_bit == TIME_1BIT - 1 ;
/**************************************************************
计数器:传输多少个图像
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_picture <= 'd0;
else if(add_picture_cnt) begin
if(end_picture_cnt)
cnt_picture <= 'd0;
else
cnt_picture <= cnt_picture + 1'b1;
end
assign add_picture_cnt = end_cnt ;
assign end_picture_cnt = add_picture_cnt && cnt_picture == 3-1 ;
/**************************************************************
计数器:发一帧图像的回复复位时间
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 'd0;
else if(add_cnt) begin
if(end_cnt)
cnt <= 'd0;
else
cnt <= cnt + 1'b1;
end
assign add_cnt = steat_c == DONE ;
assign end_cnt = add_cnt && cnt == TIME_RES - 1 ;
/**************************************************************
每一bit的高电平 或 低电平持续时间判断
**************************************************************/
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
dq <= 'b0;
else begin
case (steat_c)
DATA : if(data_2 == 1)begin
if(cnt_1_bit <= 30)
dq <= 1'b1;
else
dq <= 1'b0;
end
else begin