一.原理
1.码型
-
ws2812中,码型为归零码。其区别在于:0码的占空比低,1码的占空比高。如下图所示
-
0码,1码,复位码时间约束如下
上图中,高低电平时间给的是范围,那么我们可以根据范围内取合适的值,例如T0H取300ns,T0L取900ns,T1H取600ns,T1L取600ns(保证T0H+T0L=T1H+T1L)
2.数据传输方法
-
我们可以理解为共64个位置,每个位置需要24bit的数据,我们生成64*24bit的数据,这些数据每经历一个位置,就把这个位置给足24bit的数据,然后再经历下一个位置,走完64个位置也就把数据给完了。
-
24bit数据结构如下
我们看到注释中写道两个需要注意的点:高位先发,与我们印象中从0-7不同,顺序是从7-0;GRB顺序,我们通常情况下数据会以RGB的顺序生成。我们需要对这两个点在代码中进行调整,否则会导致我们生成的灯光效果与我们预想的颜色不一样。
- 典型应用电路
可以与上文中数据传输方法结合理解
3.辅助理解
二.生成图片
-
打开电脑自带的画图软件(可以在搜索栏搜索)
-
点击左上角的文件->属性
-
选择单位为像素,宽度高度均为8
-
绘图后保存为24位位图(.bmp)文件
-
bmp文件转换为mif文件
!!!注意:要将mif文件保存至工程文件夹prj文件夹下!!!
笔者曾因这个问题一直没有生成对应数据
三.设计思路
1.模块图
2.状态转移
3.调用rom核
-
rom(read only memory),只读存储器。可以读出mif文件的数据,这里rom用在ws2812_ctrl模块中。
-
约束数据宽度和数据深度
-
选中复位和读使能
-
加入mif文件
-
和ram一样rom输出信号较输入信号同样经过了两个触发器,需要打两拍以保证没有数据丢失。
4.调用FIFO核
-
前文说到,24bit数据高位先发,遵循GRB顺序,这里用FIFO来调节它
-
约束数据宽度和数据深度
-
添加复位信号
-
前显模式
四.代码
- ws2812_driver.v
/**************************************功能介绍***********************************
Date : 2023年8月14日13:45:15
Author : Alegg xy.
Version : 1.0
Description: 接口模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module ws2812_driver(
input clk ,
input rst_n ,
input pix_data_vld,
input [23:0] pix_data,
output ready ,
output reg ws2812_io
);
//---------<参数定义>---------------------------------------------------------
//状态参数
localparam IDLE = 3'd0,//
RST = 3'd1,//
DATA = 3'd2;//
reg [2:0] cstate ;//现态
reg [2:0] nstate ;//次态
//0,1电平对应值
localparam T0H = 300/20,
T0L = 900/20,
T1H = 600/20,
T1L = 600/20;
parameter MAX_400US = 15'd20000;
parameter MAX_1200NS = 6'd60;
parameter MAX_BIT = 5'd24;//24bit数据
parameter MAX_NUM = 7'd64;//64个灯
//---------<内部信号定义>-----------------------------------------------------
reg [31:0] cnt_400us ;
wire add_cnt_400us ;
wire end_cnt_400us ;
reg [11:0] cnt_1200ns ;
wire add_cnt_1200ns ;
wire end_cnt_1200ns ;
reg [4:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [5:0] cnt_num ;
wire add_cnt_num ;
wire end_cnt_num ;
//fifo信号定义
wire [23:0] fifo_wr_data;
wire [23:0] fifo_rd_data;
wire fifo_wr_req;
wire fifo_rd_req;
wire fifo_empty;
wire fifo_full;
//400us计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_400us <= 'd0;
end
else if(add_cnt_400us)begin
if(end_cnt_400us)begin
cnt_400us <= 'd0;
end
else begin
cnt_400us <= cnt_400us + 1'd1;
end
end
end
assign add_cnt_400us = cstate == RST;
assign end_cnt_400us = add_cnt_400us && cnt_400us == MAX_400US - 1'd1;
//1200ns计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1200ns <= 'd0;
end
else if(add_cnt_1200ns)begin
if(end_cnt_1200ns)begin
cnt_1200ns <= 'd0;
end
else begin
cnt_1200ns <= cnt_1200ns + 1'd1;
end
end
end
assign add_cnt_1200ns = cstate == DATA;
assign end_cnt_1200ns = add_cnt_1200ns && cnt_1200ns == MAX_1200NS - 1'd1;
//24bit计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
end
assign add_cnt_bit = end_cnt_1200ns;
assign end_cnt_bit = add_cnt_bit && cnt_bit == MAX_BIT - 1'd1;
//64个灯计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_num <= 'd0;
end
else if(add_cnt_num)begin
if(end_cnt_num)begin
cnt_num <= 'd0;
end
else begin
cnt_num <= cnt_num + 1'd1;
end
end
end
assign add_cnt_num = end_cnt_bit;
assign end_cnt_num = add_cnt_num && cnt_num == MAX_NUM - 1'd1;
//状态跳转条件
wire IDLE_RST;
wire RST_DATA;
wire DATA_IDLE;
assign IDLE_RST = (cstate == IDLE) && pix_data_vld;//开始传输数据信号
assign RST_DATA = (cstate == RST) && end_cnt_400us;//计400us
assign DATA_IDLE = (cstate == DATA) && end_cnt_num;//计满64个24bit
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE : begin
if (IDLE_RST) begin
nstate = RST;
end
else begin
nstate = cstate;
end
end
RST : begin
if (RST_DATA) begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA : begin
if (DATA_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = cstate;
endcase
end
//fifo例化
fifo fifo_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( fifo_wr_data ),
.rdreq ( fifo_rd_req ),
.wrreq ( fifo_wr_req ),
.empty ( fifo_empty ),
.full ( fifo_full ),
.q ( fifo_rd_data ),
.usedw ( )
);
assign fifo_wr_data = {pix_data[15:8],pix_data[23:16],pix_data[7:0]};//传输进去的是GRB
assign fifo_wr_req = pix_data_vld && ~fifo_full;
assign fifo_rd_req = end_cnt_bit && ~fifo_empty;
always@(posedge clk or negedge rst_n)
if(!rst_n)
ws2812_io <= 0;
else case(cstate)
IDLE : ws2812_io <= 0;
RST : ws2812_io <= 0;
DATA : if(fifo_rd_data[23-cnt_bit]) begin //发送数据1
if(cnt_1200ns < T1H)
ws2812_io <= 1;
else
ws2812_io <= 0;
end
else begin //发送数据0
if(cnt_1200ns < T0H)
ws2812_io <= 1;
else
ws2812_io <= 0;
end
default : ws2812_io <= 1;
endcase
assign ready = cstate == IDLE;
endmodule
- ws2812_ctrl.v
/**************************************功能介绍***********************************
Date : 2023年8月14日14:23:39
Author : Alegg xy.
Version : 1.0
Description: 显示一张图片
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module ws2812_ctrl(
input clk ,
input rst_n ,
input ready ,
output [23:0] pix_data,
output pix_data_vld
);
//---------<参数定义>---------------------------------------------------------
localparam IDLE = 'd0,
DATA = 'd1,
DONE = 'd2;
//---------<内部信号定义>-----------------------------------------------------
reg [2:0] cstate ;//现态
reg [2:0] nstate ;//次态
reg [5:0] cnt_x ;
wire add_cnt_x ;
wire end_cnt_x ;
reg [5:0] cnt_y ;
wire add_cnt_y ;
wire end_cnt_y ;
//rom
wire rom_rd_data_vld;
wire rom_rd_req;
reg rom_rd_req_r1;//打一拍
reg rom_rd_req_r2;//打两拍
//横坐标计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_x <= 'd0;
end
else if(add_cnt_x)begin
if(end_cnt_x)begin
cnt_x <= 'd0;
end
else begin
cnt_x <= cnt_x + 1'd1;
end
end
end
assign add_cnt_x = cstate == DATA;
assign end_cnt_x = add_cnt_x && cnt_x == 7;
//纵坐标计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_y <= 'd0;
end
else if(add_cnt_y)begin
if(end_cnt_y)begin
cnt_y <= 'd0;
end
else begin
cnt_y <= cnt_y + 1'd1;
end
end
end
assign add_cnt_y = end_cnt_x;
assign end_cnt_y = add_cnt_y && cnt_y == 7;
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE:begin
if (ready) begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA:begin
if (end_cnt_y) begin
nstate = DONE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
//rom例化
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 <= 'd0;
rom_rd_req_r2 <= 'd0;
end
else begin
rom_rd_req_r1 <= rom_rd_req;
rom_rd_req_r2 <= rom_rd_req_r1;
end
end
assign rom_rd_req = cstate == DATA;
assign rom_rd_data_vld = rom_rd_req_r2;
assign pix_data_vld = rom_rd_data_vld;
endmodule
- top.v
/**************************************功能介绍***********************************
Date : 2023年8月14日15:45:36
Author : Alegg xy.
Version : 1.0
Description: 顶层模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
output ws2812_io
);
//---------<参数定义>---------------------------------------------------------
wire pix_data_vld;
wire [23:0] pix_data;
wire ready;
//---------<内部信号定义>-----------------------------------------------------
ws2812_driver u_ws2812_driver(
.clk (clk),
.rst_n (rst_n),
.pix_data_vld (pix_data_vld),
.pix_data (pix_data),
.ready (ready),
.ws2812_io (ws2812_io)
);
ws2812_ctrl u_ws2812_ctrl(
.clk (clk),
.rst_n (rst_n),
.ready (ready),
.pix_data (pix_data),
.pix_data_vld (pix_data_vld)
);
endmodule
五.仿真
- tb_top.v
`timescale 1ns/1ns
module tb_top();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
//输出信号定义
wire tb_ws2812_io;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
defparam u_top.u_ws2812_driver.MAX_400US = 20;
// defparam u_top.u_ws2812_ctrl3.MAX_500ms = 25;
//模块例化
top u_top(
.clk (tb_clk ),
.rst_n (tb_rst_n ),
.ws2812_io (tb_ws2812_io )
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
//产生激励
initial begin
tb_rst_n = 1'b1;
#(CLOCK_CYCLE*2);
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*20);
tb_rst_n = 1'b1;
end
endmodule
- 仿真效果