一、ws2812b概述
WS2812B是一种常见的RGB LED灯带,每个灯珠内部都有一个芯片控制,通过发送特定的时序数据来控制其亮灭。发送数据时,需要按照一定的时序发送24位RGB数据,其中高位在前低位在后,格式为GRB。发送数据时,需要注意不仅仅是发送高电平或低电平,而是要发送占空比不同的PWM波,比如给予一定的高电平和低电平时间。重置码是发送一个持续280us的低电平信号。可以先发送一组24位的数据,然后接一个重置信号表示一组结束。数据采用单总线,归零码的通讯方式, 每个像素点接收24bit数据,溢出的数据传输给下一个像素点
WS2812B作为一种常见的RGB LED(三色LED)驱动芯片,具有以下优点和缺点:
优点:
- 集成度高:WS2812B芯片将RGB LED和控制电路集成在一个小型封装中,简化了设计和布线过程。
- 控制灵活:每个WS2812B芯片都具有独立的控制电路,可以通过单线串联多个LED,并根据需要独立控制每个LED的颜色和亮度。
- 易于编程:WS2812B芯片与微控制器通信采用简单的串行通信协议,使得编程和控制相对容易。
- 色彩丰富:每个WS2812B芯片都能够显示256级亮度的红、绿、蓝三个颜色通道,通过调整三个颜色的亮度可以实现丰富多彩的效果。
缺点:
- 电源需求高:WS2812B芯片对电源的要求较高,供电电源需要稳定而强大,以保证每个芯片的正常工作。
- 刷新频率限制:由于通信协议的限制,WS2812B的刷新频率相对较低,控制大量LED时可能会出现刷新延迟。
- 单点故障影响范围大:因为WS2812B芯片串联连接,当某个芯片发生故障时,可能会影响整个串联链路的正常工作。
需要注意的是,以上是对WS2812B芯片本身的优缺点进行的讨论,实际使用中还需要根据具体应用场景和需求综合考虑。
二、ws2812b数据传输特性
三、ws2812b模块划分
ws2812b 的设计分为两个部分,一部分为ws2812b_control模块,另一部分为ws2812b_drive模块。程序设计主要通过状态机实现,系统框架图如下:
四、IP设计
4.1 ROM IP
ROM在本次工程的主要作用是将要显示的图像数据进行存储,在ws2812b_control模块的配合下输出像素数据,假设 输入图像如下:
一个像素大小为8*32的图像,IP配置如下,当然,也可以根据图像的实际大小对ROM的参数大小进行修改:
4.2 fifo IP
为了避免数据在各模块的传输过程中存在丢失,fifo主要是在ws2812b_drive模块对输入的数据进行缓存(数据缓冲),配置如下:
五、ws2812b_control模块
5.1 状态跳转
该模块只有简单的两个状态,
IDLE:空闲状态,无数据传输,输出高电平,当pix_data_vld信号到来时跳转到DATA;
DATA:数据位,通过计数器产生读取ROM的特定地址,从而读出数据输出,假如数据传输计数结束,跳转到IDLE;
5.2 设计文件
5.2.1 静态显示
/**************************************************************
@Function: 提供64个RGB像素数据,给到ws2812接口模块,用于验证接口模块
**************************************************************/
module ws2812_control(
input clk ,
input rst_n ,
output [23:0] pix_data ,
output pix_data_vld ,
input ready //可以接收图像数据了
);
parameter IDLE = 0,
DATA = 1,
DONE = 2;
reg [2:0] state ;
reg [5:0] cnt_x;
wire add_x_cnt,end_x_cnt;
reg [4:0] cnt_y;
wire add_y_cnt,end_y_cnt;
wire rom_rd_req;
wire rom_rd_data_vld;
reg rom_rd_req_r1 ;
reg rom_rd_req_r2 ;
// localparam RED = 24'hFF0000, //红色
// ORANGE = 24'hFF8000, //橙色
// YELLOW = 24'hFFFF00, //黄色
// GREEN = 24'h00FF00, //绿色
// CYAN = 24'h00FFFF, //青色
// BLUE = 24'h0000FF, //蓝色
// PURPPLE = 24'h8000FF, //紫色
// BLACK = 24'h000000, //黑色
// WHITE = 24'hFFFFFF, //白色
// GRAY = 24'hC0C0C0; //灰色
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(ready)
state <=DATA;
DATA : if(end_y_cnt)
state <=DONE;
default : state <= IDLE;
endcase
/**************************************************************
图像数据个数计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_x <= 'd0;
else if(add_x_cnt) begin
if(end_x_cnt)
cnt_x <= 'd0;
else
cnt_x <= cnt_x + 1'b1;
end
assign add_x_cnt = state == DATA;
assign end_x_cnt = add_x_cnt && cnt_x == 8 - 1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_y <= 'd0;
else if(add_y_cnt) begin
if(end_y_cnt)
cnt_y <= 'd0;
else
cnt_y <= cnt_y + 1'b1;
end
assign add_y_cnt = end_x_cnt;
assign end_y_cnt = add_y_cnt && cnt_y == 8 - 1;
/*
always@(*)
case(cnt_y)
0 : pix_data = RED ;
1 : pix_data = ORANGE ;
2 : pix_data = YELLOW ;
3 : pix_data = GREEN ;
4 : pix_data = CYAN ;
5 : pix_data = BLUE ;
6 : pix_data = PURPPLE ;
7 : pix_data = GRAY ;
default : pix_data = RED ;
endcase */
assign rom_rd_req = state == DATA ;
rom rom_inst (
.aclr ( ~rst_n ),
.address ( cnt_x + cnt_y*8 ),
.clock ( clk ),
.rden ( rom_rd_req ),
.q ( pix_data )
);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rom_rd_req_r1 <= 0;
rom_rd_req_r2 <= 0;
end
else begin
rom_rd_req_r1 <= rom_rd_req;
rom_rd_req_r2 <= rom_rd_req_r1;
end
end
assign rom_rd_data_vld = rom_rd_req_r2;
assign pix_data_vld = rom_rd_data_vld;
endmodule
5.2.1 动态显示
module ws2812_control2(
input clk ,
input rst_n ,
output [23:0] pix_data ,
output pix_data_vld ,
input ready //可以接收图像数据了
);
parameter IDLE = 0,
DATA = 1,
DONE = 2,
DELAY = 3;
parameter DLEAT_MAX = 1500_0000;
reg [2:0] state;
reg [5:0] cnt_x;
wire add_x_cnt,end_x_cnt;
reg [4:0] cnt_y;
wire add_y_cnt,end_y_cnt;
reg [6:0] cnt_offset;
wire add_cnt_offset,end_cnt_offset;
reg [24:0] delay_cnt;
wire add_delay_cnt,end_delay_cnt ;
wire rom_rd_req ;
wire rom_rd_data_vld ;
reg rom_rd_req_r1 ;
reg rom_rd_req_r2 ;
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(ready)
state <=DATA;
DATA : if(end_y_cnt)
state <=DELAY;
DELAY : if (end_delay_cnt) begin
state <= IDLE;
end
default : state <= IDLE;
endcase
//****************************************************************
//-- 帧间隔
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
delay_cnt <= 'd0;
end
else if(add_delay_cnt)begin
if(end_delay_cnt)begin
delay_cnt <= 'd0;
end
else begin
delay_cnt <= delay_cnt + 1'b1;
end
end
end
assign add_delay_cnt = state == DELAY;
assign end_delay_cnt = add_delay_cnt && delay_cnt == DLEAT_MAX - 1;
/**************************************************************
图像数据个数计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_x <= 'd0;
else if(add_x_cnt) begin
if(end_x_cnt)
cnt_x <= 'd0;
else
cnt_x <= cnt_x + 1'b1;
end
assign add_x_cnt = state == DATA;
assign end_x_cnt = add_x_cnt && cnt_x == 8 - 1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_y <= 'd0;
else if(add_y_cnt) begin
if(end_y_cnt)
cnt_y <= 'd0;
else
cnt_y <= cnt_y + 1'b1;
end
assign add_y_cnt = end_x_cnt;
assign end_y_cnt = add_y_cnt && cnt_y == 8 - 1;
//偏移计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_offset <= 'd0;
end
else if(add_cnt_offset)begin
if(end_cnt_offset)begin
cnt_offset <= 'd0;
end
else begin
cnt_offset <= cnt_offset + 1'b1;
end
end
end
assign add_cnt_offset = end_delay_cnt;
assign end_cnt_offset = add_cnt_offset && cnt_offset == 32 - 1;
wire [4:0] real_row;// 0~32
assign real_row = cnt_x + cnt_offset;
//****************************************************************
//--rom
//****************************************************************
assign rom_rd_req = state == DATA ;
rom rom_inst (
.aclr ( ~rst_n ),
.address ( real_row + cnt_y*32 ),
.clock ( clk ),
.rden ( rom_rd_req ),
.q ( pix_data )
);
//打拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rom_rd_req_r1 <= 0;
rom_rd_req_r2 <= 0;
end
else begin
rom_rd_req_r1 <= rom_rd_req ;
rom_rd_req_r2 <= rom_rd_req_r1 ;
end
end
assign rom_rd_data_vld = rom_rd_req_r2 ;
assign pix_data_vld = rom_rd_data_vld ;
endmodule
六、 ws2812b_drive模块
6.1 状态跳转
IDLE:空闲状态,无数据传输,输出低电平,当pix_data_vld信号到来时跳转到RST;
RST:复位状态,输出低电平,400us复位时间结束后跳转到DATA;
DATA:数据位,假如64个像素数据传输结束,跳转到IDLE;
6.2 设计文件
/**************************************************************
一个bit的周期1200ns,时钟50M,一共60个时钟周期
0码 高电平 300ns
低电平 900ns
1码 高电平 600ns
低电平 600ns
**************************************************************/
module ws2812_drive (
input clk ,
input rst_n ,
input [23:0] pix_data ,
input pix_data_vld ,
output ready , //可以接收图像数据了
output reg ws2812_io
);
parameter IDLE = 0 ,
RST = 1 ,
DATA = 2 ;
reg [1:0] state ;
wire idle2rst ;
wire rst2data ;
wire data2idle ;
wire fifo_wr_req ;
wire fifo_rd_req ;
wire [23:0] fifo_wr_data ;
wire [23:0] fifo_rd_data ;
wire fifo_empty ;
wire fifo_full ;
reg [11:0] cnt_cyc ;
wire add_cyc_cnt,end_cyc_cnt;
reg [4:0] cnt_bit ;
wire add_bit_cnt,end_bit_cnt;
reg [5:0] cnt_num ;
wire add_num_cnt,end_num_cnt;
reg [31:0] cnt_rst ;
wire add_rst_cnt,end_rst_cnt;
localparam T0H = 300/20 ,
T0L = 900/20 ,
T1H = 600/20 ,
T1L = 600/20 ;
parameter RST_MAX = 400_000/20;
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(idle2rst)
state <= RST;
RST : if(rst2data)
state <= DATA;
DATA : if(data2idle)
state <= IDLE;
default : state <= IDLE;
endcase
assign idle2rst = state == IDLE && pix_data_vld; //已经存放了一帧图像数据了
assign rst2data = state == RST && end_rst_cnt; //400us复位时间结束
assign data2idle = state == DATA && end_num_cnt;
/**************************************************************
缓存图像数据
**************************************************************/
fifo fifo_inst (
.aclr (~rst_n ),
.clock (clk ),
.data (fifo_wr_data ), //24bit GRB
.rdreq (fifo_rd_req ),
.wrreq (fifo_wr_req ),
.empty (fifo_empty ),
.full (fifo_full ),
.q (fifo_rd_data ), //24bit GRB
.usedw ( )
);
assign fifo_wr_data = {pix_data[15:8],pix_data[23:16],pix_data[7:0]};
assign fifo_wr_req = pix_data_vld && ~fifo_full;
assign fifo_rd_req = end_bit_cnt && ~fifo_empty;
/**************************************************************
数据时间线控制
**************************************************************/
//一共64*24*1200ns
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_cyc <= 'd0;
else if(add_cyc_cnt) begin
if(end_cyc_cnt)
cnt_cyc <= 'd0;
else
cnt_cyc <= cnt_cyc + 1'b1;
end
assign add_cyc_cnt = state == DATA;
assign end_cyc_cnt = add_cyc_cnt && cnt_cyc == 1200/20 - 1; //一个bit的持续时间
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_bit <= 'd0;
else if(add_bit_cnt) begin
if(end_bit_cnt)
cnt_bit <= 'd0;
else
cnt_bit <= cnt_bit + 1'b1;
end
assign add_bit_cnt = end_cyc_cnt;
assign end_bit_cnt = add_bit_cnt && cnt_bit == 24 - 1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_num <= 'd0;
else if(add_num_cnt) begin
if(end_num_cnt)
cnt_num <= 'd0;
else
cnt_num <= cnt_num + 1'b1;
end
assign add_num_cnt = end_bit_cnt;
assign end_num_cnt = add_num_cnt && cnt_num == 64 - 1;
/**************************************************************
复位计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_rst <= 'd0;
else if(add_rst_cnt) begin
if(end_rst_cnt)
cnt_rst <= 'd0;
else
cnt_rst <= cnt_rst + 1'b1;
end
assign add_rst_cnt = state == RST;
assign end_rst_cnt = add_rst_cnt && cnt_rst == RST_MAX - 1; //复位时间400us
/**************************************************************
实现单总线协议
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
ws2812_io <= 0;
else case(state)
IDLE : ws2812_io <= 0;
RST : ws2812_io <= 0;
DATA : if(fifo_rd_data[23-cnt_bit]) begin //发送数据1
if(cnt_cyc < T1H)
ws2812_io <= 1;
else
ws2812_io <= 0;
end
else begin //发送数据0
if(cnt_cyc < T0H)
ws2812_io <= 1;
else
ws2812_io <= 0;
end
default : ws2812_io <= 1;
endcase
assign ready = state == IDLE;
endmodule
七、TOP设计
/**************************************功能介绍***********************************
Date :
Author : lxx.
Version :
Description:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
output ws2812_io
);
//---------<内部信号定义>-----------------------------------------------------
wire [23:0] pix_data ;
wire pix_data_vld ;
//显示一张图
//ws2812_control ws2812_control_inst(
// /* input */.clk ( clk ) ,
// /* input */.rst_n ( rst_n ) ,
// /* output reg [23:0]*/.pix_data ( pix_data ) ,
// /* output */.pix_data_vld( pix_data_vld ) ,
// /* input */.ready ( ready )
//);
//动态显示
ws2812_control2 ws2812_control2_inst(
/* input */.clk ( clk ) ,
/* input */.rst_n ( rst_n ) ,
/* output reg [23:0]*/.pix_data ( pix_data ) ,
/* output */.pix_data_vld( pix_data_vld ) ,
/* input */.ready ( ready )
);
ws2812_drive ws2812_drive_inst(
/* input */.clk ( clk ) ,
/* input */.rst_n ( rst_n ) ,
/* input [23:0] */.pix_data ( pix_data ) , //
/* input */.pix_data_vld ( pix_data_vld ) ,
/* output */.ready ( ready ) ,
/* output reg */.ws2812_io ( ws2812_io )
);
endmodule
八、仿真模块
8.1 仿真文件
`timescale 1ns/1ns
module tb_top();
//激励信号定义
reg clk ;
reg rst_n ;
//输出信号定义
wire ws2812_io ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
defparam u_top.ws2812_control2_inst.DLEAT_MAX = 200; //参数重定义
//模块例化
top u_top(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* output */ .ws2812_io (ws2812_io )
);
//产生时钟
initial clk = 1'b1;
always #(CLOCK_CYCLE/2) clk = ~ clk;
//产生激励
initial begin
rst_n = 1'b0;
#(CLOCK_CYCLE*5);
rst_n = 1'b1;
end
endmodule
8.2 波形分析
ROM:
ws2812b_control模块
fifo写:
放大:
fifo读:
ws2812b_drive模块
放大: