ws2812静态显示图片

一.原理

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 
  • 仿真效果
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值