天天看點

【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示

目錄

  • 一、知識了解
  • 二、子產品設計
  • 三、程式實作
  • 四、管腳配置及結果展示

上一篇博文:【入門學習一】基于 FPGA 使用 Verilog 實作按鍵點燈代碼及原理講解

功能描述:通過前面一篇學習的按鍵使用,本篇文章進一步使用按鍵,通過點選按鍵後,可以讓蜂鳴器播放不同的曲子

一、知識了解

PWM 技術控制蜂鳴器不同聲響

  • 所謂 PWM 就是脈沖寬度調制,本文通過變化輸出的脈沖頻率來使得蜂鳴器發出不同的音符聲音。
  • 下表是每個音符所對應的頻率及半周期。
音符 對應頻率(Hz) 時鐘周期數
1 523 95600
2 587 85150
3 659 75850
4 698 71600
5 784 63750
6 880 56800
7 988 50600
  • 音符對應的時鐘周期數越多,那麼它的一個周期就越長,從下圖可以看出來,音符從 1 到 7,它的一個周期逐次遞減。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 是以說,如何讓蜂鳴器發聲,隻需要往蜂鳴器的管腳輸出連續的高低變化電平即可。
  • 就拿我這個闆子的蜂鳴器來說,圖中有個 PNP 型三極管,當基極為高電平時截止,也就是高電平的發射極 Q7 無法導通到集電極使蜂鳴器發聲,低電平時導通,此時高電平的發射極 Q7 可以導通到集電極使蜂鳴器發聲。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 要讓蜂鳴器發出不同的聲音,采用 PWM 原理改變基極的脈沖寬度即可,也就是前面提到的,不同音符的一個周期所對應不同寬度脈沖。

設定每個音符持續時長

  • 當輸出一個周期的脈沖給蜂鳴器,它肯定會響,但是有一點,一個周期的脈沖時長隻有幾百毫秒,我們能聽到嗎?當然聽不到,是以需要不斷地重複一個周期的脈沖,使它連續輸出波形長達 1 s 或者 0.5 s,也就是一個音符的持續時長,那麼我們就肯定能聽到了。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 這裡我以音符 1 為基準,讓它的一個周期重複 250 次,那麼它總時長為 95600 × 250 = 23 , 900 , 000 95600×250=23,900,000 95600×250=23,900,000 個時序周期,其它的音符總時長也為 23 , 900 , 000 23,900,000 23,900,000 個時序周期,是以不難得出

    每 個 音 符 的 重 複 次 數 = 23 , 900 , 000 該 音 符 一 個 周 期 時 序 數 每個音符的重複次數=\frac{23,900,000}{該音符一個周期時序數}\\ 每個音符的重複次數=該音符一個周期時序數23,900,000​

  • 音符1 = 23 , 900 , 000 95600 ≈ 250 ( 次 ) = \frac{23,900,000}{95600}≈250(次) =9560023,900,000​≈250(次)

    音符2 = 23 , 900 , 000 85150 ≈ 281 ( 次 ) = \frac{23,900,000}{85150}≈281(次) =8515023,900,000​≈281(次)

    音符3 = 23 , 900 , 000 75850 ≈ 315 ( 次 ) = \frac{23,900,000}{75850}≈315(次) =7585023,900,000​≈315(次)

    音符4 = 23 , 900 , 000 71600 ≈ 334 ( 次 ) = \frac{23,900,000}{71600}≈334(次) =7160023,900,000​≈334(次)

    音符5 = 23 , 900 , 000 63750 ≈ 375 ( 次 ) = \frac{23,900,000}{63750}≈375(次) =6375023,900,000​≈375(次)

    音符6 = 23 , 900 , 000 56800 ≈ 421 ( 次 ) = \frac{23,900,000}{56800}≈421(次) =5680023,900,000​≈421(次)

    音符7 = 23 , 900 , 000 50600 ≈ 472 ( 次 ) = \frac{23,900,000}{50600}≈472(次) =5060023,900,000​≈472(次)

歌譜

  • 歌譜如下:
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示

二、子產品設計

  • 由于程式比較簡單,是以就隻需要一個蜂鳴器 .v 檔案即可,它也是頂層子產品,再在其中引用按鍵子產品。
  • 其中按鍵子產品 key_debounce.v 在前一篇博文中已經貼出了,這裡就不再重複貼出代碼以及講解了。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示

三、程式實作

蜂鳴器子產品 pwm_buzzer.v

