天天看點

S02_CH08_ ZYNQ 定時器中斷實驗

S02_CH08_ ZYNQ 定時器中斷實驗

上一章實作了PS接受來自PL的中斷,本章将在ZYNQ的純PS裡實作私有定時器中斷。每隔一秒中斷一次,在中斷函數裡計數加1,通過序列槽列印輸出。

8.1中斷原理

中斷對于保證任務的實時性非常必要,在ZYNQ裡內建了中斷控制器GIC(Generic Interrupt Controller).GIC可以接受I/O外設中斷IOP和PL中斷,将這些中斷發給CPU。

中斷體系結構框圖圖下:

S02_CH08_ ZYNQ 定時器中斷實驗
8.1.1軟體中斷(SGI)

SGI通過寫ICDSGIR寄存器産生SGI.

8.1.2共享中斷SPI

通過PS和PL内各種I/O和存儲器控制器産生。

8.1.3私有中斷(PPI)

包含:全局定時器,私有看門狗定時器,私有定時器以及來自PL的FIQ/IRQ。本文主要介紹PPI,其它的請參考官方手冊ug585_Zynq_7000_TRM.pdf。

ZYNQ每個CPU連結5個私有外設中斷,所有中斷的觸發類型都是固定不變的。并且來自PL的快速中斷信号FIQ和中斷信号IRQ反向,然後送到中斷控制器是以盡管在ICDICFR1寄存器内反應的他們是低電平觸發,但是PS-PL接口中為高電平觸發。如圖所示:

S02_CH08_ ZYNQ 定時器中斷實驗
8.1.4私有定時器

zynq中每個ARM core都有自己的私有定時器,私有定時器的工作頻率為CPU的一半,比如Miz702的ARM工作頻率為666MHZ,則私有定時器的頻率為333MHz.

私有定時器的特性如下:

(1)32位計數器,達到零時産生一個中斷

(2)8位預分頻計數器,可以更好的控制中斷周期

(3)可配置一次性或者自動重加載模式

(4)定時器時間可以通過下式計算:

定時時間 = 1/定時器頻率*(預加載值+1)

8.2 搭建硬體工程

Step1:建立一個名為為Miz_sys的工程,晶片類型根據自身情況設定。

Step2:建立一個BD檔案,并命名為system。

Step3:添加 ZYNQ7 Processing System,根據自己的硬體類型配置好輸入時鐘頻率與記憶體型号。

Step4:在ZYNQ7 Processing System配置視窗中,使能中斷,單擊OK完成配置。

S02_CH08_ ZYNQ 定時器中斷實驗

Step5:單擊添加IP按鈕,添加兩個邏輯門子產品和一個concat  IP。

S02_CH08_ ZYNQ 定時器中斷實驗
S02_CH08_ ZYNQ 定時器中斷實驗

Step6:輕按兩下邏輯門子產品,将其配置為非功能。

S02_CH08_ ZYNQ 定時器中斷實驗

Step7:按以下電路,完善整體電路。

S02_CH08_ ZYNQ 定時器中斷實驗

Step8:右鍵單擊Block檔案,檔案選擇Generate the Output Products。

Step9:右鍵單擊Block檔案,選擇Create a HDL wrapper,根據Block檔案内容産生一個HDL 的頂層檔案,并選擇讓vivado自動完成。

Step10:添加一個限制檔案,打開對應自己硬體的原理圖,檢視按鍵部分引腳連接配接情況,完成限制。Miz702限制檔案如下所示:

set_property PACKAGE_PIN T18 [get_ports {SW1[0]}]

set_property IOSTANDARD LVCMOS33 [get_ports {SW1[0]}]

set_property PACKAGE_PIN R18 [get_ports {SW2[0]}]

set_property IOSTANDARD LVCMOS33 [get_ports {SW2[0]}]

Step10:生成Bit檔案。

8.3 加載到SDK

Step1:導出硬體。

Step2:建立一個空SDK工程,并添加一個main.c的檔案。

Step3:在main.c檔案中添加以下程式,按Ctrl+S儲存後自動開始編譯。

/*

* main.c

*

*  Created on: 2016年6月26日

*      Author: Administrator

*/

#include <stdio.h>

#include "xadcps.h"

#include "xil_types.h"

#include "Xscugic.h"

#include "Xil_exception.h"

#include "xscutimer.h"

//timer info

#define TIMER_DEVICE_ID     XPAR_XSCUTIMER_0_DEVICE_ID

#define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID

#define TIMER_IRPT_INTR     XPAR_SCUTIMER_INTR

#define TIMER_LOAD_VALUE    0x13D92D3F

static XScuGic Intc; //GIC

static XScuTimer Timer;//timer

static void TimerIntrHandler(void *CallBackRef)

{

    static int sec = 0;   //計數

    XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;

    XScuTimer_ClearInterruptStatus(TimerInstancePtr);

    sec++;

    printf(" %d Second\n\r",sec);  //每秒列印輸出一次

}

void SetupInterruptSystem(XScuGic *GicInstancePtr,

        XScuTimer *TimerInstancePtr, u16 TimerIntrId)

