草庐IT

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

FPGA与ZYNQ的学习笔记 2023-03-28 原文

这次设计一个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的状态,该模块初步验证可行。

有关基于FPGA的RGB灯WS2812B的控制器设计的更多相关文章

  1. Ruby Readline 在向上箭头上使控制台崩溃 - 2

    当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby​​安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少

  2. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  3. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  4. ruby - Rails 3 的 RGB 颜色选择器 - 2

    状态:我正在构建一个应用程序,其中需要一个可供用户选择颜色的字段,该字段将包含RGB颜色代码字符串。我已经测试了一个看起来很漂亮但效果不佳的。它是“挑剔的颜色”,并托管在此存储库中:https://github.com/Astorsoft/picky-color.在这里我打开一个关于它的一些问题的问题。问题:请建议我在Rails3应用程序中使用一些颜色选择器。 最佳答案 也许页面上的列表jQueryUIDevelopment:ColorPicker为您提供开箱即用的产品。原因是jQuery现在包含在Rails3应用程序中,因此使用基

  5. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  6. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  7. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  8. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  9. ruby-on-rails - 在 Rails 控制台中使用 asset_path - 2

    在我的Character模型中,我添加了:字符.rbbefore_savedoself.profile_picture_url=asset_path('icon.png')end但是,对于数据库中已存在的所有角色,它们的profile_picture_url为nil。因此,我想进入控制台并遍历所有这些并进行设置。在我试过的控制台中:Character.find_eachdo|c|c.profile_picture_url=asset_path('icon.png')end但这给出了错误:NoMethodError:undefinedmethod`asset_path'formain:O

  10. ruby-on-rails - 带有 Pry 的 Rails 控制台 - 2

    当我进入Rails控制台时,我已将pry设置为加载代替irb。我找不到该页面或不记得如何将其恢复为默认行为,因为它似乎干扰了我的Rubymine调试器。有什么建议吗? 最佳答案 我刚发现问题,pry-railsgem。忘记了它的目的是让“railsconsole”打开pry。 关于ruby-on-rails-带有Pry的Rails控制台,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/question

随机推荐