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