基于FPGA驱动WS2812B

先丢手册

C2976072_发光二极管-LED_WS281...SEMI发光二极管_LED规格书.PDF https://www.aliyundrive.com/s/PN49tZmA6zk 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。

手册详解

在驱动ws2812b之前,我们先需要对手册上的知识进行一个梳理:

产品概述与特点(特点有一些注意事项也值得注意,但是我们在这主要先学会使用)我们略过,我们来看它的引脚及其功能

13f491337a8d4c1696ac07e407a5146c.png

我们可以发现它有四个引脚,分别为电源和地以及输入输出口,然后我们再看最大额定值,电源电压是在3.7到5.3V,我们fpga一般引脚引出的电压为3.3V,所以我们需要设置一下电压值,或者直接找一些特定的5VCC IO口,输入逻辑电压为-0.3V到VDD+0.7V,我们继续往下看高低电平的逻辑电压要求(温度一般都满足,可以了解即可):

 8a7a81742f7048a197c096872c74a0d2.png

 我们可以发现该LED灯可以承受的电流最大为+-1uA,我们一般只要加上的电压符合的话电流也会满足。我们看到高电平输入最小要2.7V最大不超过VDD+0.7V我们FPGA输出的3.3V是可以支持的,然后我们看到低电平要求-0.3V到0.7V,我们FPGA提供的低电平一般为0V也是符合要求的。我们继续往下看。

69ef7bd1cc7d498cb9f596fccc1e2687.png

 开关特性与LED特性我们也了解即可。

下面来到最重要的,WS2812B不是我们平常LED灯给高电平就亮,给低电平就熄灭,它对于高低电平有一定的要求,我们需要对信号进行调制。

97476d7fa22749c8af3888761cc92df4.png

 手册这里对0,1,重置的码型要求都做出了要求,我们观察表格后将0与1的单比特长度人为都设定为1200ns方便我们编写代码。我们设定的时间如下:

4148cd80454d4918bf78e4d5ad1d742f.png

 连接方式图中也有告诉,如果是自己购买灯珠连接务必要按照手册连接。然后我们手册下方便介绍了我们传输数据的要求:

09ac0a737c8a454db361aed481b1ed05.png

 我们可以了解到我们发送数据后,第一个灯珠会将前收到的24bit给截获,然后向下一个灯珠发送整形后的数据,我们对于一个阵列只需要将每一个的灯珠信号给顺序连接发出即可。然后还有一个很重要的是灯珠接收的数据结构是GRB结构,且为高位先发,这一点很重要,有一些没有注意直接将RGB发出会出现错误。

代码构思

我们分析完毕了手册,我们接下来就该构思我们的工程模块该如何分配,我们通过手册可以知道,驱动WS2812B的01信号都需要进行编码,所以我们肯定需要一个代码模块来进行该操作,然后还需要把接收到的RGB信号(为何使用RGB信号?->因为我们一般提供的信息都是为RGB格式,人为转化会很麻烦且可能出错,所以我们直接统一对信号进行整型就可以了)进行GRB整型。而把信号连续发送过去我们也能够想到FIFO这个模块,我们可以把我们手上有的串联起来的灯珠所需要的全部信号从高位开始写入FIFO然后再读取FIFO中数据即可,就不用人为去思考算法写入。而以上关于驱动WS2812S的模块我们可以作为一个单独的驱动模块,而我们想要显示的数据及其用户控制我们单独作为另一个模块,与驱动模块分离,有利于我们编写代码和代码维护。

我们接下来开始小模块的构思:

驱动模块:

我们发现WS2812B工作分为初始未工作,重置,然后进行数据写入,写入完后又进入初始,如需再次写入则重复以上过程,所以我们很自然的可以想到使用状态机,我们定义一个初始形态IDLE,一个复位RES,一个数据传输DATA三个状态之间进行转换

659af524bcb54f60abc75e396a05293b.png

 我们初始状态直接等待外部我们控制信号及其数据传入,所以我们将一个外部使能pix_en作为我们的跳转条件进入RES,而进入RES后我们便进行RES要求的时长(定义计数器来延时)进行0输出给灯珠进行重置,在延时结束时候进入数据传输DATA状态。而最重要的DATA中包含着我们码型变换。

