這篇文章來源于DevicePlus.com英語網(wǎng)站的翻譯稿。
在本教程的第1部分中,我們介紹了FPGA,并在嵌入式 Micro的Mojo FPGA上完成了一個簡單的入門項目。在第2部分中,我們將介紹一個更復(fù)雜的項目:在Mojo FPGA上實現(xiàn)硬件PWM。
脈沖寬度調(diào)制(PWM)被廣泛應(yīng)用于嵌入式系統(tǒng)中,用以控制LED亮度、電機轉(zhuǎn)速,甚至可用于通信。如果您使用的是Arduino,那么在使用analogWrite()函數(shù)的時候一定遇到過PWM。在Mojo上實現(xiàn)PWM之前,我們應(yīng)該首先了解一下PWM是如何工作的!
脈沖寬度調(diào)制(PWM)
微控制器和其他嵌入式系統(tǒng)處理器使用數(shù)字信號來進行計算和執(zhí)行任務(wù)。這些信號僅以兩種電平狀態(tài)中的一種出現(xiàn):“高”電平(通常為3.3V或5V)和“低”電平(通常為0V)。這兩種電平分別編碼為二進制1和0,因此可用于執(zhí)行各種各樣的工作任務(wù)。
但是,如果我們想輸出比“開和關(guān)”更多分級的電壓呢?在諸如上文中所提到的要求輸出可變強度的應(yīng)用激發(fā)了對該問題的探索。一種解決方案是將系統(tǒng)連接到稱為“數(shù)模轉(zhuǎn)換器(DAC)”的外部設(shè)備,該設(shè)備從主處理器獲取二進制1和0形式的數(shù)字信號輸入,并輸出幾乎連續(xù)的0V到系統(tǒng)“最大電壓”范圍內(nèi)的電壓值。但是,對于大多數(shù)嵌入式系統(tǒng)應(yīng)用程序來說,存在一種更簡單的方法:PWM!
從某種程度上講,PWM利用了人類的感知系統(tǒng)和物理系統(tǒng)(如電機)能夠?qū)ψ兓妮斎脒M行快速平均這一事實。系統(tǒng)生成一個包含高頻率脈沖的數(shù)字信號,因此很難將每個脈沖區(qū)分開來。在給定的波形周期內(nèi),信號在該時間內(nèi)的某些時間為高電平,其余時間為低電平—高電平所占的時間比例稱為信號的占空比或工作周期。平均輸出效果(無論是LED的亮度還是電機轉(zhuǎn)速)取決于PWM信號的占空比。當然,顧名思義,我們可以通過更改波形的占空比(脈沖寬度)來改變輸出效果!下圖顯示了不同占空比及其對應(yīng)波形的比較。
圖1:不同占空比及其相關(guān)脈沖波形的比較。顯然,當信號一直為高電平,占空比為100%時,出現(xiàn)了最“強烈”的輸出效果
Arduino板上的微控制器IC(如ATMega芯片)具有專用于根據(jù)處理器指令生成PWM信號的內(nèi)部硬件。這些電路的輸出連接到微控制器IC上的特定引腳。這也意味著只有這些引腳可以提供PWM信號。但是,在像Mojo這樣的FPGA上,我們可以根據(jù)需要配置內(nèi)部硬件,也就是說我們可以創(chuàng)建任意數(shù)量的硬件PWM電路!
Mojo FPGA上的硬件PWM
在本教程中,我們將探索如何在Verilog中實現(xiàn)硬件PWM,并了解Verilog代碼的模塊化如何使我們能夠根據(jù)需要在Mojo中配置盡可能多的硬件PWM電路。
以下是您需要遵照實施的所有內(nèi)容:
? Mojo V3開發(fā)板
我們將在嵌入式Micro提供的Mojo Base Project的基礎(chǔ)上,為該項目構(gòu)建我們自己的Verilog代碼。以這些項目為基礎(chǔ)來構(gòu)建是很有幫助的,因為設(shè)備規(guī)格和ISE中的其他初始化工作都已經(jīng)為我們設(shè)置好了!如果需要,您可以通過對項目目錄中的.xise項目文件進行重命名來更改項目名稱。我將其命名為“MojoPWM.xise”。
通常,我們首先會在UCF中表明名稱和引腳編號,用于與Mojo上的I/O引腳建立的不同連接。但是,對于本項目,我們將使用板載LED,信號名稱和引腳連接都已經(jīng)指定好了。因此,此處不需要額外的聲明。我們將從創(chuàng)建一個新的Verilog模塊開始,該模塊將指定我們硬件PWM的行為。并非將代碼直接放入mojo_top模塊中,我們將創(chuàng)建一個獨立的模塊以利用模塊化設(shè)計。如果我們要創(chuàng)建不同的硬件PWM電路來運行不同的LED,則只需要創(chuàng)建該種獨立PWM模塊的新實例即可,無需復(fù)制和粘貼大量代碼。
右鍵單擊左側(cè)“Hierarchy”窗口中的任意位置,然后點擊名為“New Source…”的選項,在“Source Type”的選項列表中,選擇“Verilog Module”,并將文件命名為“PWM.v”,以此將創(chuàng)建出一個新的Verilog文件,該文件具有用于PWM模塊的框架。
在真正開始編寫代碼之前,我們先來討論一下我們的PWM實現(xiàn)方法。如前所述,我們用該硬件生成的信號本身是周期性的,也就是說信號值與時間有關(guān)。因此,我們必須根據(jù)不間斷的滴答時鐘信號來指定其行為。這個時鐘信號已經(jīng)作為系統(tǒng)輸入包含在mojo_top模塊中了,為方波信號,圖形如下所示:
我們的硬件操作可總結(jié)如下:
? 每個時鐘周期(從信號的上升沿開始),我們將增加內(nèi)部計數(shù)器的值,該值的范圍為0-255。
? 占空比將作為輸入包含在模塊中,范圍為0-255(就像Arduino的硬件PWM中的那樣)。如果我們的計數(shù)器值小于占空比,那么輸出信號將為高電平,否則,輸出信號將為低電平。
? 在復(fù)位線上收到高電平值時,硬件將復(fù)位計數(shù)器。
我們選擇值255作為最大計數(shù)器值,因為這是8位數(shù)值中可以存儲的最大值(11111111)。如果從該值增加1,則計數(shù)器將回復(fù)到00000000,因為會溢出。要了解有關(guān)二進制計算和整數(shù)的二進制表示的更多信息,請點擊下面附錄中的鏈接進行查看!
這是一個時序圖,表示我們隨內(nèi)部時鐘信號的硬件操作:
圖2:占空比為3/255的8位硬件PWM模塊的時序圖。二進制中的最大占空比值與最大計數(shù)器值相同,均為11111111
我們將以輸入和輸出信號列表作為開始,進行對PWM模塊的Verilog描述:
input clk,
input rst,
input[7:0] duty,
output sig_drv
您可能已經(jīng)從它們的名稱中看出來了,這四個信號分別為時鐘、復(fù)位、占空比值和PWM輸出信號。
接下來,我們需要限定輸出信號sig_drv的數(shù)據(jù)類型。Verilog有兩種數(shù)據(jù)類型,線網(wǎng)(wire)和寄存器(reg)。雖然這兩種類型之間的差異對我們的應(yīng)用程序來說是很微小的,但是有一個主要區(qū)別需要注意,就是當我們就像在本項目中這樣使用always塊時,只能寫regs而不能寫wires。我們隨后會討論always塊及其相關(guān)操作。如果信號列表中的信號沒有被限定為wire或reg,Verilog將默認其聲明為wire類型。在這種情況下,我們需要通過模塊信號列表之后的以下行將sig_drv描述為reg:
reg sig_drv;
我們還將使用如上所述的8位計數(shù)器,并且通過always塊對其進行設(shè)置。因此,我們需要聲明一個8位大小的計數(shù)器,如下所示:
reg[7:0] counter;
您可能已經(jīng)注意到了,像許多其他編程語言一樣,Verilog是0索引的,這意味著計數(shù)總是從0開始的。因此,一個8位計數(shù)器中的位索引值為數(shù)字0到7。
接下來,我們對8位計數(shù)器的向上計數(shù)和輸出信號的驅(qū)動的邏輯進行描述。我們可以使用always來完成!always塊是一種Verilog結(jié)構(gòu),用戶可以指定僅在always塊的觸發(fā)條件被滿足時才會進行的操作。一個 always塊的基本結(jié)構(gòu)如下:
always @ (…)
begin
…
end
在 “@” 符號后的括號內(nèi),用戶需要指定觸發(fā)條件,該條件將決定何時執(zhí)行塊內(nèi)的邏輯。在我們的項目中,需要兩個always塊:
always @(*)
begin
end
always @(posedge clk or posedge rst)
begin
end
在第一個塊中,觸發(fā)條件為“*” ,這意味著只要項目中的任何信號發(fā)生變化,該塊內(nèi)的邏輯就會被執(zhí)行。硬件工程師可能將此塊稱為組合邏輯,該邏輯始終將輸出值定義為輸入值的某些函數(shù)。在此塊中,我們將放入在所有時刻都起作用的邏輯,而不是每個時鐘周期只執(zhí)行一次的邏輯,如輸出信號的驅(qū)動。
如前所述,輸出信號為高電平驅(qū)動還是低電平驅(qū)動取決于計數(shù)器相比于占空比的大小??梢酝ㄟ^以下代碼行中的always @ (*) 塊實現(xiàn)此功能:
if (duty > counter)
begin
sig_drv = 1’b1;
end
else
begin
sig_drv = 1’b0;
end
sig_drv信號的寬度為1位(只有0和1兩種值…一個位),因此我們在給它分配的值前加上字符“1’b”。從上面的代碼中我們可以看到,當占空比大于計數(shù)器值時,sig_drv線被驅(qū)動為1(高),否則被驅(qū)動為0(低)。
在第二個塊中,觸發(fā)條件為posedge clk 或 posedge rst。這意味著當時鐘信號從低電平變?yōu)楦唠娖交驈?fù)位線從低電平變?yōu)楦唠娖綍r,該塊內(nèi)的邏輯被執(zhí)行,且在每個時鐘周期內(nèi)僅執(zhí)行一次。我們將使用該always塊來指定每執(zhí)行一個時鐘周期時的計數(shù)器增加??梢允褂么藟K中的以下代碼行完成此操作:
if (rst)
begin
counter <= 8’b0;
end
else
begin
counter <= counter + 1;
end
if語句的第一段指定了當復(fù)位線變?yōu)楦唠娖綍r,必須將8位計數(shù)器復(fù)位為全零。第二段的else條件下指定了如果復(fù)位線不是高電平,則計數(shù)器的值會被增加。
我們可以看到分配給計數(shù)器的值取決于其先前的值。硬件工程師將這種類型的邏輯稱為順序模型,因為輸出既是輸入的函數(shù),也是過去狀態(tài)值的函數(shù)。
關(guān)于該代碼最后需要說明的是 “<=” 運算符,即非阻塞賦值運算符,用于將值賦給計數(shù)器變量。當我們像往常一樣使用 “=” 運算符(阻塞賦值運算符)來給信號賦值時,其實我們已經(jīng)默許Verilog對編寫的代碼自上而下來執(zhí)行。換句話說,如果我們連續(xù)編寫了兩個“=”賦值語句,那么第一個賦值操作將會在第二個賦值操作開始之前完成執(zhí)行。這其實在某些邏輯上是必要的,因為我們可能會使用以此方式分配的值進行后續(xù)計算。
但是,在基于高速時鐘信號的順序邏輯中,我們實際上希望所有的值的分配都在某種程度上并行發(fā)生(如果它們彼此獨立的話),這樣我們就不會將程序延遲到與下一個時鐘邊沿發(fā)生沖突的程度。在本程序中,我們沒有在每個時鐘邊沿上指定執(zhí)行多種任務(wù)。但是,如果我們需要這樣做的話,使用非阻塞賦值運算符可以完成這項操作。
完整的PWM模塊應(yīng)如下所示:
現(xiàn)在我們已經(jīng)創(chuàng)建了PWM模塊,可以在mojo_top模塊中將其實例化了!在Verilog中,將一個模塊在另一個模塊中實例化使您可以在更高級別的模塊中一次或多次調(diào)用子模塊功能,而不必復(fù)制其代碼。就我們的項目來說,我們可以根據(jù)需要創(chuàng)建足夠多的PWM信號來驅(qū)動不同的LED,甚至連接到Mojode 輸出引腳上!要配置PWM信號來點亮Mojo上的第8個LED,我們可以添加以下行:
PWM my_pwm(.clk(clk), .rst(rst), .duty(8’b01000000), .sig_drv(led[7]));
該行的第一個單詞PWM是我們要實例化的模塊的名稱。這將在我們選擇實例化多個副本時幫助識別同一PWM模塊的不同實例。
在模塊名稱后的括號內(nèi),我們使用了.(signal_name) 格式將更高級別模塊 (signal_name) 中的信號分配給子模塊中的相應(yīng)信號 (module_signal_name)。
如果要更改PWM信號的占空比,我們要做的就是更改傳遞到占空比參數(shù)中的值。如果被驅(qū)動的輸出信號,則只需要將作為參數(shù)傳遞的信號更改為.sig_drv。
您所完成的mojo_top模塊應(yīng)如下所示:
要將代碼上傳到Mojo板上,請按照之前的步驟進行操作:在ISE中生成編程文件,加載Mojo Loader應(yīng)用程序,然后將.bin文件上傳到Mojo。
恭喜您!您已經(jīng)在Mojo上實現(xiàn)硬件PWM了!如果想進行進一步的實驗,請嘗試創(chuàng)建多個硬件PWM信號并為其提供不同的占空比參數(shù)!您可以通過修改代碼,來實現(xiàn)通過一些撥動開關(guān)將占空比值輸入到Mojo中嗎?
我們希望您對自己的第一個FPGA項目感到滿意!請繼續(xù)關(guān)注來獲取更多有關(guān)FPGA和微控制器的教程!
審核編輯黃宇
-
微控制器
+關(guān)注
關(guān)注
48文章
7596瀏覽量
151748 -
PWM
+關(guān)注
關(guān)注
114文章
5196瀏覽量
214373 -
Arduino
+關(guān)注
關(guān)注
188文章
6474瀏覽量
187442
發(fā)布評論請先 登錄
相關(guān)推薦
評論