基于FPGA驱动WS2812B

2023-10-15 09:10
文章标签 驱动 fpga ws2812b

本文主要是介绍基于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) 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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/216757

相关文章

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

驱动(RK3588S)第七课时:单节点设备树

目录 需求一、设备树的概念1、设备树的后缀名:2、设备树的语法格式3、设备树的属性(重要)4、设备树格式举例 二、设备树所用函数1、如何在内核层种获取设备树节点:2、从设备树上获取 gpio 口的属性3、获取节点上的属性只针对于字符串属性的4、函数读取 np 结点中的 propname 属性的值,并将读取到的 u32 类型的值保存在 out_value 指向的内存中,函数的返回值表示读取到的

驱动安装注册表指令

HKCR: HKEY_CLASSES_ROOT HKCU: HKEY_CURRENT_USER HKLM: HKEY_LOCAL_MACHINE HKU: HEKY_USER HER: 相对根键

UMDF驱动安装

VS2013 + WDF8.1,UMDF驱动选择User Mode Driver,不要选User Mode Driver 2.0,否则Win7安装有问题,如图 另外,在驱动安装时不要忘记WUDFUpdate_<主版本号><次版本号>.dll文件,具体文件名在INF中查找。此文件可在WDF的安装目录中找到。注意:在WDF的安装目录中会有3个WUDFUpdate_xxx.dll文件,x86,x6

FPGA编译与部署方法全方位介绍

FPGA编译与部署是FPGA开发中的核心环节,涉及从代码编写、调试到将设计部署到FPGA硬件的全过程。这个流程需要经过创建项目、编写FPGA VI、模拟调试、编译生成比特流文件,最后将设计部署到硬件上运行。编译的特点在于并行执行能力、定制化硬件实现以及复杂的时钟管理。通过LabVIEW的FPGA模块和NI硬件,可以快速完成开发和部署,尤其适用于复杂控制与高性能数据处理系统。 1. FPG

FPGA开发:条件语句 × 循环语句

条件语句 if_else语句 if_else语句,用来判断是否满足所给定的条件,根据判断的结果(真或假)决定执行给出的两种操作之一。 if(表达式)语句; 例如: if(a>b) out1=int1; if(表达式)         语句1; else         语句2; 例如: if(a>b)out1=int1;elseout1=int2; if(表达式1) 语句1; els

电脑驱动分类

电脑驱动程序(驱动程序)是操作系统与硬件设备之间的桥梁,用于使操作系统能够识别并与硬件设备进行通信。以下是常见的驱动分类: 1. 设备驱动程序 显示驱动程序:控制显卡和显示器的显示功能,负责图形渲染和屏幕显示。 示例:NVIDIA、AMD 显示驱动程序。打印机驱动程序:允许操作系统与打印机通信,控制打印任务。 示例:HP、Canon 打印机驱动程序。声卡驱动程序:管理音频输入和输出,与声卡硬件

麒麟系统安装GPU驱动

1.nvidia 1.1显卡驱动 本机显卡型号:nvidia rtx 3090 1.1.1下载驱动 打开 https://www.nvidia.cn/geforce/drivers/ 也可以直接使用下面这个地址下载 https://www.nvidia.com/download/driverResults.aspx/205464/en-us/ 1.1.3安装驱动 右击,

FPGA开发:模块 × 实例化

模块的结构 对于C语言,其基本单元为函数。与此类似,Verilog的基本设计单元称之为"模块"(block)。对于整个项目的设计思想就是模块套模块。 一个模块由两个部分组成:一部分描述接口,一部分描述逻辑功能。 每个Verilog模块包含4个部分:端口定义、IO说明、内部信号声明、功能定义。且位于module和endmodule之间,如下: module block(a,b,c);inpu

windows10 卸载网络驱动以及重新安装

右键桌面此电脑的图标,点击管理,设备管理器—网络适配器,找到下图中的驱动(不同的系统或者显卡会导致网卡驱动名称与下图不一样,多为Realtek开头),右键选择卸载设备,然后重启电脑,系统会自动重新安装驱动 新电脑首次安装驱动: 根据主板厂家,比如华硕,进入华硕官网,点击服务支持,点击下载中心,选择型号,点击右侧驱动程序和工具软件,选择windows版本,下载相应的驱动,下载完之后在对应文件中找