1. 項目簡介
呼吸燈,指的是一個LED從暗到亮,從亮到暗逐漸變化,如此循環反復,就像人的呼吸一樣有節奏。它采用PWM的方式,在固定的頻率下,通過調整占空比的方式來控制LED燈亮度的變化。
PWM(Pulse Width Modulation),即脈沖寬度調制,是一種對模擬信號電平進行數字編碼的方法。通過高分辨率計數器的使用,方波的占空比被調制用來對一個具體模擬信號的電平進行編碼,被廣泛應用於測量、通信、功率控制等領域。
2. 設計要求
實現呼吸燈的效果,即由滅漸亮,然后再由亮漸滅,要求一個完整的呼吸過程包含:呼氣(2秒)和吸氣(2秒),周期為4秒。系統框圖如下圖所示,其中,clk為系統時鍾、50Mhz,rst_n為系統復位,低電平有效,led[3:0]為輸出led燈,高電平點亮。
3. 設計原理
LED的亮度與流過的電流成正比。不同的脈沖占空比的方波輸出后加在LED上,LED燈就會顯示不同的亮度,通過不斷地調節方波的占空比,從而實現LED燈亮度的調節。在一定的頻率下,如果占空比是0,則LED不亮;如果占空比是100%,則LED最亮;如果占空比剛好是50%,則LED亮度適中。如果我們讓占空比從0%-100%變化,再從100%-0%不斷變化,就可以實現LED一呼一吸的效果。其設計思路如下:
(1)一個完整的呼吸過程包含:呼氣(2秒)和吸氣(2秒),周期為4秒。
(2)考慮呼氣的過程,就是讓led燈要有亮滅的變化,從暗逐漸到亮。這里必須要將2秒的時間進行拆分,為什么?
(3)為什么要將2秒進行拆分?因為如果不拆分,只有一個固定不變的占空比,無論占空比是多少,led始終是固定的,即led的亮度也是固定的。
(4)如何拆分?理論上:我們可以無限的細分,拆分的份數越多,led的變化就越流暢;實際上:由於人的眼睛有視覺暫留效應,對應光的變化,人眼的分辨率是30-40ms,所以我們無限細分是無意義的。
參數取值:為了led的亮度變化比較流暢,我們取2ms的間隔將2s進行拆分,也就是每隔20ms我們給led燈一個新的亮度,
這樣我們人眼就能分辨出這個亮度的變化,則 cnt * T = 2000ms,T=2ms,cnt=1000,即拆分1000份。
(5)20ms周期連續變化示意圖
由步驟4得知,我們將2秒拆分為1000份的2ms,也就是2ms周期的波形在時序圖上看是會重復1000次;為了體現led燈亮度,每一份20ms波形的占空比都不能相同,且必須是連續增加/減小的。如下圖示意
(6)由步驟5得知,波形重復1000次,也就是2ms的時間內,我們一共有1000個狀態,每一個狀態就是一個占空比t * 1000 = 20ms,則 t = 2us,由公式計算得知,2us的占空比為2/2*1000 %100 = 1‰
(7)從上面的描述中,我們可以總結出,要做出呼吸燈,我們需要利用三個計數器分別計數(類似於數字鍾中秒、分鍾、小時)。第一個計數器cnt_2us用來對2us的計數,第二個計數器cnt_2ms實現對2ms計數,第三個計數器實現對2s計數。三個計數器中,利用后面兩個計數器cnt_2ms和cnt_2s的大小進行比較,來改變一個時鍾周期內的占空比。
4. 設計實現
module led_breath( input wire clk, //系統時鍾,50Mhz input wire rst_n, //系統復位,低電平有效 output wire [3:0] led //led燈,高電平點亮 ); parameter T_2us = 100; parameter T_2ms = 100_000; parameter T_2s = 100_000_000; reg [6:0] cnt_2us; reg [9:0] cnt_2ms; reg [9:0] cnt_2s; reg valid; wire wave; always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_2us <= 7'd0; else if(cnt_2us < T_2us - 1'b1) cnt_2us <= cnt_2us + 1'b1; else cnt_2us <= 7'd0; end always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_2ms <= 10'd0; else if(cnt_2us == T_2us - 1'b1) if(cnt_2ms < T_2ms/T_2us - 1'b1) cnt_2ms <= cnt_2ms + 1'b1; else cnt_2ms <= 10'd0; else cnt_2ms <= cnt_2ms; end always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_2s <= 10'd0; else if((cnt_2us == T_2us - 1'b1)&&(cnt_2ms == T_2ms/T_2us - 1'b1)) if(cnt_2s < T_2s/T_2ms - 1'b1) cnt_2s <= cnt_2s + 1'b1; else cnt_2s <= 10'd0; else cnt_2s <= cnt_2s; end assign wave = (cnt_2s > cnt_2ms) ? 1'b1 : 1'b0; always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) valid <= 1'b0; else if((cnt_2us == T_2us - 1'b1)&&(cnt_2ms == T_2ms/T_2us - 1'b1)&&(cnt_2s == T_2s/T_2ms - 1'b1)) valid <= ~valid; else valid <= valid; end assign led[0] = (valid == 1'b0) ? wave : ~wave; assign led[1] = (valid == 1'b0) ? wave : ~wave; assign led[2] = (valid == 1'b0) ? wave : ~wave; assign led[3] = (valid == 1'b0) ? wave : ~wave; endmodule
5. 仿真測試
`timescale 1ns/1ps module led_breath_tb(); reg clk; reg rst_n; wire [3:0] led; defparam led_breath_inst.T_2us = 10; defparam led_breath_inst.T_2ms = 100; defparam led_breath_inst.T_2s = 1000; led_breath led_breath_inst( .clk (clk), .rst_n (rst_n), .led (led) ); initial begin repeat(1000*10)begin clk = 1'b0; #10; clk = 1'b1; #10; end end initial begin rst_n = 1'b0; #21; rst_n = 1'b1; end endmodule
注:PWM波一般可以使用單片機或者專用PWM波生成電路產生(一般簡單的單片機就可以,然后選擇PWM控制器路數比較多的即可),也有很多用CPLD或者FPGA來實現PWM波的,但是實際應用中,由於FPGA成本和功耗上對單片機不具備優勢,所以實際產品中很少會用FPGA或者CPLD芯片。