文章目錄
聲明:本文為原創作品,著作權歸本博文作者所有,如需轉載,請註明出處http://www.cnblogs.com/kingst/
簡介
NIOS II是一個建立在FPGA上的嵌入式軟核處理器,除了可以根據需要任意添加已經提供的外設外,使用者還可以通過定製使用者邏輯外設和定製使用者指令來實現各種應用要求。這節我們就來研究如何定製基於Avalon匯流排的使用者外設。
SOPC Builder提供了一個元件編輯器,通過這個元件編輯器我們就可以將我們自己寫的邏輯封裝成一個SOPC Builder元件了。下面,我們就以PWM實驗為例,詳細介紹一下定製基於Avalon匯流排的使用者外設的過程。
我們要將的PWM是基於Avalon匯流排中的Avalon Memory Mapped Interface (Avalon-MM),而Avalon匯流排還有其他類型的裝置,比如Avalon Streaming Interface (Avalon-ST)、Avalon Memory Mapped Tristate Interface等等,在這裡我就不詳細敘述了,需要進一步瞭解的請參考ALTERA公司的《Avalon Interface Specifications》(mnl_avalon_spec.pdf)。
Avalon-MM介面是記憶體映射系統下的用於主從裝置之間的讀寫的介面,就是一個基於Avalon-MM的主從裝置系統。而我們這節需要做的就是高亮部分。他的地位與UART,RAM Controller等模組並駕齊驅的。
Avalon-MM介面有很多特點,其中最大的特點就是根據自己的需求自由選擇訊號線,不過裡面還是有一些要求的。建議大家在看本文之前,先看一遍《Avalon Interface Specifications》,這樣就能對Avalon-MM介面有一個整體的瞭解。
為Avalon-MM外設的一個結構圖,
理論的就說這些,下面我們舉例來具體說明,讓大家可以更好的理解。
構建HDL
我們這一節是PWM為例,所以首先,我們要構建一個符合Avalon-MM Slave介面規範的可以實現PWM功能的時序邏輯,在這裡,我們利用Verilog語言來編寫。在程式中會涉及到Avalon訊號,在這裡,我們說明一下這些訊號(其中,方向以從裝置為基準)
HDL中的訊號 |
Avalon訊號類型 |
寬度 |
方向 |
描述 |
clk |
clk |
1 |
input |
同步時鐘訊號 |
reset_n |
reset_n |
1 |
input |
複位訊號,低電平有效 |
chipselect |
chipselect |
1 |
input |
片選訊號 |
address |
address |
2 |
input |
2位地址,解碼後確定寄存器offset |
write |
write |
1 |
input |
寫使能訊號 |
writedata |
writedata |
32 |
input |
32位寫資料值 |
read |
read |
1 |
input |
讀時能訊號 |
byteenable |
byteenable |
1 |
input |
位元組使能訊號 |
readdata |
readdata |
32 |
output |
32位讀資料值 |
此外,程式中還包括一個PWM_out訊號,這個訊號是PWM輸出,不屬於Avalon介面訊號。
PWM內部還包括使能控制寄存器、周期設定寄存器以及占空比設定寄存器。設計中將各寄存器映射成Avalon Slave連接埠地址空間內一個單獨的位移地址。沒個寄存器都可以進行讀寫訪問,軟體可以讀回寄存器中的當前值。寄存器及位移地址如下:
寄存器名 |
位移量 |
訪問屬性 |
描述 |
clock_divide_reg |
00 |
讀/寫 |
設定PWM輸出周期的時鐘數 |
duty_cycle_reg |
01 |
讀/寫 |
設定一個周期內PWM輸出低電平的始終個數 |
control_reg |
10 |
讀/寫 |
使能和關閉PWM輸出,為1時使能PWM輸出 |
程式如下:
module PWM( clk,reset_n,chipselect,address,write,writedata,read,byteenable,readdata,PWM_out);input clk;input reset_n;input chipselect;input [1:0]address;input write;input [31:0] writedata;input read;input [3:0] byteenable;output [31:0] readdata;output PWM_out;reg [31:0] clock_divide_reg; reg [31:0] duty_cycle_reg; reg control_reg;reg clock_divide_reg_selected;reg duty_cycle_reg_selected;reg control_reg_selected;reg [31:0] PWM_counter;reg [31:0] readdata;reg PWM_out;wire pwm_enable;//地址解碼always @ (address)beginclock_divide_reg_selected<=0;duty_cycle_reg_selected<=0;control_reg_selected<=0;case(address)2'b00:clock_divide_reg_selected<=1;2'b01:duty_cycle_reg_selected<=1;2'b10:control_reg_selected<=1;default:beginclock_divide_reg_selected<=0;duty_cycle_reg_selected<=0;control_reg_selected<=0;endendcaseend //寫PWM輸出周期的時鐘數寄存器always @ (posedge clk or negedge reset_n)beginif(reset_n==1'b0)clock_divide_reg=0;elsebeginif(write & chipselect & clock_divide_reg_selected)beginif(byteenable[0])clock_divide_reg[7:0]=writedata[7:0];if(byteenable[1])clock_divide_reg[15:8]=writedata[15:8];if(byteenable[2])clock_divide_reg[23:16]=writedata[23:16];if(byteenable[3])clock_divide_reg[31:24]=writedata[31:24];endendend//寫PWM周期占空比寄存器always @ (posedge clk or negedge reset_n)beginif(reset_n==1'b0)duty_cycle_reg=0;elsebeginif(write & chipselect & duty_cycle_reg_selected)beginif(byteenable[0])duty_cycle_reg[7:0]=writedata[7:0];if(byteenable[1])duty_cycle_reg[15:8]=writedata[15:8];if(byteenable[2])duty_cycle_reg[23:16]=writedata[23:16];if(byteenable[3])duty_cycle_reg[31:24]=writedata[31:24];endendend//寫控制寄存器always @ (posedge clk or negedge reset_n)beginif(reset_n==1'b0)control_reg=0;elsebeginif(write & chipselect & control_reg_selected)beginif(byteenable[0])control_reg=writedata[0];endendend//讀寄存器always @ (address or read or clock_divide_reg or duty_cycle_reg or control_reg or chipselect)beginif(read & chipselect)case(address)2'b00:readdata<=clock_divide_reg;2'b01:readdata<=duty_cycle_reg;2'b10:readdata<=control_reg;default:readdata=32'h8888;endcaseend//控制寄存器assign pwm_enable=control_reg;//PWM功能部分always @ (posedge clk or negedge reset_n)beginif(reset_n==1'b0)PWM_counter=0;elsebeginif(pwm_enable)beginif(PWM_counter>=clock_divide_reg)PWM_counter<=0;elsePWM_counter<=PWM_counter+1;endelsePWM_counter<=0;endend always @ (posedge clk or negedge reset_n)beginif(reset_n==1'b0)PWM_out<=1'b0;elsebeginif(pwm_enable)beginif(PWM_counter<=duty_cycle_reg)PWM_out<=1'b1;elsePWM_out<=1'b0;endelsePWM_out<=1'b0;endendendmodule
上面的程式儲存好以後,命名為PWM.v,並將其存放到工程目錄下。
硬體設定
接下來,我們就通過SOPC Builder,來建立PWM模組了。首先,開啟Quartus軟體,進入SOPC Builder。進入後,點擊紅圈處
點擊後,如所示,點擊Next,
點擊後,如所示,點擊紅圈處,將我們剛才建立的PWM.v加進來。(我將PWM。v放到了工程目錄下的pwm檔案夾下)
加入後,系統會對PWM.v檔案進行分析,如所示,出現紅圈處的文字,說明分析成功,點擊close,關閉對話方塊。
然後點擊Next,如所示,通過,我們可以看到,PWM.v中的訊號都出現在這裡面了。我們可以根據我們的功能要求來配置這些訊號,其中,Interface是Avalon介面類型 ,它包括Avalon-MM、Avalon-ST、Avalon Memory Mapped Tristate Interface等等。Signal Type指的是各個Avalon介面類型下的訊號類型。PWM.v中的訊號我們已經在前面都介紹過了,大家按照上面的要求設定就可以了。預設情況只有PWM_out需要改動,如示紅圈處設定,
其中,Interface在下拉式功能表中選擇紅圈處所示的選項。
上面的選項都設定好以後,點擊Next,如所示,我們通過紅圈處的下拉條向下拉
拉到所示位置停止,我們將紅圈處的改選為NATIVE,這個地方就是地址對齊的選項,我們選擇為靜態地址對齊。其他的地方都預設,不需要改動。
這裡面還有很多選項,其中Timing部分需要說明一下,PWM的Avalon Slave連接埠與Avalon Slave連接埠時鐘訊號同步,讀/寫時的建立很保持時間為0,因為讀、寫寄存器僅需要一個刻度,所以讀/寫時為0等待切不需要讀延時。
接著點擊Next,如所示,其中紅圈處需要注意,這個地方需要可以建立新組,然後在SOPC Builder中體現出來。
點擊Finish後,會出現下面的對話方塊,點擊Yes,就會產生一個PWM_hw.tcl指令檔,大家可以開啟看一下,裡面放置的是剛才我們配置PWM時候的配置資訊。
上面都完成以後,我們回到了SOPC Builder介面,我們在左側邊欄中可以找到所示的紅圈處
大家看到了吧,MyIP就是我們剛才建立的group。雙擊PWM,我們建立PWM模組,如
點擊Finish,完成建立。
這裡還需要設定一步,點擊紅圈處
點擊後,如所示,點擊IP Serarch Path,然後點擊Add,添加PWM.v所在位置的路徑
添加後,如所示
點擊Finish完成。設定這個選項是為了讓SOPC Builder可以找到PWM.v的位置。不然就會出現下次你進入SOPC Builder的時候PWM模組無效的問題。
接下來的工作就是自動分配地址,分配中斷,編譯,等待......
編譯好以後,我們回到Quartus軟體介面,我們可以看到,PWM出現了,我將它接到了一個LED上了,我們可以通過PWM改變LED的亮度,實現LED漸亮漸滅的過程。
接下來又是編譯,等待.....
做好硬體部分工作以後,我們開啟NIOS IDE,開始軟體編程部分。
軟體開發
首先對工程重新編譯一次,Ctril+B,等待......
編譯好以後,我們來看一下system.h的變化情況,我們可以發現,多出來PWM部分了。
下面是PWM測試代碼,
#include <unistd.h>#include "system.h"//根據寄存器的位移量,我們定義一個結構體PWMtypedef struct{ volatile unsigned int divi; volatile unsigned int duty; volatile unsigned int enable;}PWM;int main(){int dir = 1; //將pwm指向PWM_0_BASE首地址PWM *pwm = (PWM *)PWM_0_BASE;//對pwm進行初始化,divi最大值為232-1。 pwm->divi = 1000; pwm->duty = 0; pwm->enable = 1; //通過不斷的改變duty值來改變LED一個周期亮燈的時間長短 while(1){ if(dir > 0){ if(pwm->duty < pwm->divi) pwm->duty += 100; else dir = 0; } else{ if(pwm->duty > 0) pwm->duty -= 100; else dir = 1; } usleep(100000); } return 0; }
到此,這一節的內容就講完了,大家如果對此有疑問可以給我留言,也可以通過qq:984597569,或者email:avic633@gmail.com與我聯絡,謝謝!