基于FPGA的RGB灯WS2812B的控制器设计

这次设计一个RGB灯的控制器,该控制器具有如下特点:

  • 每个灯的颜色可调,亮灭可控
  • 可以设置参数来修改RGB的数目
     
    WS2812B的数据时序如下图所示:
     
    image
     
    image
     
    image
    (图片来源自网络、侵权删)
     
    为了方便设计我把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

仿真结果

image
空闲状态时输出为0
 
image
多次发送,数据能够正常衔接
 
image
最后发送完后数据能够变为0
 
image
每位符号都能正常发送
 
结论:该模块仿真通过。

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

仿真结果

image
结论:模块能够正确给出Data_valid和send_done信号,且60us的帧同步信号也能正确的给出。仿真通过

二、上板验证

将程序烧入芯片中,通过VIO控制写入RAM的数据可以改变RGB的状态。
image
image
该模块能够正常的改变每个RGB的状态,该模块初步验证可行。

posted @ 2023-03-03 10:53  Lclone  阅读(1260)  评论(0编辑  收藏  举报