我们现在说一说我们实现码型转换的原理,笔者这里使用的硬件为64个灯珠的连接构成8x8的点阵,每一个灯珠要接收24bit信号一共需要发送64个24bit信号,所以我们可以使用三个计数器的串联来实现这个效果:

//bit时间计数器开始
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_bit_time <= 0;
    end
    else if(add_cnt_bit_time) begin
        if(end_cnt_bit_time) begin
            cnt_bit_time <=  0;
        end
        else begin
            cnt_bit_time <=  cnt_bit_time + 1'b1;
        end
    end
    else begin
        cnt_bit_time <= cnt_bit_time;
    end
end
    
assign add_cnt_bit_time = state_c == DATA;
assign end_cnt_bit_time = add_cnt_bit_time && cnt_bit_time == TIME_BIT - 1'b1;
//计数器结束
    
//一组数据24bit计数器开始
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_bit_num <= 0;
    end
    else if(add_cnt_bit_num) begin
        if(end_cnt_bit_num) begin
            cnt_bit_num <=  0;
        end
        else begin
            cnt_bit_num <=  cnt_bit_num + 1'b1;
        end
    end
    else begin
        cnt_bit_num <= cnt_bit_num;
    end
end
    
assign add_cnt_bit_num = end_cnt_bit_time;
assign end_cnt_bit_num = add_cnt_bit_num && cnt_bit_num == BIT_NUM - 1'b1;
//计数器结束
    
//一帧计数器开始
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_frame <= 0;
    end
    else if(add_cnt_frame) begin
        if(end_cnt_frame) begin
            cnt_frame <=  0;
        end
        else begin
            cnt_frame <=  cnt_frame + 1'b1;
        end
    end
    else begin
        cnt_frame <= cnt_frame;
    end
end
    
assign add_cnt_frame = end_cnt_bit_num;
assign end_cnt_frame = add_cnt_frame && cnt_frame == FRAME - 1'b1;
//计数器结束

 总体处理好后我们接下来需要对每bit信号0,1进行调制,我们在上方手册分析时候定义了以下代码:

//--信号调制定义
//T0H  0码高电平时间 300ns
//T0L  0码低电平时间 900ns
//T1H  1码高电平时间 600ns
//T1L  1码低电平时间 600ns
//RES 帧单位低电平时间300us

localparam  T0H_TIME = 15,
            T1H_TIME = 30;

我们分别也使用两个计数器来实现01调制需要时间的延时,当在IDLE和RES状态时候我们给IO口都输出一个常规低电平信号,在DATA中我们对0信号调制是在T0H计时器还没有记满时候为高电平,记满后为低电平,而1信号调制则是在T1H还没计满时为高电平,记满后为低电平,代码如下:

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        ws2812_io <= 0;
    end
    else begin
        case (state_c)
            IDLE: ws2812_io <= 0;
            RES : ws2812_io <= 0;
            DATA: 
                begin
                    if (~fifo_rd_data[23-cnt_bit_num]) begin
                        if (cnt_bit_time < T0H_TIME) begin
                            ws2812_io <= 1;
                        end
                        else begin
                            ws2812_io <= 0;
                        end
                    end
                    else begin
                        if (cnt_bit_time < T1H_TIME) begin
                            ws2812_io <= 1;
                        end
                        else begin
                            ws2812_io <= 0;
                        end
                    end
                end
            default: ws2812_io <= 0;
        endcase
    end
end

代码中的~fifo_rd_data[23-cnt_bit_num]是我们读取FIFO中的数据,中括号的23-cnt_bit_num是因为要高位先传所以是23-cnt_bit_num而不是cnt_bit_num。

我们接着来看波形调制之前的FIFO如何处理:

//****************************************************************
//--fifo定义
wire            fifo_wrreq  ;
wire            fifo_rdreq  ;
wire            fifo_empty  ;
wire            fifo_full   ;
wire    [23:0]  fifo_wr_data;
wire    [23:0]  fifo_rd_data;
    
//****************************************************************
//****************************************************************
//--例化fifo 以及控制逻辑
fifo	fifo_inst (
    .aclr       ( ~rst_n        ),
    .clock      ( clk           ),
    .data       ( fifo_wr_data  ),
    .rdreq      ( fifo_rdreq    ),
    .wrreq      ( fifo_wrreq    ),
    .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]};
