概述
實現彩虹呼吸燈
題目就是這么簡短,但這是目前我碰到的最有意思的一道題目,因為他有無數種解決方法,並且每一種都是那么高級或者巧妙,比如
- 可以利用3路不同初相的PWM調制信號驅動三顆RGB燈重疊呼吸
- 利用1路PWM信號以及狀態機,將一個周期分為3個狀態,分別是[R降G升B滅],[R滅,G降,B升]和[R升,G滅,B降],依次往復實現重疊呼吸
- 將PWM拆分為3段,分別為升,降,滅,在不同時間周期性的輸送給RGB實現重疊呼吸
當然,不只這幾種,還有更高級的方法或者生成語句也可以更加簡練的完成題目,在這里,我將采取上面羅列的幾種方法的一種折中方案,采取"拆分PWM","三元運算符實現單行條件信號分配","監視模塊內運行情況並以監視信號作為狀態轉換的觸發條件"來實現彩虹呼吸燈.
題目分析
題目只有七個字:"實現彩虹呼吸燈",其中"呼吸兩個字",已經確定了這個實驗和脈寬調制扯不開干系,另外"彩虹"也說明這個實驗需要很多的色彩,單單靠單色LED是完成不了的,一定需要三色RGB完成,並且只是讓R,G,B三個LED交替呼吸,也達不到"彩虹"的效果,所以需要讓三色燈按照一定的規律重疊呼吸,這里為了方便,我按照下圖示意的樣式進行編程
(抱歉畫工實在欠缺,咳咳)
意思就是在R燈最亮時,G燈開始升,R燈開始降,在G燈最亮時,R燈已滅,B燈開始升,G燈開始降,以此類推.
通過這個圖也可以容易的分成三個情況,用以實現狀態機.
PWM
PWM是個啥?
PWM( Pulse width modulation )就是脈沖寬度調制,是一種通過數字信號對模擬信號控制的有效技術.簡單來說,規律的進行脈寬調制,比如將一束方波的占空比不斷減小,那么這束方波的有效值也相應的減小,占空比增大,有效值也增大,借此來對LED的亮度進行控制,加以周期性的增減,即可實現呼吸燈.
呼吸燈只是PWM的一個具體應用.
PWM咋實現?
在之前的學習早已接觸過PWM調制的實現方法,在這里直接給出代碼,可以通過注釋回憶PWM實現過程
module PWM
(input CLK
,input FLAG//標志位,控制輸出的PWM是升還是降(1升0降)
,output STT//監視信號(脈沖)
,output PWM
);
reg[24:0]cnt1;
reg[24:0]cnt2;
parameter freq=2400;//通過這個freq來控制PWM的周期
reg stt;//監視狀態
always@(posedge CLK)
if(cnt2==freq-1)//cnt2滿,則狀態為1(只持續一個時鍾周期)
stt<=1'b1;
else
stt<=1'b0;
assign STT=stt;
always@(posedge CLK)
if(cnt1>=freq-1)//滿則清零
cnt1<=1'b0;
else
cnt1<=cnt1+1'b1;
always@(posedge CLK)
if(cnt1==freq-1)//cnt1滿,以cnt1從空到滿為一個周期執行操作
if(FLAG)//升的情況
if(cnt2>=freq-1)
cnt2<=1'b0;
else
cnt2<=cnt2+1'b1;//升
else//降的情況
if(cnt2<=0)
cnt2<=freq-1;
else
cnt2<=cnt2-1'b1;//降
else
cnt2<=cnt2;
assign PWM=(cnt1<cnt2)?1'b0:1'b1;//PWM的核心,輸出調制好的PWM信號
endmodule
本代碼參考此網頁,內有更詳細的圖片和講解
代碼中的stt
和STT
是監視脈沖,不影響PWM輸出;輸入信號FLAG
控制PWM輸出信號是升還是降.二者作用在頂層代碼處詳細解釋.
頂層模塊
PWM很容易實現,需要動腦子的就是如何通過例化模塊來實現交替呼吸.下面給出我的算法.
例化模塊
先看代碼
wire UP;
wire DW;
wire STT0;
wire STT1;
PWM up(CLK,1,STT0,UP);
PWM dw(CLK,0,STT1,DW);
其中up
例化模塊中的1
代表FLAG
,在此表示這個up
例化模塊是一個"升"模塊,即為可以產生控制LED亮度從滅到亮的PWM信號,dw
例化模塊則代表可以產生一個可以控制從亮到暗的PWM信號.通過這個設計可以將PWM模塊的功能拆分,提供兩種模式供主模塊靈活調用.
代碼中的UP
和DW
分別為代表亮度升和亮度降的PWM信號.
狀態分析
這里不按照文首的那種狀態機思路來寫,而是將RGB三色燈分成3路對待,這里先以R為例.
對R來說,他的亮滅規律為:升(一單位時間),降(一單位時間),滅(一單位時間).然后可以以此來寫條件語句進行信號分配,可能第一時間想到的就是直接定義一個分頻,不同時間顯示不同狀態即可,但是這種寫法不利於后期拓展,易讀性和可維護性也稍差,在這里采用很方便的"三元運算符"解決.先來看這段代碼
reg[1:0]flag0=2'b00;
always@(posedge STT0)
if(flag0==2'b10)
flag0<=1'b0;
else
flag0<=flag0+1'b1;
這里定義了一個標志為flag0
,它是以上文提到過的監視脈沖STT
為觸發進行遞增計數的,STT
是一個在PWM模塊內每一個工作周期完成后就輸出一單位時間高電平的監視脈沖,通過這個脈沖可以知道PWM已經工作完一個周期,可以進行下一周期的工作,在頂層代碼里則充當了狀態轉移的觸發條件.
再來看這一行代碼
assign LED[0]=(flag0==2'b00)?UP:((flag0==2'b01)?DW:1'b1);
這一行是實現RGB燈工作狀態的核心代碼,通過(兩層)三元運算符在一條表達式內就完成了條件賦值.
這條代碼的意思就是,如果標志位flag0
是2'b00
,則R亮度升,若不是,則檢測標志為是否為2'b01
,若是,則R亮度降,如不是,則滅.然后通過上一個代碼塊中的代碼可以知道,每一個PWM周期完成后(表現為R已到達最亮或者最暗),狀態發生轉移,標志為變為下一個狀態,R也就在完成了亮度升之后立刻開始亮度降,宏觀表現為"呼吸"的狀態.
代碼整合
上文里兩個代碼塊就足以讓一個燈完成一個狀態的工作,這部分代碼如下
reg[1:0]flag0=2'b00;
always@(posedge STT0)
if(flag0==2'b10)
flag0<=1'b0;
else
flag0<=flag0+1'b1;
assign LED[0]=(flag0==2'b00)?UP:((flag0==2'b01)?DW:1'b1);
reg[1:0]flag1=2'b01;
always@(posedge STT0)
if(flag1==2'b10)
flag1<=1'b0;
else
flag1<=flag1+1'b1;
assign LED[1]=(flag1==2'b00)?UP:((flag1==2'b01)?DW:1'b1);
reg[1:0]flag2=2'b10;
always@(posedge STT0)
if(flag2==2'b10)
flag2<=1'b0;
else
flag2<=flag2+1'b1;
assign LED[2]=(flag2==2'b00)?UP:((flag2==2'b01)?DW:1'b1);
三個燈就相當於將這一段代碼例化三次,就可以讓三色燈分別進行互相不影響的狀態轉移(呼吸變化),但是我們的目的是讓他們按照文首圖中的規律重疊呼吸,該怎么實現呢?
這很簡單,很容易想到,三段一樣的代碼里都分別有一個獨立的標志為flag
,他是reg
類型數據,所以可以在定義時給他分配一個初始狀態,這樣就相當於給三個燈設置了不同的初相,在后面工作的時候由於工作周期相同,就會一直保持最開始的相位差,周期性的進行文首圖中的交替呼吸.
至此,彩虹呼吸燈已經完成.
效果
最后的效果圖點此查看,圖片較大,加載可能比較慢.(因為燈實在是太亮了,就蒙了一層紙來觀察顏色變化)
后話
這篇文章是目前寫過的第二費力的了,其中的代碼更新了很多很多次,在琢磨更精簡更巧妙的算法上和修Bug上花了很多的時間和精力,前前后后燒錄上板測試不下50次(不誇張T_T),在本地commit了無數個版本,回滾了無數次,一遍一遍修改,最后才得到了你看到的這些代碼.我的水平有限,所以就算如此文中的代碼和講解一定有所缺漏,還請希望大家多多包涵,並指出不足之處,改進這篇文章,來幫助更多的人.
本項目完整代碼存放在我的Github中,最新版以Github上為准(順路給顆Star唄;-)