這篇筆記是我之前在調試MicroBlaze時記錄下來的,當時在網上查了一些資料,發現都講的不是特別清楚,所以自己整理了一個筆記,如有差錯,希望大家指正。
在這次示例中,本文完成了一個改變流水燈的間隔時間以及按鍵檢測的間隔時間可變的一個MicroBlaze程序,修改參數后不用再經過布局布線,方便調試。
典型的嵌入式程序設計流程如下:

如上圖所示,在FPGA中開發嵌入式系統主要需要三個工具套件,分別為ISE,XPS和SDK。
ISE篇
先打開ISE,新建一個Project

我用的是黑金的AX309的板子


然后建一個Embedded Processor

XPS篇
建完后會自動打開XPS軟件,點擊Yes創建一個新的XPS Project

點擊OK,使用AXI總線

選擇Create a System for a Custom Board, Reference Clock Frequecy是板上的時鍾頻率,也可以是你用PLL生成的時鍾然后輸到MIcroBlaze系統的時鍾頻率,Optimization Strategy選擇Area和Throughput都可以,在這里我選的是Area。點擊Next

處理器頻率選擇100Mhz,Local Memory Size選擇32KB,用於緩存你寫的應用程序,盡量選大一些。不用添加其它外設,點擊Finish即可

完了之后會生成一個基本的MicroBlaze系統,里面包含了MIcroBlaze系統所需的基本模塊,如clock generator,axi4liite等等。

在這里,我們需要自己新建一個IP,在建IP之前,我們要先想好這個IP的功能和端口是什么,包括輸入和輸出端口,我目前要實現的功能是,通過檢測按鍵KEY1,來開啟和關閉流水燈,同時可以通過修改MIcroBlaze中的程序來改變流水燈的間隔時間以及按鍵檢測的間隔時間,所以在這個自定義IP中,端口有兩個,一個是控制4個LED燈亮的輸出端口LED[3:0],一個是按鍵的輸入端口dip。
首先,點擊Hardware>>Create or Import Peripheral

點擊next

選擇Create templates for a new perphera,點擊next

在這里有兩個選擇,上面那個是將你創建的外設安裝到EDK的庫中,這樣下次再創建工程就可以直接利用你創建的外設,可重復利用,另外一個選項就是只用於當前XPS Project,在這里我們這個外設只在這個工程中使用,所以選擇To an XPS Project,點擊next。

為你創建的這個外設取名字,我這里取的是blink,你可以在description中添加對這個外設功能的描述。點擊next

選擇外設總線相連的協議,在這里選的是AXI4_Lite,點擊next。

在這里需要勾選User logic software registers,表示我們是通過軟件來更改寄存器的值,從而來更改我們所需要改變的參數的值。點擊next

在這里我們選擇寄存器的個數,一般有幾個參數就選擇幾個寄存器,也可以把幾個參數合在一個寄存器里,每個寄存器是32位的,總共可以選擇32個寄存器,在這里我們需要修改兩個參數(流水燈亮的間隔時間,按鍵檢測的間隔時間),所以這里選擇兩個寄存器。

在這里不作修改,點擊next

這里是選擇是否生成仿真平台和仿真模型(Simulation Model),用於在ISim或Modelsim中進行仿真,在這里我們不需要仿真,不勾選,點擊next

因為我們是用Verilog進行編程,所以需要勾選Generate stub“user logic” template in Verilog instead of VHDL,之后我們再對user logic模塊進行修改時就可以用Verilog了,第三個勾選的話可以生成相關驅動,這樣便可以利用驅動中的API函數了,不過不勾選也沒關系,因為我們需要的函數在MIcroBlaze核里都有,在這里我們不勾選,點擊next


點擊finish

這個時候可以看到,在IP Catalog中的USER有我們創建的外設BLINK

在這里我們需要修改幾個文件,首先右鍵BLINK>>view MPD,在## Ports處添加外設的端口LED[3:0]和dip

一個是user logic這里需要添加端口

最后一個就是generic map這里需要添加映射


通過軟件控制的寄存器便是這兩個寄存器

