本文主要是介绍基于FPGA驱动WS2812B,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
先丢手册
C2976072_发光二极管-LED_WS281...SEMI发光二极管_LED规格书.PDF https://www.aliyundrive.com/s/PN49tZmA6zk 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。
手册详解
在驱动ws2812b之前,我们先需要对手册上的知识进行一个梳理:
产品概述与特点(特点有一些注意事项也值得注意,但是我们在这主要先学会使用)我们略过,我们来看它的引脚及其功能
我们可以发现它有四个引脚,分别为电源和地以及输入输出口,然后我们再看最大额定值,电源电压是在3.7到5.3V,我们fpga一般引脚引出的电压为3.3V,所以我们需要设置一下电压值,或者直接找一些特定的5VCC IO口,输入逻辑电压为-0.3V到VDD+0.7V,我们继续往下看高低电平的逻辑电压要求(温度一般都满足,可以了解即可):
我们可以发现该LED灯可以承受的电流最大为+-1uA,我们一般只要加上的电压符合的话电流也会满足。我们看到高电平输入最小要2.7V最大不超过VDD+0.7V我们FPGA输出的3.3V是可以支持的,然后我们看到低电平要求-0.3V到0.7V,我们FPGA提供的低电平一般为0V也是符合要求的。我们继续往下看。
开关特性与LED特性我们也了解即可。
下面来到最重要的,WS2812B不是我们平常LED灯给高电平就亮,给低电平就熄灭,它对于高低电平有一定的要求,我们需要对信号进行调制。
手册这里对0,1,重置的码型要求都做出了要求,我们观察表格后将0与1的单比特长度人为都设定为1200ns方便我们编写代码。我们设定的时间如下:
连接方式图中也有告诉,如果是自己购买灯珠连接务必要按照手册连接。然后我们手册下方便介绍了我们传输数据的要求:
我们可以了解到我们发送数据后,第一个灯珠会将前收到的24bit给截获,然后向下一个灯珠发送整形后的数据,我们对于一个阵列只需要将每一个的灯珠信号给顺序连接发出即可。然后还有一个很重要的是灯珠接收的数据结构是GRB结构,且为高位先发,这一点很重要,有一些没有注意直接将RGB发出会出现错误。
代码构思
我们分析完毕了手册,我们接下来就该构思我们的工程模块该如何分配,我们通过手册可以知道,驱动WS2812B的01信号都需要进行编码,所以我们肯定需要一个代码模块来进行该操作,然后还需要把接收到的RGB信号(为何使用RGB信号?->因为我们一般提供的信息都是为RGB格式,人为转化会很麻烦且可能出错,所以我们直接统一对信号进行整型就可以了)进行GRB整型。而把信号连续发送过去我们也能够想到FIFO这个模块,我们可以把我们手上有的串联起来的灯珠所需要的全部信号从高位开始写入FIFO然后再读取FIFO中数据即可,就不用人为去思考算法写入。而以上关于驱动WS2812S的模块我们可以作为一个单独的驱动模块,而我们想要显示的数据及其用户控制我们单独作为另一个模块,与驱动模块分离,有利于我们编写代码和代码维护。
我们接下来开始小模块的构思:
驱动模块:
我们发现WS2812B工作分为初始未工作,重置,然后进行数据写入,写入完后又进入初始,如需再次写入则重复以上过程,所以我们很自然的可以想到使用状态机,我们定义一个初始形态IDLE,一个复位RES,一个数据传输DATA三个状态之间进行转换
我们初始状态直接等待外部我们控制信号及其数据传入,所以我们将一个外部使能pix_en作为我们的跳转条件进入RES,而进入RES后我们便进行RES要求的时长(定义计数器来延时)进行0输出给灯珠进行重置,在延时结束时候进入数据传输DATA状态。而最重要的DATA中包含着我们码型变换。
我们现在说一说我们实现码型转换的原理,笔者这里使用的硬件为64个灯珠的连接构成8x8的点阵,每一个灯珠要接收24bit信号一共需要发送64个24bit信号,所以我们可以使用三个计数器的串联来实现这个效果:
//bit时间计数器开始
always @(posedge clk or negedge rst_n) beginif(!rst_n) begincnt_bit_time <= 0;endelse if(add_cnt_bit_time) beginif(end_cnt_bit_time) begincnt_bit_time <= 0;endelse begincnt_bit_time <= cnt_bit_time + 1'b1;endendelse begincnt_bit_time <= cnt_bit_time;end
endassign 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) beginif(!rst_n) begincnt_bit_num <= 0;endelse if(add_cnt_bit_num) beginif(end_cnt_bit_num) begincnt_bit_num <= 0;endelse begincnt_bit_num <= cnt_bit_num + 1'b1;endendelse begincnt_bit_num <= cnt_bit_num;end
endassign 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) beginif(!rst_n) begincnt_frame <= 0;endelse if(add_cnt_frame) beginif(end_cnt_frame) begincnt_frame <= 0;endelse begincnt_frame <= cnt_frame + 1'b1;endendelse begincnt_frame <= cnt_frame;end
endassign 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 帧单位低电平时间300uslocalparam T0H_TIME = 15,T1H_TIME = 30;
我们分别也使用两个计数器来实现01调制需要时间的延时,当在IDLE和RES状态时候我们给IO口都输出一个常规低电平信号,在DATA中我们对0信号调制是在T0H计时器还没有记满时候为高电平,记满后为低电平,而1信号调制则是在T1H还没计满时为高电平,记满后为低电平,代码如下:
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginws2812_io <= 0;endelse begincase (state_c)IDLE: ws2812_io <= 0;RES : ws2812_io <= 0;DATA: beginif (~fifo_rd_data[23-cnt_bit_num]) beginif (cnt_bit_time < T0H_TIME) beginws2812_io <= 1;endelse beginws2812_io <= 0;endendelse beginif (cnt_bit_time < T1H_TIME) beginws2812_io <= 1;endelse beginws2812_io <= 0;endendenddefault: ws2812_io <= 0;endcaseend
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 -> towire 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 帧单位低电平时间300uslocalparam 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) beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n; //次态与现态相差一个时钟周期endend //状态机第二段 组合逻辑 描述次态always @(*) begincase(state_c) //现态状态决定次态IDLE :beginif(idle2res)state_n = RES;elsestate_n = state_c;endRES :beginif(res2data)state_n = DATA;elsestate_n = state_c;endDATA :beginif(data2idle)state_n = IDLE;elsestate_n = state_c;enddefault : state_n = state_c;endcaseendassign 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) beginif(!rst_n) begincnt_res <= 0;endelse if(add_cnt_res) beginif(end_cnt_res) begincnt_res <= 0;endelse begincnt_res <= cnt_res + 1'b1;endendelse begincnt_res <= cnt_res;endendassign 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) beginif(!rst_n) begincnt_bit_time <= 0;endelse if(add_cnt_bit_time) beginif(end_cnt_bit_time) begincnt_bit_time <= 0;endelse begincnt_bit_time <= cnt_bit_time + 1'b1;endendelse begincnt_bit_time <= cnt_bit_time;endendassign 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) beginif(!rst_n) begincnt_bit_num <= 0;endelse if(add_cnt_bit_num) beginif(end_cnt_bit_num) begincnt_bit_num <= 0;endelse begincnt_bit_num <= cnt_bit_num + 1'b1;endendelse begincnt_bit_num <= cnt_bit_num;endendassign 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) beginif(!rst_n) begincnt_frame <= 0;endelse if(add_cnt_frame) beginif(end_cnt_frame) begincnt_frame <= 0;endelse begincnt_frame <= cnt_frame + 1'b1;endendelse begincnt_frame <= cnt_frame;endendassign 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) beginif(!rst_n) beginws2812_io <= 0;endelse begincase (state_c)IDLE: ws2812_io <= 0;RES : ws2812_io <= 0;DATA: beginif (~fifo_rd_data[23-cnt_bit_num]) beginif (cnt_bit_time < T0H_TIME) beginws2812_io <= 1;endelse beginws2812_io <= 0;endendelse beginif (cnt_bit_time < T1H_TIME) beginws2812_io <= 1;endelse beginws2812_io <= 0;endendenddefault: ws2812_io <= 0;endcaseendendassign 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) beginif(!rst_n) beginrden_r1 <= 0;rden_r2 <= 0;endelse beginrden_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 -> towire data2done ;wire done2idle ;//状态寄存器reg [2:0] state_c ; //current 现态reg [2:0] state_n ; //next 次态//状态机第一段always @(posedge clk or negedge rst_n) beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n; //次态与现态相差一个时钟周期endend //状态机第二段 组合逻辑 描述次态always @(*) begincase(state_c) //现态状态决定次态IDLE :beginif(idle2data)state_n = DATA;elsestate_n = state_c;endDATA :beginif(data2done)state_n = DONE;elsestate_n = state_c;endDONE :beginif(done2idle)state_n = IDLE;elsestate_n = state_c;enddefault : state_n = state_c;endcaseendassign idle2data = state_c == IDLE && ready; //跳转条件 跳转下个状态assign data2done = state_c == DATA && end_cnt_y;assign done2idle = state_c == DONE && 0;//****************************************************************//--例化romrom64 rom_inst (.aclr ( ~rst_n ),.address ( address ),.clock ( clk ),.rden ( rden ),.q ( q ));always @(posedge clk or negedge rst_n) beginif(!rst_n) beginrden_r1 <= 0;rden_r2 <= 0;endelse beginrden_r1 <= rden;rden_r2 <= rden_r1;endendassign 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 -> towire data2done ;wire done2wait ;wire wait2idle ;//状态寄存器reg [3:0] state_c ; //current 现态reg [3:0] state_n ; //next 次态//状态机第一段always @(posedge clk or negedge rst_n) beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n; //次态与现态相差一个时钟周期endend //状态机第二段 组合逻辑 描述次态always @(*) begincase(state_c) //现态状态决定次态IDLE :beginif(idle2data)state_n = DATA;elsestate_n = state_c;endDATA :beginif(data2done)state_n = DONE;elsestate_n = state_c;endDONE :beginif(done2wait)state_n = WAIT;elsestate_n = state_c;endWAIT :beginif (wait2idle) beginstate_n = IDLE;endelse beginstate_n = state_c;endenddefault : state_n = state_c;endcaseendassign 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;//****************************************************************//--例化romrom256 rom_inst (.aclr ( ~rst_n ),.address ( address ),.clock ( clk ),.rden ( rden ),.q ( q ));always @(posedge clk or negedge rst_n) beginif(!rst_n) beginrden_r1 <= 0;rden_r2 <= 0;endelse beginrden_r1 <= rden;rden_r2 <= rden_r1;endendassign 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) beginif(!rst_n) begincnt_offset <= 0;endelse if(add_cnt_offset) beginif(end_cnt_offset) begincnt_offset <= 0;endelse begincnt_offset <= cnt_offset + 1'b1;endendelse begincnt_offset <= cnt_offset;endendassign 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) beginif(!rst_n) begincnt_delay <= 0;endelse if(add_cnt_delay) beginif(end_cnt_delay) begincnt_delay <= 0;endelse begincnt_delay <= cnt_delay + 1'b1;endendelse begincnt_delay <= cnt_delay;endendassign 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
谢谢观看!!!
这篇关于基于FPGA驱动WS2812B的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!