assign  fifo_wrreq      =   pix_en && ~fifo_full;
assign  fifo_rdreq      =   end_cnt_bit_num && ~fifo_empty;

第一个assign是将RGB->GRB(pix_data为我们控制模块想要我们显示的数据)

第二个assign是fifo的读使能为外部的使能信号与上~fifo_full也就是没有写满且外部给使能后即可写入

第三个assign是fifo的读使能,是当一帧信号全部传入fifo且与上~fifo_empty也就是没有读空即可读取

这便是我们驱动模块,驱动模块只负责驱动,不和我们控制模块挂钩,只要正确一般不用修改。

最后放上全部的驱动代码:

/*
 * @Author: MEGA
 * @Date: 2023-08-11 14:18:29
 * @LastEditTime: 2023-08-14 14:11:50
 * @LastEditors: Please set LastEditors
 * @Description: 
 * @Version: 
 * @FilePath: \WS2812B\src\ws2812_driver.v
 * 
 */
module ws2812_driver#(parameter TIME_BIT = 60, BIT_NUM = 24, FRAME = 64) (
    input               clk             ,
    input               rst_n           ,
    input               pix_en          ,
    input   [23:0]      pix_data        ,

    output              ready           ,
    output  reg         ws2812_io       
);

    //状态定义
    localparam	IDLE    =   3'b001,
                RES     =   3'b010,
                DATA    =   3'b100;
    //状态跳转条件
    wire        idle2res    ;   //2 -> to
    wire        res2data    ;
    wire        data2idle   ;
    //状态寄存器
    reg [2:0]   state_c     ;   //current 现态
    reg [2:0]   state_n     ;   //next    次态

    //****************************************************************
    //--信号调制定义
    //T0H  0码高电平时间 300ns
    //T0L  0码低电平时间 900ns
    //T1H  1码高电平时间 600ns
    //T1L  1码低电平时间 600ns
    //RES 帧单位低电平时间300us

    localparam  T0H_TIME = 15,
                T1H_TIME = 30,
                RES_TIME = 15_000; //仿真15 上板记得改回15_000!!!
    //****************************************************************

    //****************************************************************
    //--计数器定义
    reg [5:0] cnt_bit_time;
    wire add_cnt_bit_time;
    wire end_cnt_bit_time;

    reg [4:0] cnt_bit_num;
    wire add_cnt_bit_num;
    wire end_cnt_bit_num;

    reg [6:0] cnt_frame;
    wire add_cnt_frame;
    wire end_cnt_frame;

    reg [13:0] cnt_res;
    wire add_cnt_res;
    wire end_cnt_res;
    //****************************************************************

    //****************************************************************
    //--fifo定义
    wire            fifo_wrreq  ;
    wire            fifo_rdreq  ;
    wire            fifo_empty  ;
    wire            fifo_full   ;
    wire    [23:0]  fifo_wr_data;
    wire    [23:0]  fifo_rd_data;
    
    //****************************************************************

    //****************************************************************
    //--例化fifo 以及控制逻辑
    fifo	fifo_inst (
	    .aclr       ( ~rst_n        ),
	    .clock      ( clk           ),
	    .data       ( fifo_wr_data  ),
	    .rdreq      ( fifo_rdreq    ),
	    .wrreq      ( fifo_wrreq    ),
	    .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]};
    assign  fifo_wrreq      =   pix_en && ~fifo_full;
    assign  fifo_rdreq      =   end_cnt_bit_num && ~fifo_empty;
    //****************************************************************

    //状态机第一段
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            state_c <= IDLE;
        end
        else begin
            state_c <= state_n; //次态与现态相差一个时钟周期
        end
    end 
    //状态机第二段  组合逻辑 描述次态
    always @(*) begin
        case(state_c)    //现态状态决定次态
            IDLE :
                begin
                    if(idle2res)
                        state_n = RES;
                    else
                        state_n = state_c;
                end
            RES   :
                begin
                    if(res2data)
                        state_n = DATA;
                    else
                        state_n = state_c;
                end
            DATA   :
                begin
                    if(data2idle)
                        state_n = IDLE;
                    else
                        state_n = state_c;
                end
            default : state_n = state_c;
        endcase
    end

    assign idle2res     =   state_c == IDLE && pix_en           ;    //跳转条件 跳转下个状态
    assign res2data     =   state_c == RES  && end_cnt_res      ;
    assign data2idle    =   state_c == DATA && end_cnt_frame    ;

    //****************************************************************
    //--数据生成  计数器
    
    //res信号计数器开始
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_res <= 0;
        end
        else if(add_cnt_res) begin
            if(end_cnt_res) begin
                cnt_res <=  0;
            end
            else begin
                cnt_res <=  cnt_res + 1'b1;
            end
        end
        else begin
            cnt_res <= cnt_res;
        end
    end
    
    assign add_cnt_res = state_c == RES;
    assign end_cnt_res = add_cnt_res && cnt_res == RES_TIME - 1'b1;
    //计数器结束

    //bit时间计数器开始
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_bit_time <= 0;
        end
        else if(add_cnt_bit_time) begin
            if(end_cnt_bit_time) begin
                cnt_bit_time <=  0;
            end
            else begin
                cnt_bit_time <=  cnt_bit_time + 1'b1;
            end
        end
        else begin
            cnt_bit_time <= cnt_bit_time;
        end
    end
    
    assign add_cnt_bit_time = state_c == DATA;
    assign end_cnt_bit_time = add_cnt_bit_time && cnt_bit_time == TIME_BIT - 1'b1;
    //计数器结束
    
    //一组数据24bit计数器开始
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_bit_num <= 0;
        end
        else if(add_cnt_bit_num) begin
            if(end_cnt_bit_num) begin
                cnt_bit_num <=  0;
            end
            else begin
                cnt_bit_num <=  cnt_bit_num + 1'b1;
            end
        end
        else begin
            cnt_bit_num <= cnt_bit_num;
        end
    end
    
    assign add_cnt_bit_num = end_cnt_bit_time;
    assign end_cnt_bit_num = add_cnt_bit_num && cnt_bit_num == BIT_NUM - 1'b1;
    //计数器结束
    
    //一帧计数器开始
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_frame <= 0;
        end
        else if(add_cnt_frame) begin
            if(end_cnt_frame) begin
                cnt_frame <=  0;
            end
            else begin
                cnt_frame <=  cnt_frame + 1'b1;
            end
        end
        else begin
            cnt_frame <= cnt_frame;
        end
    end
    
    assign add_cnt_frame = end_cnt_bit_num;
    assign end_cnt_frame = add_cnt_frame && cnt_frame == FRAME - 1'b1;
    //计数器结束
    //****************************************************************

    //****************************************************************
    //--IO输出逻辑

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            ws2812_io <= 0;
        end
        else begin
            case (state_c)
                IDLE: ws2812_io <= 0;
                RES : ws2812_io <= 0;
                DATA: 
                    begin
                        if (~fifo_rd_data[23-cnt_bit_num]) begin
                            if (cnt_bit_time < T0H_TIME) begin
                                ws2812_io <= 1;
                            end
                            else begin
                                ws2812_io <= 0;
                            end
                        end
                        else begin
                            if (cnt_bit_time < T1H_TIME) begin
                                ws2812_io <= 1;
                            end
                            else begin
                                ws2812_io <= 0;
                            end
                        end
                    end
                default: ws2812_io <= 0;
            endcase
        end
    end

    assign  ready = state_c == IDLE;
    //****************************************************************

endmodule

控制模块:

控制模块我们是这样构思:

把我们想要的数据存入rom然后再通过控制读使能来控制怎么显示我们的数据。

而rom的读取过程我们也可以归纳成初始IDLE(初始)->DATA(读取)->DONE(完成)

因为前文说过我们硬件为一个8x8点阵结构,我们可以定义两个定时器cnt_x和cnt_y来分别表示我们的xy轴:

//行列计数器开始
always@(posedge clk or negedge rst_n)	
    if(!rst_n)								
        cnt_x <= 'd0;						
    else    if(add_cnt_x) begin				
        if(end_cnt_x)						
            cnt_x <= 'd0;  				
        else									
            cnt_x <= cnt_x + 1'b1;		
    end											
assign add_cnt_x = rden;
assign end_cnt_x = add_cnt_x && cnt_x == 8 - 1;
always@(posedge clk or negedge rst_n)	
    if(!rst_n)								
        cnt_y <= 'd0;						
    else    if(add_cnt_y) begin				
        if(end_cnt_y)						
            cnt_y <= 'd0;  				
        else									
            cnt_y <= cnt_y + 1'b1;		
    end											
assign add_cnt_y = end_cnt_x;
assign end_cnt_y = add_cnt_y && cnt_y == 8 - 1;