接下來就是自己為實現功能而寫的一些模塊,以下是為了實現功能而編寫的Verilog文件
//User Logic //reg0 流水燈時間間隔 //reg1 按鍵檢測時間間隔,用於防抖 //reg1 讀入的時候讀的是dip的狀態 reg [31:0] timer; reg timer_enable; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin timer <= 32'b0; end else if (timer == 4*slv_reg0) begin timer <= 32'b0; end else if (timer_enable) begin timer <= timer + 1'b1; end else begin timer <= 32'b0; end end //按鍵間隔時間計數 reg [31:0] counter; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin counter <=32'b0; end else if (counter == slv_reg1) begin counter <= 32'b0; end else begin counter <= counter + 1'b1; end end //檢測按鍵的下降沿 reg dip_temp1; reg dip_temp2; wire dip_neg; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin dip_temp1 <= 1'b1; end else if (counter == slv_reg1) begin dip_temp1 <= dip; end else begin dip_temp1 <= dip_temp1; end end //鎖定檢測dip的值 always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin dip_temp2 <= 1'b1; end else begin dip_temp2 <= dip_temp1; end end assign dip_neg = dip_temp2 & (~dip_temp1); //流水燈的主要狀態機 reg [3:0] current_state; reg [3:0] next_state; parameter State_idle = 4'd0; parameter First_LED = 4'd1; parameter Second_LED = 4'd2; parameter Third_LED = 4'd3; parameter Last_LED = 4'd4; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin current_state <= State_idle; end else begin current_state <= next_state; end end always @ (current_state,dip_neg,timer) begin next_state = 4'dx; case (current_state) State_idle: begin if (dip_neg) next_state = First_LED; else next_state = State_idle; end First_LED: begin if (dip_neg) next_state = State_idle; else if (timer == slv_reg0 - 1) next_state = Second_LED; else next_state = First_LED; end Second_LED: begin if (dip_neg) next_state = State_idle; else if (timer == 2*slv_reg0 - 1) next_state = Third_LED; else next_state = Second_LED; end Third_LED: begin if (dip_neg) next_state = State_idle; else if (timer == 3*slv_reg0 - 1) next_state = Last_LED; else next_state = Third_LED; end Last_LED: begin if (dip_neg) next_state = State_idle; else if (timer == 4*slv_reg0 - 1) next_state = First_LED; else next_state = Last_LED; end default: next_state = State_idle; endcase end always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin timer_enable <= 1'b0; LED <= 4'b0000; end else begin case (next_state) State_idle: begin timer_enable <= 1'b0; LED <= 4'b0000; end First_LED: begin timer_enable <= 1'b1; LED <= 4'b0001; end Second_LED: begin timer_enable <= 1'b1; LED <= 4'b0010; end Third_LED: begin timer_enable <= 1'b1; LED <= 4'b0100; end Last_LED: begin timer_enable <= 1'b1; LED <= 4'b1000; end default: begin timer_enable <= 1'b0; LED <= 4'b0000; end endcase end end
然后再添加自己創建的外設BLINK,添加進來后,點擊PORT欄,將LED和dip設為External Port,如下圖所示

完成上述修改后,先在XPS中點擊Hardware>>Generate Netlist,然后返回到ISE中,新建一個V文件,用作頂層文件,來例化這個MIcroBlaze核

然后,編寫UCF文件,將端口信號與管腳一一對應起來


點擊Finish,在生成的helloworld.c文件中進行程序編寫

在這里,我先額外建了一個文件夾blink_led,放置寫寄存器的操作
/* * blink_led.c * * Created on: 2018-5-23 */ #include "blink_led.h" #include "xio.h" //流水燈的間隔時間 void set_LED_interval(uint32_t bassaddr_p, uint32_t num_ms, uint32_t num_us) { //CLK 100MHZ XIo_Out32((bassaddr_p) + 0x00000000, num_ms*100000 + num_us*100); } //按鍵檢測的間隔時間 void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms) { //CLK 100MHZ XIo_Out32((bassaddr_p + 0x00000004), num_ms*100000); }
其中寄存器的讀寫是靠xio.h中包含的API函數來進行操作的,所以要添加include“xio.h”,另外,此程序需要在helloword.c引用,所以也需要編寫一個頭文件,在helloword.c中進行程序的編寫時需要包含進去
/* * blink_led.h * * Created on: 2018-5-23 */ #ifndef BLINK_LED_H_ #define BLINK_LED_H_ #include <stdint.h> void set_LED_interval(uint32_t bassaddr_p, uint32_t num_ms, uint32_t num_us); void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms); #endif /* BLINK_LED_H_ */
然后再在helloword.c中進行程序的編寫
*/ #include <stdio.h> #include "platform.h" #include "blink_led/blink_led.h" #include "xil_printf.h" #define blink_LED_ADDR 0x7C600000 int main() { init_platform(); set_LED_interval(blink_LED_ADDR, 5000, 0); print("set_LED_interval Completed\n"); set_dip_interval(blink_LED_ADDR, 10); print("set_dip_interval Completed\n"); return 0; }
在Debug之前,先設置Debug Configuration,勾上Connect STDIO to Console,這樣,打印出來的信息就會顯示在Console欄中。

至此整個自定義IP過程完成,接下來就是上電,將程序燒寫進去進行調試就可以了。
