FPGA学习——驱动WS2812B
FPGA驱动WS2812B
近期准备复现紫光的视频氛围灯,现在学习了如何驱动WS2812B
将实现任意灯的点亮以及流水实现。
原理
这个灯带每个灯珠里面都有一个芯片控制,只要按照一定的时序发送数据即可控制其亮灭。
只要把24位RGB一位一位发送出去即可,也就是只要发送0码或者1码,按顺序发送24个即可。
下面是发送时序。
很明显 0码和1码并不是说给个低电平或者高电平就行,而是给一个占空比不同的pwm波,比如0码就是给220ns380ns的高电平再给580ns1.6us的低电平即可。1码同理。 而reset码就是280us的低电平。
可以先发送N组24bit的数据再接一个reset信号表示一组结束
高位在前低位在后格式是GRB。
代码
代码也参考了别人的设计框架,利用三段式状态机,只要修改一些参数即可实现点亮任意灯和流水的间隔。
//GRB顺序点亮
//实现任意灯珠的点亮
//同时实现流水灯
module ws_2812_crtl
(
input wire clk ,
input wire rst ,
//input wire [23:0] led_state ,
output wire led //输出led信号
);
//灯珠数量
parameter LED_NUM =5;
parameter FREQ =50_000_0; //100ms加一次 也就是频率10HZ
wire clk_74;
wire locked;
clk_wiz_0 clk_74_25
(
// Clock out ports
.clk_out1(clk_74), // output clk_out1
// Status and control signals
.reset(~rst), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1(clk)); // input clk_in1
//灯珠计数
reg [23:0] led_cnt;
//时序参数定义 一共是67 50是1us 17 是340ns
parameter T0H = 6'd17;
parameter T0L = 6'd50;//0码的高低电平时间
parameter T1H = 6'd50;
parameter T1L = 6'd17;//1码
parameter RST = 14'd15000;//复位时间
//灯的亮度
reg [24:0] led_brink ;
reg [31:0] led_brink_cnt ;
//状态机 一个复位状态
parameter IDLE = 16'b0000_0000_0000_0000;
parameter LED_start = 16'b0000_0000_0000_0001;
parameter RST_FSM = 16'b1000_0000_0000_0000;
//三段式状态机
reg [15:0] state;
reg [15:0] state_n;//状态
reg [6:0] cycle_cnt;//周期计数
reg led_pwm;// Led 输出
reg shift;//24位移位输出led色彩
reg state_tran;//状态转移第一个led到最后一个
reg state_tran_rst;//状态转移,复位至LEd+
reg [13:0] rst_cnt;//复位输出
reg [4:0] bit_cnt;//对24位进行计数移位
//输出
assign led = led_pwm;
//LEd数据高低周期,一个0/1码持续时间 控制时序
always @(posedge clk_74 or negedge rst)
begin
if(!rst)
cycle_cnt <= 7'd0;
else if(cycle_cnt == (T0H + T0L - 6'd1)) //一个周期到达
cycle_cnt <= 7'd0;
else if(state != RST_FSM)//一轮传输下来,复位后对齐
cycle_cnt <= cycle_cnt + 1'b1;
else
cycle_cnt <= 7'd0;
end
//三段式状态机
//FSM 1
always @ (posedge clk_74 or negedge rst)
begin
if(!rst)
state <= IDLE;
else
state <= state_n;
end
//寄存频率
reg led_freq;
//RGB灯赋值
always@ (posedge clk or negedge rst)
begin
if(!rst)
led_brink_cnt <= 24'd0;
else if(led_brink_cnt == FREQ-1)
led_brink_cnt <= 24'd0;
else
led_brink_cnt <= led_brink_cnt+1'b1;
end
//流水控制步长
always@ (posedge clk or negedge rst)
begin
if(!rst)
led_brink <= 25'd0;
else if(led_brink_cnt == FREQ-1)
led_brink <= led_brink+1'b1;
end
//FSM 2 状态输出
always @ (posedge clk or negedge rst)
begin
if(!rst)
begin
led_pwm <= 1'b0;
shift <= 1'b0;
end
else
begin
case(state)
IDLE :
begin
led_pwm <= 1'b1; //一开始为1 因为不管是0还是1 一开始都是高电平 只是持续时间不同
end
LED_start :
begin
shift<=led_brink[bit_cnt];
if(shift == 1'b1) //说明要输出1码
begin
if(led_cnt == LED_NUM-1)
led_cnt <= 6'd0;
else if(cycle_cnt == T1H)
led_pwm <= 1'b0;
else if(cycle_cnt == (T1H + T0H - 6'd1))
begin
led_pwm <= 1'b1;
led_cnt <= led_cnt + 1'b1;
end
else
led_pwm <= led_pwm;
end
else
begin
if(led_cnt == LED_NUM-1)
led_cnt <= 6'd0;
else if(cycle_cnt == T0H)
led_pwm <= 1'b0;
else if(cycle_cnt == (T1H + T0H - 6'd1))
begin
led_pwm <= 1'b1;
led_cnt <= led_cnt + 1'b1;
end
else
led_pwm <= led_pwm;
end
end
RST_FSM:
begin
led_pwm <= 1'b0;
end
default:
begin
led_pwm <= 1'b0;
end
endcase
end
end
//FSM 3 状态转移
always @ (*)
begin
case(state)
IDLE : state_n = LED_start;
LED_start : begin
if(state_tran)
state_n= RST_FSM;
else
state_n = state;
end
RST_FSM :
begin
if(state_tran_rst)
state_n = IDLE;
else
state_n = state;
end
endcase
end
//记录24位的时间,方便转移状态led 每选完一个led的色彩就跳转到下一个状态
always @(posedge clk or negedge rst)
begin
if(!rst)
begin
bit_cnt <= 5'd0;
state_tran <= 1'b0;
end
else if (bit_cnt == 5'd24)
begin
bit_cnt <= 5'd0;
if(led_cnt == LED_NUM-1)
begin
state_tran <= 1'b1;
end
end
else if(cycle_cnt == (T0H + T0L - 6'd1))
begin
bit_cnt <= bit_cnt + 1'b1;
state_tran <= 1'b0;
end
else
begin
bit_cnt <= bit_cnt;
state_tran <= 1'b0;
end
end
//记录rst的时间,状态转换 没到达一个RST_FSM就加一 达到需要的复位计数后 重新开始新的一轮
always @(posedge clk or negedge rst)
begin
if(!rst)
rst_cnt <= 14'd0;
else if(state == RST_FSM)
rst_cnt <= rst_cnt + 1'b1;
else
rst_cnt <= 14'd0;
end
//rst的时间一到就发状态转移信号,开始下一轮 因为复位只要280us以上 所以跑个几轮 再刷新一次 是一样的
always @(posedge clk or negedge rst)
begin
if(!rst)
state_tran_rst <= 1'b0;
else if(rst_cnt == RST)
state_tran_rst <=1'b1;
else
state_tran_rst <= 1'b0;
end
endmodule
通过修改LED_NUM和FREQ即可改变控制灯珠的数量和流水间隔。
同时记得led_cnt的位宽也要注意修改。
实现不同模式 只要在led_start里面修改逻辑即可。
最后就是引脚绑定,把led随便绑定到一个脚上接灯的数据线即可。
效果
更新工程啦。以及一点内容勘误
虽然上述代码可以点亮,但是会发现颜色和预期的不符合 原因在于led_brink最好是要25位,在上面的代码需要判断到第25位才会发出24位rgb数据,简单一点就把rgb数据改成25位即可。已经修改好了上述代码了。下面是工程链接。
vivado 2018.3
不同板子改一下引脚即可。
链接:https://pan.baidu.com/s/13Q2fzerSXzc6ZLHBEMC4mg
提取码:cy6p