LinCoding

【读书笔记】详细解析基于FPGA的呼吸灯设计

0
阅读(4091)

【主题】:详细解析基于FPGA的呼吸灯设计

【作者】:LinCoding

【时间】:2016.11.16

      呼吸灯的原理,就是PWM调节技术,不懂的同学请百度,还是只解析代码。

      (源码出自CrazyBingo,尊重版权)

led_breath
#(
	parameter 			LED_WIDTH = 4
)
(
	input				clk,		//global clock
	input				rst_n,		//global reset
	
	output	[LED_WIDTH-1'b1:0]	led_data
);

 第一部分是输入输出变量的定义,没什么好说的

//-----------------------------------
//counter for 1us
localparam	T1US = 6'd50;
//localparam	T1US = 6'd5;	//just for simulation
reg	[5:0]	delay_1us_cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		delay_1us_cnt	<= 6'd0;
	else if ( delay_1us_cnt < T1US )
		delay_1us_cnt	<= delay_1us_cnt + 1'b1;
	else
		delay_1us_cnt	<= 6'd1;
end
wire	delay_1us		= ( delay_1us_cnt == T1US ) ? 1'b1 : 1'b0;

 第二部分定义了一个1us的计数器(定时器),还是那个模式,计数器的输出用组合逻辑。

//-----------------------------------
//counter for 1ms
localparam	T1MS = 10'd1000;
//localparam	T1MS = 10'd100;		//just for simulation
reg	[9:0]	delay_1ms_cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		delay_1ms_cnt	<= 10'd0;
	else if ( delay_1us )
		delay_1ms_cnt	<= ( delay_1ms_cnt < T1MS ) ? ( delay_1ms_cnt + 1'b1 ) : 10'd1;
	else
		delay_1ms_cnt	<= delay_1ms_cnt;
end
wire	delay_1ms		= ( delay_1ms_cnt == T1MS && delay_1us ) ? 1'b1 : 1'b0;

第三部分是基于1us计数器定义的1ms计数器,这里有两点需要注意

1、以下代码

delay_1ms_cnt	<= ( delay_1ms_cnt < T1MS ) ? ( delay_1ms_cnt + 1'b1 ) : 10'd1;

虽然这部分可以用if……else……代替,但是,感觉这样写可读性更好。计数器输出仍然采用的是组合逻辑。

2、注意组合逻辑的输出中除了delay_1ms_cnt == T1MS之外还有delay_1us这个条件,这样做的好处是可以使得输出delay_1ms这个标志位的周期为1个时钟周期

blob.png

      很明显,加上这个条件以后,时序非常的和谐。

但是如果去掉这个条件呢?如下图所示。

blob.png

blob.png

 时序变得混乱不堪,因此对于有使能条件的计数器,在输出时,要加上使能条件,比如这个计数器中的使能条件就是delay_1us,因此在输出时,要加上这个条件。其实在详细解析74HC595驱动时候,就有这个问题的影子,只不过不是计数器而已。

blob.png


//-----------------------------------
//counter for 1s
localparam	T1S = 10'd1000;
//localparam	T1S = 10'd100;		//just for simulation
reg	[9:0]	delay_1s_cnt;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		delay_1s_cnt	<= 10'd0;
	else if ( delay_1ms )
		delay_1s_cnt	<= ( delay_1s_cnt < T1S ) ? ( delay_1s_cnt + 1'b1 ) : 10'd1;
	else
		delay_1s_cnt	<= delay_1s_cnt;
end
wire	delay_1s		= ( delay_1s_cnt == T1S && delay_1ms ) ? 1'b1 : 1'b0;

 第四部分是1s的计数器,还是要注意,输出时应加上delay_1ms这个使能条件,原因在上文中已经说明,此时如果不加这个条件,时序将更加混乱。如下图所示。

blob.png

//-----------------------------------
//PWM parameter
wire	[9:0]	duty_cnt	= delay_1ms_cnt;
wire	[9:0]	period_cnt	= delay_1s_cnt;

//-----------------------------------
//switch mode
reg		display_mode;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		display_mode	<= 1'b0;
	else if ( delay_1s )
		display_mode	<= ~ display_mode;
	else
		display_mode	<= display_mode;
end

这一部分就开始PWM的部分了,首先,PWM的周期将是1ms,占空比的步长将是1us,因为delay_1s_cnt每计一个数是1ms的时间,delay_1ms_cnt每计一个数是1us的时间,而在这delay_1s_cnt计一个数时delay_1ms_cnt从1增加到了1000。

以上代码中的always块就是每计时到达1s的时候,反转PWM的模式。

//-----------------------------------
//generate PWM
reg		pwm_on;
always @ ( posedge clk or negedge rst_n )
begin
	if ( ! rst_n )
		pwm_on	<= 1'b0;
	else
		case ( display_mode )
			1'b0:	pwm_on	<= ( duty_cnt < period_cnt ) ? 1'b1 : 1'b0;
			1'b1:	pwm_on	<= ( duty_cnt < period_cnt ) ? 1'b0 : 1'b1;
			default:;
		endcase
end
//-----------------------------------
//output to LED
assign	led_data = {LED_WIDTH{pwm_on}};

最后一部分就很简单了,根据计数比较大小输出1或者0,然后驱动LED灯。

blob.png

时序真的美到不行。效果也很赞!

总结:

 在使用具有使能信号的计数器时,输出信号的条件应加上此使能信号,以免使得输出时序变得混乱。


以上内容仅为笔者个人见解,如有不同观点,欢迎交流!