module pwm_buzzer(
	input				clk		,		//時鐘輸入
	input				rst_n	,		//複位按鍵輸入
	input				key_in	,		//按鍵輸入
	
	output	reg			buzzer			//驅動蜂鳴器
	);
	
	wire				press	;		//線,連接配接按鍵标志信号
	
	//引用按鍵子產品
	key_debounce u_key_debounce(
		.clk			(clk	),
		.rst_n			(rst_n	),
		.key			(key_in	),
		.press			(press	)
	);
	
	//定義音符時序周期數
	localparam			M0 	= 98800,
						M1	= 95600,
						M2	= 85150,
						M3	= 75850,
						M4	= 71600,
						M5  = 63750,
						M6	= 56800,
						M7	= 50600;
	
	//信号定義
	reg		[16:0]		cnt0		;	//計數每個音符對應的時序周期
	reg		[10:0]		cnt1		;	//計數每個音符重複次數
	reg		[5 :0]		cnt2		;	//計數曲譜中音符個數
	
	reg		[16:0]		pre_set		;	//預裝載值
	wire	[16:0]		pre_div		;	//占空比
	
	reg		[10:0]		cishu		;	//定義不同音符重複不同次數
	wire	[10:0]		cishu_div	;	//音符重複次數占空比
	
	reg 				flag		;	//歌曲種類标志:0小星星,1兩隻老虎
	reg		[5 :0]		YINFU		;	//定義曲譜中音符個數
	
	//歌曲種類标志位
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			flag <= 1'b0;
		end
		else if(press) begin
			flag <= ~flag;
		end
	end
	
	//重設音符的個數
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n)
			YINFU <= 48;
		else if(flag == 1'b1)
			YINFU <= 36;
		else
			YINFU <= 48;
	end
	
	//計數每個音符的周期,也就是表示音符的一個周期
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			cnt0 <= 0;
		end
		else if(press)
			cnt0 <= 0;
		else begin
			if(cnt0 == pre_set - 1)
				cnt0 <= 0;
			else
				cnt0 <= cnt0 + 1;
		end
	end
	
	//計數每個音符重複次數,也就是表示一個音符的響鳴持續時長
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			cnt1 <= 0;
		end
		else if(press)
			cnt1 <= 0;
		else begin
			if(cnt0 == pre_set - 1)begin
				if(cnt1 == cishu)
					cnt1 <= 0;
				else
					cnt1 <= cnt1 + 1;
			end
		end
	end
	
	//計數有多少個音符,也就是曲譜中有共多少個音符
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			cnt2 <= 0;
		end
		else if(press)
			cnt2 <= 0;
		else begin
			if(cnt1 == cishu && cnt0 == pre_set - 1) begin
				if(cnt2 == YINFU - 1) begin
					cnt2 <= 0;
				end
				else
					cnt2 <= cnt2 + 1;
			end
		end
	end
	
	//定義音符重複次數
	always @(*) begin
		case(pre_set)
			M0:cishu = 242;
			M1:cishu = 250;
			M2:cishu = 281;
			M3:cishu = 315;
			M4:cishu = 334;
			M5:cishu = 375;
			M6:cishu = 421;
			M7:cishu = 472;
		endcase
	end
	
	//曲譜定義
	always @(*) begin
		if(flag == 1'b0) begin
			case(cnt2)	//小星星歌譜
				0 : pre_set = M1;
				1 : pre_set = M1;
				2 : pre_set = M5;
				3 : pre_set = M5;
				4 : pre_set = M6;
				5 : pre_set = M6;
				6 : pre_set = M5;
				7 : pre_set = M0;
				
				8 : pre_set = M4;
				9 : pre_set = M4;
				10: pre_set = M3;
				11: pre_set = M3;
				12: pre_set = M2;
				13: pre_set = M2;
				14: pre_set = M1;
				15: pre_set = M0;
				
				16: pre_set = M5;
				17: pre_set = M5;
				18: pre_set = M4;
				19: pre_set = M4;
				20: pre_set = M3;
				21: pre_set = M3;
				22: pre_set = M2;
				23: pre_set = M0;
				
				24: pre_set = M5;
				25: pre_set = M5;
				26: pre_set = M4;
				27: pre_set = M4;
				28: pre_set = M3;
				29: pre_set = M3;
				30: pre_set = M2;
				31: pre_set = M0;
				
				32: pre_set = M1;
				33: pre_set = M1;
				34: pre_set = M5;
				35: pre_set = M5;
				36: pre_set = M6;
				37: pre_set = M6;
				38: pre_set = M5;
				39: pre_set = M0;
				
				40: pre_set = M4;
				41: pre_set = M4;
				42: pre_set = M3;
				43: pre_set = M3;
				44: pre_set = M2;
				45: pre_set = M2;
				46: pre_set = M1;
				47: pre_set = M0;
			endcase
		end
		else begin
			case(cnt2)	//兩隻老虎歌譜
				0 : pre_set = M1;
				1 : pre_set = M2;
				2 : pre_set = M3;
				3 : pre_set = M1;
				4 : pre_set = M1;
				5 : pre_set = M2;
				6 : pre_set = M3;
				7 : pre_set = M1;
				8 : pre_set = M3;
				9 : pre_set = M4;
				10: pre_set = M5;
				11: pre_set = M0;
				
				12: pre_set = M3;
				13: pre_set = M4;
				14: pre_set = M5;
				15: pre_set = M0;
				
				16: pre_set = M5;
				17: pre_set = M6;
				18: pre_set = M5;
				19: pre_set = M4;
				20: pre_set = M3;
				21: pre_set = M1;
				22: pre_set = M5;
				23: pre_set = M6;
				24: pre_set = M5;
				25: pre_set = M4;
				26: pre_set = M3;
				27: pre_set = M1;
				28: pre_set = M2;
				29: pre_set = M5;
				30: pre_set = M1;
				31: pre_set = M0;
				
				32: pre_set = M2;
				33: pre_set = M5;
				34: pre_set = M1;
				35: pre_set = M0;
			endcase
		end
	end
	
	assign pre_div = pre_set >> 1;	//除以2
	assign cishu_div = cishu * 4 / 5;
	
	//向蜂鳴器輸出脈沖
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			buzzer <= 1'b1;
		end
		else if(pre_set != M0) begin
			if(cnt1 < cishu_div) begin
				if(cnt0 < pre_div) begin
						buzzer <= 1'b1;
				end
				else begin
						buzzer <= 1'b0;
				end
			end
			else begin
				buzzer <= 1'b1;
			end
		end
		else
			buzzer <= 1'b1;
	end
	
endmodule
	
           
  • 其中要說明的一點是,曲譜中有一個 “-” 的符号,我将它定位 M0,可以在向蜂鳴器輸出波形的時序邏輯中,可以看到當音符為 M0 時,它輸出高電平,也就是讓它此時不發聲。
  • 還有一點時,我設定了一個時間占空比為 4/5,也就是前 4/5 發聲,後 1/5 的時間不發聲,如果不設定時間占空比也可以,隻不過它會連續不斷的播放曲譜,感覺就不好聽了,是以這裡設定了個時間占空比。

代碼執行過程:

  • 首先設定 flag 的預設值為 0,那麼它最開始會自動開始循環播放小星星。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 其中,最開始會計數一個音符的周期,直至計滿不同音符所對應的周期數,它就會歸零,重新開始計數。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 計滿一個周期後,計數周期重複次數的計數器就會 +1,直至計滿不同音符所對應的重複次數,它就會歸零,重新開始計數。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 然後開始該歌曲的下一個音符。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 随着 cnt2 的值不斷累加,其所代表的音符類别也在不斷改變,這裡使用一個 case 語句來羅列一整首歌的音符順序。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 當按下按鍵後,press 脈沖信号傳遞過來,改變 flag 的值,以及重設 cnt0、cnt1、cnt2 的值歸零。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示
  • 當 flag 的值變為 1’b1,那麼 if 條件語句中,就會選擇第二個 case ,也就是第二首歌。
  • 其實我這樣講解代碼執行過程并不正确,這樣講隻是便于了解,應該配置時鐘去看語句的執行情況,說明一點,assign 語句是随時鐘變化的,也就是說,時鐘信号變化一次,它就執行一次,品一品這個意思。

四、管腳配置及結果展示

管腳配置

  • 要按照自己開發闆的管腳進行配置。
    【入門學習二】基于 FPGA 使用 Verilog 實作蜂鳴器響動的代碼及原理講解一、知識了解二、子產品設計三、程式實作四、管腳配置及結果展示

結果展示

  • 最後結果是指唱歌發生,是以沒辦法展示出來,這個代碼是我自己手寫的,親測可以成功,按鍵按下後,也可以切歌。
  • 如果你燒錄程式後還是無法發聲,那麼就是管腳的配置問題,具體查一下你自己開發闆的管腳原理圖,并配置正确。
  • 當然,如果想實作發一個聲,LED 燈閃亮一下,也是很簡單可以做到的,如果有興趣可以做一下。

下一篇博文:【入門學習三】基于 FPGA 使用 Verilog 實作按鍵狀态機代碼及原理詳解