基于FPGA的RGB灯WS2812B的控制器设计
这次设计一个RGB灯的控制器,该控制器具有如下特点:
- 每个灯的颜色可调,亮灭可控
- 可以设置参数来修改RGB的数目
WS2812B的数据时序如下图所示:
(图片来源自网络、侵权删)
为了方便设计我把T1H和T0L的时间值设为0.8us,为了稳定将RES设置为60us。
设计的思路是,设置一个400ns的计时器,然后再设置一个计400ns次数的计时器(每计三次清零),然后1码就是前两个400us为高电平,第三个400us为低电平;0码也是同理。
一、设计的代码
1、单像素控制模块
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: GDUT
// Engineer: Lclone
//
// Create Date: 2023/03/01 12:04:28
// Design Name:
// Module Name: WS2812B_controller
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module WS2812B_controller(
input Clk, //时钟信号
input Rst_n, //复位信号
input [23:0] Disp_Data, //像素点的数据
input Data_valid, //数据有效信号
output reg Send_done, //单像素发送完成信号
output reg Data_out //数据输出
);
parameter CNT_400NS = 20;
reg [4:0] cnt_400ns; //40us计数器
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
cnt_400ns <= 0;
else if(cnt_400ns == CNT_400NS - 1)
cnt_400ns <= 0;
else
cnt_400ns <= cnt_400ns + 1'b1;
end
reg [23:0] Disp_Data_reg; //数据寄存
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
Disp_Data_reg <= 0;
else if(Data_valid)
Disp_Data_reg <= Disp_Data;
else if(Send_done)
Disp_Data_reg <= 0;
else
Disp_Data_reg <= Disp_Data_reg;
end
parameter NUM_400NS = 3;
reg [1:0] num_400ns; //400ns段计数器,用来控制符号1和符号0的生成
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
num_400ns <= 0;
else if(num_400ns == NUM_400NS - 1 & cnt_400ns == CNT_400NS - 1)
num_400ns <= 0;
else if(cnt_400ns == CNT_400NS - 1)
num_400ns <= num_400ns + 1'b1;
else
num_400ns <= num_400ns;
end
parameter BIT_CNT = 24;
reg [4:0] bit_cnt; //位计数器
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
bit_cnt <= 0;
else if(bit_cnt == BIT_CNT - 1 & num_400ns == NUM_400NS - 1 & cnt_400ns == CNT_400NS - 1)
bit_cnt <= 0;
else if(num_400ns == NUM_400NS - 1 & cnt_400ns == CNT_400NS - 1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
Send_done <= 0;
else if(bit_cnt == BIT_CNT - 1 & num_400ns == NUM_400NS - 1 & cnt_400ns == CNT_400NS - 2)
Send_done <= 1;
else
Send_done <= 0;
end
reg symbol_1;
reg symbol_0;
always @(*) begin // 这里将case = 2的情况提前到case = 0 ,是为了让模块不在工作的时候使输出为0;实际case情况串起来时也是满足要求的
case (num_400ns)
0:begin
symbol_1 <= 0;
symbol_0 <= 0;
end
1:begin
symbol_1 <= 1;
symbol_0 <= 1;
end
2:begin
symbol_1 <= 1;
symbol_0 <= 0;
end
default:begin
symbol_1 <= 0;
symbol_0 <= 0;
end
endcase
end
always @(*) begin //数据传输,先传输高位
case(bit_cnt)
0: Data_out <= (Disp_Data_reg[23]) ? symbol_1 : symbol_0;
1: Data_out <= (Disp_Data_reg[22]) ? symbol_1 : symbol_0;
2: Data_out <= (Disp_Data_reg[21]) ? symbol_1 : symbol_0;
3: Data_out <= (Disp_Data_reg[20]) ? symbol_1 : symbol_0;
4: Data_out <= (Disp_Data_reg[19]) ? symbol_1 : symbol_0;
5: Data_out <= (Disp_Data_reg[18]) ? symbol_1 : symbol_0;
6: Data_out <= (Disp_Data_reg[17]) ? symbol_1 : symbol_0;
7: Data_out <= (Disp_Data_reg[16]) ? symbol_1 : symbol_0;
8: Data_out <= (Disp_Data_reg[15]) ? symbol_1 : symbol_0;
9: Data_out <= (Disp_Data_reg[14]) ? symbol_1 : symbol_0;
10: Data_out <= (Disp_Data_reg[13]) ? symbol_1 : symbol_0;
11: Data_out <= (Disp_Data_reg[12]) ? symbol_1 : symbol_0;
12: Data_out <= (Disp_Data_reg[11]) ? symbol_1 : symbol_0;
13: Data_out <= (Disp_Data_reg[10]) ? symbol_1 : symbol_0;
14: Data_out <= (Disp_Data_reg[9]) ? symbol_1 : symbol_0;
15: Data_out <= (Disp_Data_reg[8]) ? symbol_1 : symbol_0;
16: Data_out <= (Disp_Data_reg[7]) ? symbol_1 : symbol_0;
17: Data_out <= (Disp_Data_reg[6]) ? symbol_1 : symbol_0;
18: Data_out <= (Disp_Data_reg[5]) ? symbol_1 : symbol_0;
19: Data_out <= (Disp_Data_reg[4]) ? symbol_1 : symbol_0;
20: Data_out <= (Disp_Data_reg[3]) ? symbol_1 : symbol_0;
21: Data_out <= (Disp_Data_reg[2]) ? symbol_1 : symbol_0;
22: Data_out <= (Disp_Data_reg[1]) ? symbol_1 : symbol_0;
23: Data_out <= (Disp_Data_reg[0]) ? symbol_1 : symbol_0;
default:Data_out <= 0;
endcase
end
endmodule
2、单像素控制模块仿真
仿真激励
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2023/03/01 13:09:14
// Design Name:
// Module Name: WS2812B_controller_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module WS2812B_controller_tb();
reg clk_50m;
initial clk_50m <= 1;
always #10 clk_50m <= ~clk_50m;
reg rst_n;
initial begin
rst_n <= 0;
#200
rst_n <= 1;
end
reg [23:0] Disp_Data;
reg Data_valid;
wire Send_done;
wire Data_out;
WS2812B_controller WS2812B_controller_inst(
.Clk (clk_50m),
.Rst_n (rst_n),
.Disp_Data (Disp_Data),
.Data_valid (Data_valid),
.Send_done (Send_done),
.Data_out (Data_out)
);
initial begin
Disp_Data <= 0;
Data_valid <= 0;
#240;
Disp_Data <= 24'h123456;
Data_valid <= 1;
#20;
Data_valid <= 0;
@(posedge Send_done)
#20;
Disp_Data <= 24'h789abc;
Data_valid <= 1;
#20;
Data_valid <= 0;
@(posedge Send_done)
#100;
$stop;
end
endmodule
仿真结果
空闲状态时输出为0
多次发送,数据能够正常衔接
最后发送完后数据能够变为0
每位符号都能正常发送
结论:该模块仿真通过。
3、多像素显示模块
在该模块中,例化了一个24位宽、深度为16的简单双口RAM核,便于控制每个灯的状态,同时还例化了ILA和VIO IP核来方便调试。
RAM对应的地址即为RGB的编号。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: GDUT
// Engineer: Lclone
//
// Create Date: 2023/03/01 15:29:54
// Design Name:
// Module Name: WS2812B_display
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module WS2812B_display(
input Clk, //时钟输入
input Rst_n, //复位信号
output Data_out //数据输出
);
//RAM的各种信号
wire [23:0] ram_output_data;
wire [23:0] ram_input_data;
reg [3:0] ram_output_addr;
wire [3:0] ram_input_addr;
reg Data_valid;
wire ram_input_en;
//---------------------------
wire Send_done;
reg Send_done_r;//发送完成信号延一拍,方便数据对齐。
wire Data_out_t;
parameter CNT_60US = 3000;
reg [11:0] cnt_60us;
reg act_60us; //帧同步标志,在为高期间会复位单像素控制模块
ila_0 ila_0_inst (
.clk(Clk), // input wire clk
.probe0(ram_output_addr), // input wire [3:0] probe0
.probe1(ram_output_data), // input wire [23:0] probe1
.probe2(Data_valid), // input wire [0:0] probe2
.probe3(Data_out) // input wire [0:0] probe3
);
vio_0 vio_0_inst (
.clk(Clk), // input wire clk
.probe_out0(ram_input_addr), // output wire [3 : 0] probe_out0
.probe_out1(ram_input_data), // output wire [23 : 0] probe_out1
.probe_out2(ram_input_en) // output wire [0 : 0] probe_out2
);
blk_mem_gen_0 blk_mem_gen_0_inst (
.clka(Clk), // input wire clka
.wea(ram_input_en), // input wire [0 : 0] wea
.addra(ram_input_addr), // input wire [3 : 0] addra
.dina(ram_input_data), // input wire [23 : 0] dina
.clkb(Clk), // input wire clkb
.addrb(ram_output_addr), // input wire [3 : 0] addrb
.doutb(ram_output_data) // output wire [23 : 0] doutb
);
WS2812B_controller WS2812B_controller_inst(
.Clk(Clk),
.Rst_n(~act_60us & Rst_n),
.Disp_Data(ram_output_data),
.Data_valid(Data_valid),
.Send_done(Send_done),
.Data_out(Data_out_t)
);
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
ram_output_addr <= 0;
else if(cnt_60us == CNT_60US - 1 )
ram_output_addr <= 0;
else if(Send_done == 1 & ~act_60us)
ram_output_addr <= ram_output_addr + 1'b1;
else
ram_output_addr <= ram_output_addr;
end
always @(posedge Clk) Send_done_r <= Send_done;
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
Data_valid <= 0;
else if((Send_done_r == 1 & ~act_60us)|cnt_60us == CNT_60US - 1)
Data_valid <= 1;
else
Data_valid <= 0;
end
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
act_60us <= 0;
else if(ram_output_addr == 4'hf & Send_done == 1)
act_60us <= 1;
else if(cnt_60us == CNT_60US - 1)
act_60us <= 0;
else
act_60us <= act_60us;
end
always @(posedge Clk or posedge Rst_n) begin
if(Rst_n == 0)
cnt_60us <= 0;
else if(cnt_60us == CNT_60US - 1)
cnt_60us <= 0;
else if(act_60us)
cnt_60us <= cnt_60us + 1'b1;
else
cnt_60us <= 0;
end
assign Data_out = (act_60us)? 0:Data_out_t;
endmodule
4、多像素显示模块仿真
仿真文件
`timescale 1ns / 1ps
module display_tb();
reg clk_50m;
initial clk_50m <= 1;
always #10 clk_50m <= ~clk_50m;
reg rst_n;
initial begin
rst_n <= 0;
#200
rst_n <= 1;
end
wire Data_out;
WS2812B_display WS2812B_display_inst(
.Clk(clk_50m),
.Rst_n(rst_n),
.Data_out(Data_out)
);
endmodule
仿真结果
结论:模块能够正确给出Data_valid和send_done信号,且60us的帧同步信号也能正确的给出。仿真通过
二、上板验证
将程序烧入芯片中,通过VIO控制写入RAM的数据可以改变RGB的状态。
该模块能够正常的改变每个RGB的状态,该模块初步验证可行。