{

        XScuGic_Config *IntcConfig; //GIC config

        Xil_ExceptionInit();

        //initialise the GIC

        IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);

        XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,

                        IntcConfig->CpuBaseAddress);

        Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,

                    (Xil_ExceptionHandler)XScuGic_InterruptHandler,//connect to the hardware

                    GicInstancePtr);

        XScuGic_Connect(GicInstancePtr, TimerIntrId,

                        (Xil_ExceptionHandler)TimerIntrHandler,//set up the timer interrupt

                        (void *)TimerInstancePtr);

        XScuGic_Enable(GicInstancePtr, TimerIntrId);//enable the interrupt for the Timer at GIC

        XScuTimer_EnableInterrupt(TimerInstancePtr);//enable interrupt on the timer

        Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ); //Enable interrupts in the Processor.

    }

int main()

{

     XScuTimer_Config *TMRConfigPtr;     //timer config

     printf("------------START-------------\n");

     //私有定時器初始化

     TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);

     XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);

     //set up the interrupts

     SetupInterruptSystem(&Intc,&Timer,TIMER_IRPT_INTR);

     //加載計數周期,私有定時器的時鐘為CPU的一般,為333MHZ,如果計數1S,加載值為1sx(333x1000x1000)(1/s)-1=0x13D92D3F

     XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE);

     //自動裝載

     XScuTimer_EnableAutoReload(&Timer);

     //啟動定時器

     XScuTimer_Start(&Timer);

     while(1);

     return 0;

}

Step4:右擊工程,選擇Debug as ->Debug configuration。

Step5:選中system Debugger,輕按兩下建立一個系統調試。

S02_CH08_ ZYNQ 定時器中斷實驗

Step6:設定系統調試。

S02_CH08_ ZYNQ 定時器中斷實驗

打開系統自帶的視窗調試助手,點選運作按鈕開始運作程式。

S02_CH08_ ZYNQ 定時器中斷實驗

系統運作結果如下圖所示:

S02_CH08_ ZYNQ 定時器中斷實驗

8.4 程式分析

本章的程式講解依然是從main函數處開始。首先我們看看整個程式的結構。

S02_CH08_ ZYNQ 定時器中斷實驗

程式一開始的指針和測試程式就不再啰嗦了。接下來的查找配置程式也與我們上一章PL_PS中斷是一樣的,隻是換了個函數名字與基位址而已。還未掌握的可以看看我們上一章的分析。

接下來看到定時器的初始化程式,直接跟蹤這個程式,檢視其定義。如下圖所示:

S02_CH08_ ZYNQ 定時器中斷實驗

Xilinx官方提供的程式,開頭都會給出程式的功能和參數的注釋,若是不懂程式是什麼意思,不妨先看看這些。從圖檔上的程式功能注釋來看,這是一個指定定時器的初始化函數,在這個定時器被其他函數調用之前,這個函數必須先被調用。也就是說必須先進行定時器的初始化,定時器才能正常的使用。接下來看到程式部分。程式一開始用了一個特定的函數來檢測傳遞進來的參數是否是空的,如果是,則不能正常跳轉到下一個語句。

接下來的一句是檢測定時器是否已經開始了(也就是有沒有初始化成功),如果沒有,就跳到if中的語句裡面。否則,傳回一個已經初始化了的标志。接下來我們看到if語句裡面的程式。

S02_CH08_ ZYNQ 定時器中斷實驗

一開始,程式把配置指針中的裝置id拷貝進入了定時器的執行個體結構中的DeviceId。接着把程式的最後一個參數EffectiveAddress(可以猜到這個是一個基位址,具體是什麼現在還不知曉)也傳遞到了定時器的執行個體結構中的BaseAddr,緊接着把執行個體結構裡的IsStarted标志置為0,再之後把執行個體結構中的IsReady标志置為XIL_COMPONENT_IS_READY。最後再給Status變量指派為XST_SUCCUSS。可以看出來,定時器的一系列的初始化都是圍繞着這個執行個體結構來的。那麼,我們就來看看這個執行個體結構到底是什麼?我們在主函數中找到這個執行個體結構。

S02_CH08_ ZYNQ 定時器中斷實驗

在這裡,這個執行個體結構是指向一個結構體的,我們來看看這個結構體的内容。

S02_CH08_ ZYNQ 定時器中斷實驗

可以看到,這個結構體中又包含了一個結構體,我們再繼續看看它包含的這個結構體。

S02_CH08_ ZYNQ 定時器中斷實驗

此處,我們發現這兩個結構體中的内容正好是我們剛才初始化程式中配置的那些參數。接下來,我們再來看看這些參數是如何來的。這就得看到剛才我們提到過的查找配置程式了。

S02_CH08_ ZYNQ 定時器中斷實驗

這些參數就是通過查找配置這個程式擷取的。我們回過來看看這個程式。

S02_CH08_ ZYNQ 定時器中斷實驗

從上圖可以看到,這些配置是存放在一個數組當中的,讓我們繼續檢視一下數組。