我们将rom的address等于cnt_x+cnt_y*8即可准确表示我们在某一位显示的数据是什么。并且address也是逐渐增大,使我们可以顺序读取rom内容。

我们接下来看rom的设置:

wire    rden            ;
reg     rden_r1         ;
reg     rden_r2         ;
wire    [6:0]   address ;
wire    [23:0]  q       ;

//****************************************************************
//--例化rom
rom64	rom_inst (
.aclr       ( ~rst_n    ),
.address    ( address   ),
.clock      ( clk       ),
.rden       ( rden      ),
.q          ( q         )
);
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        rden_r1 <= 0;
        rden_r2 <= 0;
    end
    else begin
        rden_r1 <= rden;
        rden_r2 <= rden_r1;
    end
end
assign  rden = (state_c == DATA) ? 1:0;
assign  address = (rden) ? (cnt_x+cnt_y*8):0;
assign  pix_data = q;
//****************************************************************

第一个assign是我们让读使能在数据传输时候有效

第二个assign是将地址在读使能有效后等于cnt_x+cnt_y*8

第三个assign是将读取出来的数据赋值给我们要传递给驱动的信号

而为何我们需要对读使能打拍呢,因为我们romIP核会对rden寄存一次然后会对q输出时候再次寄存一次,所以说我们数据会延迟使能信号两拍,我们在该模块中定义的pix_en是:

assign  pix_en = (rden_r2) ? 1:0;   //fifo读取需要延迟两拍,所以用打拍后的使能判断

而驱动模块中的fifo写入信号与pix_en直接相关,如果直接写pix_en=rden?1:0;

我们则会向fifo中读入两bit错误数据(0)导致显示出错,所以我们需要使用打完两拍的信号去让pix_en有效。此时读入fifo的值就不会读入前两拍0;

ps:此处使用打拍信号只有pix_en,如果让地址计数器使能(前方cnt_x的使能)给打拍后的使能则会使读取到的值缺失一位,因为rden使能为组合逻辑,它打拍后本身就与时序逻辑的address同步,所以address就直接使用rden来作为使能。此处的两拍0不是指address=0和1时的值被占用为0,而是rom打拍的这两个周期没有输出,所以q输出为0,我们不需要写入这两位进入fifo,所以我们将与fifo写入有关的rden使能打两拍使在还没输出的时候(进入了读使能,但是q因为rom自身打拍而还没有输出)不读取无效的0数据。(读者使用别人定义好的ip核或者模块也需要注意该问题)

最后放上整个控制模块代码:

