1. 程式人生 > >[FPGA]Verilog利用PWM調製巧妙完成RGB三色彩虹呼吸燈(給簡約的題目以美妙的解答)

[FPGA]Verilog利用PWM調製巧妙完成RGB三色彩虹呼吸燈(給簡約的題目以美妙的解答)

概述

實現彩虹呼吸燈

題目就是這麼簡短,但這是目前我碰到的最有意思的一道題目,因為他有無數種解決方法,並且每一種都是那麼高階或者巧妙,比如

  • 可以利用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

本程式碼參考此網頁,內有更詳細的圖片和講解

程式碼中的sttSTT是監視脈衝,不影響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模組的功能拆分,提供兩種模式供主模組靈活呼叫.

程式碼中的UPDW分別為代表亮度升和亮度降的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燈工作狀態的核心程式碼,通過(兩層)三元運算子在一條表示式內就完成了條件賦值.

這條程式碼的意思就是,如果標誌位flag02'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唄