S02_CH08_ ZYNQ 定時器中斷實驗

圖中的兩個對象,是我們parameters.h中系統自動生成的定時器的裝置位址和基位址。隻要我們在硬體電路上添加了定時器,那這兩個參數就會自動被添加,定時器的參數也将會自動生成。

回到main函數的分析,接下來的是一個建立中斷的函數,這個函數帶了三個參數:第一個參數指向了中斷控制器,第二個指向的是定時器,第三個是中斷号。将滑鼠放在中斷号上面時,我們可以發現中斷号為29。我們可以在ug585的中斷部分檢視一下中斷号29是什麼類型的中斷。

S02_CH08_ ZYNQ 定時器中斷實驗

可以看到這是定時器中斷,上升沿觸發的。這樣定義是有一定依據的。這段程式與上一章PL_PS中斷是差不多的,我們上一章對其進行過詳細的分析,大家可參照上一章介紹的方法對其進行分析。

回到main函數,接下來的這句是本章程式中的核心部分。它将程式的定時時間設定為了1秒。那麼,系統是如何做到定時一秒的呢?定時器時間可以通過下式計算:定時時間 = 1/定時器頻率*(預加載值+1),則可以推算出:預加載值=定時時間*定時器頻率-1。定時時間是已知的,如果再知道定時器頻率則可以計算出加載的值,檢視xilinx的程式設計指導手冊ug585-zynq-7000-TRM的定時器篇得知:

S02_CH08_ ZYNQ 定時器中斷實驗

定時器頻率為處理器頻率的一半,比如Miz702的ARM工作頻率為666MHZ,則私有定時器的頻率為333MHz,則加載值為1*333_000_000*(1/s)-1=0x13D92D3F。

回到main函數的分析,當我們把滑鼠停留在裝載加載值函數XScuTimer_LoadTimer上時,SDK會顯示關于這個函數的一些資訊。

S02_CH08_ ZYNQ 定時器中斷實驗

我們看到這個函數的原函數是向一個寄存器位址中寫入了預加載值,我們計算一下這個寄存器位址。原函數的第一個參數我們剛才提到過,就是那個執行個體結構中的基位址,也就是定時器的基位址。我們在xparameters.h中找到它。

S02_CH08_ ZYNQ 定時器中斷實驗

此時我們就可以計算了:F8F00600+00=F8F00600。在ug585中查找一下這個寄存器位址,看看這個寄存器是幹什麼用的。

S02_CH08_ ZYNQ 定時器中斷實驗

可以看到,這個寄存器就是個裝載預加載值的寄存器。接着看到main函數的下一句。

S02_CH08_ ZYNQ 定時器中斷實驗

這段程式與上一句差不多一緻,我們通過分析寄存器,看看這段程式完成的功能。這段程式的寄存器位址為:F8F00600+08=F8F00608。這段程式寫入的資料為:(F8F00600+08)|0x00000002=F8F0060A。查找ug585看看寄存器的功能。

S02_CH08_ ZYNQ 定時器中斷實驗
S02_CH08_ ZYNQ 定時器中斷實驗

這個寄存器是一個預加載值控制寄存器,通過寫入我們上面分析出的那個資料,把中斷的預加載值設定為了自動裝載模式(也就是中斷一次過後,系統又會自動的裝入初值,不用人工載入初值),也就是圖中用方框圈出的部分。

回到main函數,講解最後一個函數,啟動定時器的函數。還是先跟蹤一下這個函數。

S02_CH08_ ZYNQ 定時器中斷實驗

可以看出來,這也是一個通過讀寫寄存器的方式來操作定時器的過程,我們依然是來分析一下寄存器。

S02_CH08_ ZYNQ 定時器中斷實驗

之前的一些初始化程式就跳過不再講解了,直接看到上圖所示的程式,這個程式的分析與我們剛才講的裝載初值的方法是一樣的,這裡我們可以直接計算此程式讀出的寄存器位址:F8F00600+08=F8F00608。

S02_CH08_ ZYNQ 定時器中斷實驗

這一句的意思就是把剛才得到的寄存器的位址與0x00000001或操作。此時寄存器位址為:F8F00608 | 0x00000001U =F8F00609。

S02_CH08_ ZYNQ 定時器中斷實驗

這裡我們發現,上圖中這個函數的源程式中,第一個參數即為我們第一次得到的寄存器位址,寫入的資料為第二次得到的寄存器位址。也就是說向F8F00608這個寄存器裡寫入資料F8F00609。檢視ug585,看看這麼配置是什麼意思。

S02_CH08_ ZYNQ 定時器中斷實驗

此時就可以清晰的知曉,通過控制這個寄存器的最後一位,就可以控制定時器的工作與否,剛才我們寫入的是F8F00609,将最後一位置1,也就是啟動了定時器。

8.5 本章小結

中斷對于實時系統是非常重要的,可以說是是實時性的保障吧。本章簡要介紹了ZYNQ的中斷原理和中斷類型,詳細介紹了私有定時器,建立了完整的工程進行測試。

S02_CH08_ ZYNQ 定時器中斷實驗