module ws2812_ctrl2 (
    input               clk         ,
    input               rst_n       ,
    input               ready       ,

    output              pix_en      ,
    output  [23:0]      pix_data
);

    reg	[3:0] cnt_x;
    wire	add_cnt_x;
    wire    end_cnt_x;

    reg	[3:0] cnt_y;
    wire	add_cnt_y;
    wire    end_cnt_y;	

    wire    rden            ;
    reg     rden_r1         ;
    reg     rden_r2         ;
    wire    [6:0]   address ;
    wire    [23:0]  q       ;

    //状态定义
    localparam	IDLE    =   3'b001,
                DATA    =   3'b010,
                DONE    =   3'b100;
    //状态跳转条件
    wire        idle2data   ;   //2 -> to
    wire        data2done   ;
    wire        done2idle   ;
    //状态寄存器
    reg [2:0]   state_c     ;   //current 现态
    reg [2:0]   state_n     ;   //next    次态

    //状态机第一段
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            state_c <= IDLE;
        end
        else begin
            state_c <= state_n; //次态与现态相差一个时钟周期
        end
    end 
    //状态机第二段  组合逻辑 描述次态
    always @(*) begin
        case(state_c)    //现态状态决定次态
            IDLE :
                begin
                    if(idle2data)
                        state_n = DATA;
                    else
                        state_n = state_c;
                end
            DATA   :
                begin
                    if(data2done)
                        state_n = DONE;
                    else
                        state_n = state_c;
                end
            DONE   :
                begin
                    if(done2idle)
                        state_n = IDLE;
                    else
                        state_n = state_c;
                end
            default : state_n = state_c;
        endcase
    end

    assign idle2data    =   state_c ==  IDLE    &&  ready;    //跳转条件 跳转下个状态
    assign data2done    =   state_c ==  DATA    &&  end_cnt_y;
    assign done2idle    =   state_c ==  DONE    &&  0;


    //****************************************************************
    //--例化rom
    rom64	rom_inst (
	.aclr       ( ~rst_n    ),
	.address    ( address   ),
	.clock      ( clk       ),
	.rden       ( rden      ),
	.q          ( q         )
	);

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            rden_r1 <= 0;
            rden_r2 <= 0;
        end
        else begin
            rden_r1 <= rden;
            rden_r2 <= rden_r1;
        end
    end

    assign  rden = (state_c == DATA) ? 1:0;
    assign  address = (rden) ? (cnt_x+cnt_y*8):0;
    assign  pix_data = q;
    //****************************************************************


    //****************************************************************
    //--告诉fifo可以接收数据了
    assign  pix_en = (rden_r2) ? 1:0;   //fifo读取需要延迟两拍,所以用打拍后的使能判断

    //****************************************************************

    //****************************************************************
    //--
    //行列计数器开始
    always@(posedge clk or negedge rst_n)	
        if(!rst_n)								
            cnt_x <= 'd0;						
        else    if(add_cnt_x) begin				
            if(end_cnt_x)						
                cnt_x <= 'd0;  				
            else									
                cnt_x <= cnt_x + 1'b1;		
        end											
    assign add_cnt_x = rden;
    assign end_cnt_x = add_cnt_x && cnt_x == 8 - 1;

    always@(posedge clk or negedge rst_n)	
        if(!rst_n)								
            cnt_y <= 'd0;						
        else    if(add_cnt_y) begin				
            if(end_cnt_y)						
                cnt_y <= 'd0;  				
            else									
                cnt_y <= cnt_y + 1'b1;		
        end											
    assign add_cnt_y = end_cnt_x;
    assign end_cnt_y = add_cnt_y && cnt_y == 8 - 1;
    //****************************************************************

endmodule

最后如果我们想要实现动态显示效果,我们则还需要定义一个额外的WAIT状态来实现帧与帧之间的延时(此处定义一个延时函数,计时完毕标志作为跳转回IDLE条件),读取下一帧的数据(此处为一个32x8像素的图片,我们每次显示8x8然后下一帧向右移动一像素)则对address的值进行操作:

assign  address = (rden) ? (((cnt_x+cnt_offset) % 32)+(cnt_y*32)):0;

可以看到我们定义了一个offset的偏移变量,我们在完成一帧后的DONE状态使cnt_offset加一(DONE转移到WAIT转移条件为state_c==DONE即马上跳转,所以cnt_offset只加一),而我们对于cnt_x+cnt_offset取32余数则是为了实现从图片尾部循环移动到头部,而不是到达最后一帧直接变为第一帧。

下面是动态显示控制代码:

