【读书笔记】详细解析基于FPGA的呼吸灯设计
0赞【主题】:详细解析基于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个时钟周期
很明显,加上这个条件以后,时序非常的和谐。
但是如果去掉这个条件呢?如下图所示。
时序变得混乱不堪,因此对于有使能条件的计数器,在输出时,要加上使能条件,比如这个计数器中的使能条件就是delay_1us,因此在输出时,要加上这个条件。其实在详细解析74HC595驱动时候,就有这个问题的影子,只不过不是计数器而已。
//----------------------------------- //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这个使能条件,原因在上文中已经说明,此时如果不加这个条件,时序将更加混乱。如下图所示。
//----------------------------------- //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灯。
时序真的美到不行。效果也很赞!
总结:
在使用具有使能信号的计数器时,输出信号的条件应加上此使能信号,以免使得输出时序变得混乱。
以上内容仅为笔者个人见解,如有不同观点,欢迎交流!