[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交替呼吸,也達不到"彩虹"的效果,所以需要讓三色燈按照一定的規律重疊呼吸,這里為了方便,我按照下圖示意的樣式進行編程

QF84u6.png

(抱歉畫工實在欠缺,咳咳)

意思就是在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唄;-)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM