一,系统架构
二,芯片介绍
1.管脚说明
2.数据传输时间
3.时序波形
4.数据传输方法
5.常用电路连接
三,代码展示及说明
- 驱动模块
在驱动模块首先选择使用状态机,其中包括,空闲状态,复位清空状态,和读数据状态,其中空闲状态是向fifo中写入数据,复位清空状态是清空ws2812b中的数据,读数据状态是讲fifo中存的数据依次读到ws2812b中,以这样的流程来达到对ws2812b的控制,以下是我的驱动代码:
/**************************************功能介绍***********************************
Date :
Author : WZY.
Version :
Description: 这是项目的逻辑状态机模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module state(
input wire clk ,
input wire rst_n ,
input wire [23:0] data_in ,
input wire fifo_wr_vld,
output reg ws2812b_io ,
output wire ready
);
//---------<参数定义>---------------------------------------------------------
//状态机参数定义
parameter IDLE = 3'b001,//空闲状态
RST = 3'b010,//复位状态
DATA = 3'b100;//数据传输状态
//---------<内部信号定义>-----------------------------------------------------
reg [2:0] cstate ;//现态
reg [2:0] nstate ;//次态
wire idle2rst ;
wire rst2data ;
wire data2idle ;
//fifoIP核参数定义
wire [23:0] fifo_wr_data;
wire [23:0] fifo_rd_data;
wire empty ;
wire full ;
wire fifo_rd_req ;
wire fifo_wr_req ;
//复位参数定义
reg [14:0] cnt_rst ;
wire add_cnt_rst ;
wire end_cnt_rst ;
//数据传输参数定义
reg [5:0] cnt_cyc ;
wire add_cnt_cyc ;
wire end_cnt_cyc ;
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 ;
//****************************************************************
// 状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
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 = IDLE;
endcase
end
assign idle2rst = cstate == IDLE && fifo_wr_vld;//当检测到读使能时由空闲状态转换到复位状态
assign rst2data = cstate == RST && end_cnt_rst;//当复位完成后转到数据输入状态
assign data2idle = cstate == DATA && end_cnt_num;//当数据输入完成后返回空闲状态
//第三段:描述输出,时序逻辑或组合逻辑皆可
//****************************************************************
// IP核FIFO读取
//****************************************************************
fifo_test fifo_test_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( fifo_wr_data ),
.rdreq ( fifo_rd_req ),
.wrreq ( fifo_wr_req),
.empty ( empty ),
.full ( full ),
.q ( fifo_rd_data ),
.usedw ( )
);
assign fifo_wr_data = {data_in[15:8],data_in[23:16],data_in[7:0]} ;//RGB->GRB
assign fifo_wr_req = fifo_wr_vld&&~full;//当检测到写使能并且不为满时拉高
assign fifo_rd_req = end_cnt_bit&& ~empty;//每次读取计时到一个数据后并且不为空时读出一个数据
//****************************************************************
// 复位计时
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_rst <= 15'd0;
end
else if(add_cnt_rst)begin
if(end_cnt_rst)begin
cnt_rst <= 15'd0;
end
else begin
cnt_rst <= cnt_rst + 1'b1;
end
end
end
assign add_cnt_rst = cstate == RST;
assign end_cnt_rst = add_cnt_rst && cnt_rst == 400_000/20 - 1;
//****************************************************************
// 数据传输计时
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_cyc <= 6'd0;
end
else if(add_cnt_cyc)begin
if(end_cnt_cyc)begin
cnt_cyc <= 6'd0;
end
else begin
cnt_cyc <= cnt_cyc + 1'b1;
end
end
end
assign add_cnt_cyc = cstate == DATA;
assign end_cnt_cyc = add_cnt_cyc && cnt_cyc == 1200/20 - 1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 5'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 5'd0;
end
else begin
cnt_bit <= cnt_bit + 1'b1;
end
end
end
assign add_cnt_bit = end_cnt_cyc;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 24-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_num <= 6'd0;
end
else if(add_cnt_num)begin
if(end_cnt_num)begin
cnt_num <= 6'd0;
end
else begin
cnt_num <= cnt_num + 1'b1;
end
end
end
assign add_cnt_num = end_cnt_bit;
assign end_cnt_num = add_cnt_num && cnt_num == 64-1;
//****************************************************************
// 用户接口
//****************************************************************
always @(posedge clk or negedge rst_n) begin
case (cstate)
IDLE : ws2812b_io = 0;
RST : ws2812b_io = 0;
DATA : begin
if (fifo_rd_data[23-cnt_bit] == 1) begin
ws2812b_io = (cnt_cyc <30)?1:0;
end
else begin
ws2812b_io = (cnt_cyc<15)?1:0;
end
end
default: ws2812b_io = 0;
endcase
end
//****************************************************************
// ready控制
//****************************************************************
assign ready = cstate == IDLE;
endmodule
- 数据传输
接下来是数据选择传输模块,这个模块同样使用到了ip核以及状态机,通过三个状态,空闲状态,读数据状态,以及延迟状态,其中空闲状态下,等待驱动模块准备完成跳转到读数据状态,在读数据状态下根据三个计数器即横坐标,纵坐标还有偏移坐标来作为ROM的地址,依次向外读出数据,同时驱动模块向fifo中写入数据,当读取完64个数据后跳转到等待状态,延迟500ms后再次回到IDLE状态并且偏移坐标+1,以此循环就可以达到动态显示,下面是代码展示:
/**************************************************************
@File : ws2812_control2.v
@Time : 2023/08/14 10:04:56
@Author : WangHaodong
@EditTool: VS Code
@Font : UTF-8
@Function: 显示一张图片
**************************************************************/
module ws2812_control(
input clk ,
input rst_n ,
output [23:0] pix_data ,
output pix_data_vld ,
input ready //可以接收图像数据了
);
parameter IDLE = 0,
DATA = 1,
DELAY = 2;
reg [2:0] state ;
reg [5:0] cnt_x;
wire add_x_cnt,end_x_cnt;
reg [4:0] cnt_y;
wire add_y_cnt,end_y_cnt;
reg [24:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
reg [5:0] cnt_offset ;
wire add_cnt_offset ;
wire end_cnt_offset ;
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; //灰色
parameter MAX_500S = 24_999_999;
wire rom_rd_req ;
wire rom_rd_data_vld ;
reg rom_rd_req_r1 ;
reg rom_rd_req_r2 ;
/**************************************************************
状态机
**************************************************************/
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
else begin
cnt_delay <= 0;
end
end
assign add_cnt_delay = state == DELAY;
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX_500S;
/**************************************************************
图像数据个数计数器
**************************************************************/
//横坐标
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 == 31;
wire[4:0] num_x;
assign num_x = (cnt_x+cnt_offset)%32;//避免超出32的坐标限制
//存放了一张图片
rom rom_inst (
.aclr ( ~rst_n ),
.address ( cnt_y*32+num_x ),
.clock ( clk ),
.rden (rom_rd_req),
.q (pix_data)
);
assign rom_rd_req = state == DATA;
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
rom_rd_req_r1 <= 0;
rom_rd_req_r2 <= 0;
end
else begin
rom_rd_req_r1 <= rom_rd_req;
rom_rd_req_r2 <= rom_rd_req_r1;
end
assign rom_rd_data_vld = rom_rd_req_r2;
assign pix_data_vld = rom_rd_data_vld;//打两拍使得读出数据和fifo中写入数据同步
endmodule
3.仿真演示
仿真代码:
`timescale 1ns/1ns
module state_tb();
//激励信号定义
reg clk ;
reg rst_n ;
//输出信号定义
wire ws2812b_io ;
//时钟周期参数定义
parameter CYCLE = 20;
defparam top_inst.ws2812_control_inst.MAX_500S = 10*CYCLE;
//模块例化
top top_inst
(
.clk (clk),
.rst_n (rst_n),
.ws2812b_io (ws2812b_io)
);
//产生时钟
initial clk = 1'b1;
always #(CYCLE/2) clk = ~ clk;
//产生激励
initial begin
rst_n = 1'b1;
#(CYCLE*2);
rst_n = 1'b0;
#(CYCLE*20);
rst_n = 1'b1;
#(CYCLE*1000000);
$stop;
end
endmodule
仿真结果展示: