由上述手册截图可以看出,该光源0码和1码占空比并不相同,同时一个码元持续时间也可以不同,因此在设计码型时,一个码元持续时间需要同时满足0码和1码。
数据传输方法与前文概述所说一样,没经过一个像素点便会被锁存24bit数据,然后继续逐级传输,同时每一次24bit数据传输结束需要经过经过至少280us的复位才能继续传输下一个24bit数据。
同时该光源所需24bit数据结构为GRB顺序,在设计时需要将RGB数据进行重新拼接,该拼接过程可以在传入数据时,也可以在传出数据时。
设计本项目时,建议将模块划分为:驱动模块,控制模块,以及顶层模块。
在驱动模块中,首先我们主要考虑的是64个24bit数据的传输。
由上述分析可知,每24bit数据传输间隔中我们需要至少280us的复位,复位结束才能传输下一个24bit数据,因此我们可以设计一个状态机实现数据传输与复位的状态切换。
同时我们可以设计一个ready使能信号,将该信号传输给控制模块,该信号拉高后代表驱动模块处于空闲状态,可以进入复位状态。
同时我们可以在控制模块设计一个data_vld信号,将该信号输入驱动模块,该信号拉高驱动模块应该立即进入复位状态,并在复位结束后即刻准备接受数据。
再由手册的介绍可知,在该模块我们至少需要四个计数器,分别为:
同时,由于该光源数据格式为GRB,因此我们还需要在驱动模块对传入的RGB数据重新进行拼接。
此外,由于时钟数据采用频率为50MHz,而该光源发送速率只有800Kbps,传入数据的速度远大于传出数据的速度,因此博主调用了一个FIFO核,用来临时存储传入驱动模块的数据,避免造成数据丢失。
该模块状态机设计如下:
其中,end_cnt_rst为复位计数器结束信号,end_cnt_pix为64个像素点计数器结束信号。
在该模块我们所需要的考虑仅为所需数据的存储以及如何传入驱动模块。
打开系统自带的画图工具,点击左上角文件,选择图像属性
在图像属性中设置图像为 32×8(大家可以根据自己所需自由设计宽度(如果自定义宽度,就需要在ROM核设置的时候进行更改,同时需要在博主的数据控制模块进行一个小细节的更改,博主会在后文标明),但高度固定因为动态显示相当于滚动移动宽度,无法移动高度)
然后在画图中设计自己想要显示的图案即可
设计完成后点击右上角文件另存为24位宽BMP文件,注意必须为24位宽
然后利用格式转换工具,将该BMP文件转化为mif文件
然后将生成的文件存放在自己创建的quartus prj文件夹下
在quartus IP Catalog中搜索ROM并选择单端口ROM
按照下图进行设置:
点击next,按下图进行如下设置:
一路next至总结界面:
最终设置如下:
点击finish完成ROM IP核配置
在IP Catalog中搜索FIFO,进入配置选项卡
点击next,按下图进行设置:
点击next,按下图进行配置:
一路next至总结界面:
FIFO总体配置如下:
驱动模块:
module ws2812_driver (
input wire clk ,
input wire rst_n ,
input wire [23:0] pix_data ,//输入数据
input wire pix_data_vld,//输入数据有效信号
output wire ready ,
output reg ws2812_io //输出码元信号
);
//内部参数定义
//状态参数定义
parameter IDLE = 3'b001 ,
RST = 3'b010 ,
DATA = 3'b100 ;
//码元电平参数定义
parameter T0H = 300/20 ,
T0L = 900/20 ,
T1H = 600/20 ,
T1L = 600/20 ;
//复位码元计数参数
parameter RST_MAX = 14'd15_000;
//内部信号定义
//现态、次态寄存器
reg [2:0] cstate ;
reg [2:0] nstate ;
//跳转条件定义
wire idle2rst ;
wire rst2data ;
wire data2idle ;
//一个码元时间计数器
reg [5:0] cnt_time ;
wire add_cnt_time ;
wire end_cnt_time ;
//24bit计数器
reg [4:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
//64个数据计数器
reg [5:0] cnt_pix ;
wire add_cnt_pix ;
wire end_cnt_pix ;
//复位码元计数器
reg [13:0] cnt_reset ;
wire add_cnt_reset ;
wire end_cnt_reset ;
//FIFO信号定义
wire [23:0] fifo_wr_data ;
wire fifo_rd_req ;
wire fifo_wr_req ;
wire fifo_empty ;
wire fifo_full ;
wire [23:0] fifo_rd_data ;
wire [7:0] fifo_usedw ;
//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 ( fifo_usedw )
);
assign fifo_wr_data = {pix_data[15:8],pix_data[23:16],pix_data[7:0]};//将传入的24bit RGB数据转为GRB顺序//24'hff0000;
assign fifo_wr_req = pix_data_vld && ~fifo_full;//1'b1 && ~fifo_full;//
assign fifo_rd_req = end_cnt_bit && ~fifo_empty;
//三段式状态机
//第一段
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(idle2rst)begin
nstate = RST;
end
else begin
nstate = cstate;
end
end
RST : begin
if(rst2data)begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA : begin
if(data2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate <= cstate;
endcase
end
assign idle2rst = cstate == IDLE && pix_data_vld ;//1'b1;//
assign rst2data = cstate == RST && end_cnt_reset ;
assign data2idle = cstate == DATA && end_cnt_pix ;
//码元最低持续时间计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_time <= 'd0;
end
else if(add_cnt_time)begin
if(end_cnt_time)begin
cnt_time <= 'd0;
end
else begin
cnt_time <= cnt_time + 1'b1;
end
end
end
assign add_cnt_time = cstate == DATA;
assign end_cnt_time = add_cnt_time && cnt_time == 6'd59;
//一位数据的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'b1;
end
end
end
assign add_cnt_bit = end_cnt_time;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 5'd23;
//64个数据计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_pix <= 'd0;
end
else if(add_cnt_pix)begin
if(end_cnt_pix)begin
cnt_pix <= 'd0;
end
else begin
cnt_pix <= cnt_pix + 1'b1;
end
end
end
assign add_cnt_pix = end_cnt_bit;
assign end_cnt_pix = add_cnt_pix && cnt_pix == 6'd63;
//复位计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_reset <= 'd0;
end
else if(add_cnt_reset)begin
if(end_cnt_reset)begin
cnt_reset <= 'd0;
end
else begin
cnt_reset <= cnt_reset + 1'b1;
end
end
end
assign add_cnt_reset = cstate == RST;
assign end_cnt_reset = add_cnt_reset && cnt_reset == RST_MAX - 1'b1;
//第三段 状态机输出
always@(*)begin
case(cstate)
IDLE : ws2812_io = 1'b0;
RST : ws2812_io = 1'b0;
DATA : begin
if(fifo_rd_data[23 - cnt_bit])begin
if(cnt_time < T1H)begin
ws2812_io = 1'b1;
end
else begin
ws2812_io = 1'b0;
end
end
else begin
if(cnt_time < T0H)begin
ws2812_io = 1'b1;
end
else begin
ws2812_io = 1'b0;
end
end
end
default : ws2812_io = 1'b0;
endcase
end
//ready使能信号赋值
assign ready = cstate == IDLE;
endmodule
数据控制模块:
/**************************************************************
@File : ws2812_control.v
@Time : 2023/08/11 15:09:52
@Author : majiko
@EditTool: VS Code
@Font : UTF-8
@Function: 提供64个RGB像素数据,给到ws2812接口模块,用于验证接口模块
**************************************************************/
module ws2812_ctrl2(
input clk ,
input rst_n ,
input ready ,//可以接收图像数据了
output [23:0] pix_data ,
output pix_data_vld
);
parameter IDLE = 0,
DATA = 1,
DELAY = 2;
parameter DELAY_TIME = 25'd25_000_000;
wire rom_rd_req ;
reg rom_rd_req1 ;
reg rom_rd_req2 ;
wire rom_rd_data ;
reg [2:0] state ;
reg [5:0] cnt_x ;
wire add_x_cnt;
wire end_x_cnt;
reg [4:0] cnt_y ;
wire add_y_cnt;
wire end_y_cnt;
reg [4:0] cnt_offset ;
wire add_cnt_offset ;
wire end_cnt_offset ;
reg [24:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
// localparam RED = 24'hFF0000, //红色
// ORANGE = 24'hFF8000, //橙色
// YELLOW = 24'hFFFF00, //黄色
// GREEN = 24'h00FF00, //绿色
// CYAN = 24'h00FFFF, //青色
// BLUE = 24'h0000FF, //蓝色
// PURPPLE = 24'h8000FF, //紫色
// BLACK = 24'h000000, //黑色
// WHITE = 24'hFFFFFF, //白色
// GRAY = 24'hC0C0C0; //灰色
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(ready)
state <=DATA;
DATA : if(end_y_cnt)
state <=DELAY;
DELAY : if(end_cnt_delay)
state <=IDLE;
default : state <= IDLE;
endcase
//帧间隔计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
end
assign add_cnt_delay = state == DELAY;
assign end_cnt_delay = add_cnt_delay && cnt_delay == DELAY_TIME - 1;
/**************************************************************
图像数据个数计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_x <= 'd0;
else if(add_x_cnt) begin
if(end_x_cnt)
cnt_x <= 'd0;
else
cnt_x <= cnt_x + 1'b1;
end
assign add_x_cnt = state == DATA;
assign end_x_cnt = add_x_cnt && cnt_x == 8 - 1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_y <= 'd0;
else if(add_y_cnt) begin
if(end_y_cnt)
cnt_y <= 'd0;
else
cnt_y <= cnt_y + 1'b1;
end
assign add_y_cnt = end_x_cnt;
assign end_y_cnt = add_y_cnt && cnt_y == 8 - 1;
//偏移计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_offset <= 'd0;
end
else if(add_cnt_offset)begin
if(end_cnt_offset)begin
cnt_offset <= 'd0;
end
else begin
cnt_offset <= cnt_offset + 1'b1;
end
end
end
assign add_cnt_offset = end_cnt_delay;
assign end_cnt_offset = add_cnt_offset && cnt_offset == 5'd23;
// assign pix_data_vld = add_x_cnt;
// always@(*)
// case(cnt_y)
// 0 : pix_data = RED ;
// 1 : pix_data = ORANGE ;
// 2 : pix_data = YELLOW ;
// 3 : pix_data = GREEN ;
// 4 : pix_data = CYAN ;
// 5 : pix_data = BLUE ;
// 6 : pix_data = PURPPLE ;
// 7 : pix_data = GRAY ;
// default : pix_data = RED ;
// endcase
wire [4:0] real_row ;
assign real_row = cnt_x + cnt_offset;
rom rom_inst (
.aclr ( ~rst_n ),
.address ( cnt_y*32 + real_row),
.clock ( clk ),
.rden ( rom_rd_req ),
.q ( pix_data )
);
//ROM读请求打两拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rom_rd_req1 <= 1'b0;
rom_rd_req2 <= 1'b0;
end
else begin
rom_rd_req1 <= rom_rd_req;
rom_rd_req2 <= rom_rd_req1;
end
end
assign rom_rd_req = state == DATA;
assign rom_rd_data = rom_rd_req2 ;
assign pix_data_vld = rom_rd_data ;
endmodule
顶层模块:
module ws2812_top(
input wire clk ,
input wire rst_n ,
output wire ws2812_io
);
wire [23:0] pix_data ;
wire ready ;
wire pix_data_vld;
//模块例化
//显示图片
// ws2812_ctrl u_ws2812_ctrl(
// .clk (clk ),
// .rst_n (rst_n ),
// .pix_data (pix_data ),
// .pix_data_vld (pix_data_vld ),
// .ready (ready )
// );
//动态显示图片
ws2812_ctrl2 u_ws2812_ctrl2(
.clk (clk ),
.rst_n (rst_n ),
.pix_data (pix_data ),
.pix_data_vld (pix_data_vld ),
.ready (ready )
);
ws2812_driver u_ws_2812_driver(
.clk (clk ),
.rst_n (rst_n ),
.pix_data (pix_data ),
.pix_data_vld (pix_data_vld),
.ready (ready ),
.ws2812_io (ws2812_io )
);
endmodule
引脚绑定如下:
实现效果:
博主本人为FPGA初学者,因此才会写下此篇博客作为自己对WS2812的一个总结,如果有和博主一样的初学者,希望此博文能够帮助到你。
这也是博主第一次利用FPGA驱动外设,第一次设计一个较为简单的单总线通讯协议,尽管很大程度博主都是依靠着博主老师的代码才完成的。
如果此篇博文有任何错误,还请各位提出!