module ws2812_ctrl3#(parameter  TIME_DELAY = 5_000_000) (
    input           clk         ,
    input           rst_n       ,
    input           ready       ,

    output          pix_en      ,
    output  [23:0]  pix_data
);

    reg [5:0] cnt_offset;
    wire add_cnt_offset;
    wire end_cnt_offset;

    reg [25:0] cnt_delay;
    wire add_cnt_delay;
    wire end_cnt_delay;

    reg	[3:0] cnt_x;
    wire	add_cnt_x;
    wire    end_cnt_x;

    reg	[3:0] cnt_y;
    wire	add_cnt_y;
    wire    end_cnt_y;	

    wire    rden            ;
    reg     rden_r1         ;
    reg     rden_r2         ;
    wire    [8:0]   address ;
    wire    [23:0]  q       ;

    //状态定义
    localparam	IDLE    =   4'b0001,
                DATA    =   4'b0010,
                DONE    =   4'b0100,
                WAIT    =   4'b1000;
    //状态跳转条件
    wire        idle2data   ;   //2 -> to
    wire        data2done   ;
    wire        done2wait   ;
    wire        wait2idle   ;
    //状态寄存器
    reg [3:0]   state_c     ;   //current 现态
    reg [3:0]   state_n     ;   //next    次态

    //状态机第一段
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            state_c <= IDLE;
        end
        else begin
            state_c <= state_n; //次态与现态相差一个时钟周期
        end
    end 
    //状态机第二段  组合逻辑 描述次态
    always @(*) begin
        case(state_c)    //现态状态决定次态
            IDLE :
                begin
                    if(idle2data)
                        state_n = DATA;
                    else
                        state_n = state_c;
                end
            DATA   :
                begin
                    if(data2done)
                        state_n = DONE;
                    else
                        state_n = state_c;
                end
            DONE   :
                begin
                    if(done2wait)
                        state_n = WAIT;
                    else
                        state_n = state_c;
                end
            WAIT    :
                begin
                    if (wait2idle) begin
                        state_n = IDLE;
                    end
                    else begin
                        state_n = state_c;
                    end
                end
            default : state_n = state_c;
        endcase
    end

    assign idle2data    =   state_c ==  IDLE    &&  ready;    //跳转条件 跳转下个状态
    assign data2done    =   state_c ==  DATA    &&  end_cnt_y;
    assign done2wait    =   state_c ==  DONE;
    assign wait2idle    =   state_c ==  WAIT    &&  end_cnt_delay;


    //****************************************************************
    //--例化rom
    rom256	rom_inst (
	.aclr       ( ~rst_n    ),
	.address    ( address   ),
	.clock      ( clk       ),
	.rden       ( rden      ),
	.q          ( q         )
	);

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            rden_r1 <= 0;
            rden_r2 <= 0;
        end
        else begin
            rden_r1 <= rden;
            rden_r2 <= rden_r1;
        end
    end

    assign  rden = (state_c == DATA) ? 1:0;
    assign  address = (rden) ? (((cnt_x+cnt_offset) % 32)+(cnt_y*32)):0;
    assign  pix_data = q;
    //****************************************************************


    //****************************************************************
    //--告诉fifo可以接收数据了
    assign  pix_en = (rden_r2) ? 1:0;

    //****************************************************************
    
    //****************************************************************
    //--
    //偏移计数器开始

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_offset <= 0;
        end
        else if(add_cnt_offset) begin
            if(end_cnt_offset) begin
                cnt_offset <=  0;
            end
            else begin
                cnt_offset <=  cnt_offset + 1'b1;
            end
        end
        else begin
            cnt_offset <= cnt_offset;
        end
    end
    
    assign add_cnt_offset = state_c == DONE;
    assign end_cnt_offset = add_cnt_offset && cnt_offset == 32 - 1'b1;
    //计数器结束
    
    //延时计数器开始
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_delay <= 0;
        end
        else if(add_cnt_delay) begin
            if(end_cnt_delay) begin
                cnt_delay <=  0;
            end
            else begin
                cnt_delay <=  cnt_delay + 1'b1;
            end
        end
        else begin
            cnt_delay <= cnt_delay;
        end
    end
    
    assign add_cnt_delay = state_c == WAIT;
    assign end_cnt_delay = add_cnt_delay && cnt_delay == TIME_DELAY - 1'b1;
    //计数器结束


    //行列计数器开始
    always@(posedge clk or negedge rst_n)	
        if(!rst_n)								
            cnt_x <= 'd0;						
        else    if(add_cnt_x) begin				
            if(end_cnt_x)						
                cnt_x <= 'd0;  				
            else									
                cnt_x <= cnt_x + 1'b1;		
        end											
    assign add_cnt_x = rden;
    assign end_cnt_x = add_cnt_x && cnt_x == 8 - 1;

    always@(posedge clk or negedge rst_n)	
        if(!rst_n)								
            cnt_y <= 'd0;						
        else    if(add_cnt_y) begin				
            if(end_cnt_y)						
                cnt_y <= 'd0;  				
            else									
                cnt_y <= cnt_y + 1'b1;		
        end											
    assign add_cnt_y = end_cnt_x;
    assign end_cnt_y = add_cnt_y && cnt_y == 8 - 1;
    //****************************************************************

endmodule

谢谢观看!!!

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值