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