S02_CH08_ ZYNQ 定時器中斷實驗

S02_CH08_ ZYNQ 定時器中斷實驗

上一章實現了PS接受來自PL的中斷,本章將在ZYNQ的純PS裏實現私有定時器中斷。每隔一秒中斷一次,在中斷函數裏計數加1,經過串口打印輸出。 c++

8.1中斷原理

中斷對於保證任務的實時性很是必要,在ZYNQ裏集成了中斷控制器GIC(Generic Interrupt Controller).GIC能夠接受I/O外設中斷IOP和PL中斷,將這些中斷髮給CPU。
中斷體系結構框圖圖下:
wps1F15.tmp 編程

8.1.1軟件中斷(SGI)

SGI經過寫ICDSGIR寄存器產生SGI. 數組

8.1.2共享中斷SPI

經過PS和PL內各類I/O和存儲器控制器產生。 app

8.1.3私有中斷(PPI)

包含:全局定時器,私有看門狗定時器,私有定時器以及來自PL的FIQ/IRQ。本文主要介紹PPI,其它的請參考官方手冊ug585_Zynq_7000_TRM.pdf。
ZYNQ每一個CPU連接5個私有外設中斷,全部中斷的觸發類型都是固定不變的。而且來自PL的快速中斷信號FIQ和中斷信號IRQ反向,而後送到中斷控制器所以儘管在ICDICFR1寄存器內反應的他們是低電平觸發,可是PS-PL接口中爲高電平觸發。如圖所示:
wps1F16.tmp 函數

8.1.4私有定時器

zynq中每一個ARM core都有本身的私有定時器,私有定時器的工做頻率爲CPU的一半,好比Miz702的ARM工做頻率爲666MHZ,則私有定時器的頻率爲333MHz.
私有定時器的特性以下:
(1)32位計數器,達到零時產生一箇中斷
(2)8位預分頻計數器,能夠更好的控制中斷週期
(3)可配置一次性或者自動重加載模式
(4)定時器時間能夠經過下式計算:
定時時間 = 1/定時器頻率*(預加載值+1) 測試

8.2 搭建硬件工程

Step1:新建一個名爲爲Miz_sys的工程,芯片類型根據自身狀況設置。 3d

Step2:建立一個BD文件,並命名爲system。 指針

Step3:添加 ZYNQ7 Processing System,根據本身的硬件類型配置好輸入時鐘頻率與內存型號。 調試

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

wps1F17.tmp

Step5:單擊添加IP按鈕,添加兩個邏輯門模塊和一個concat  IP。

wps1F28.tmp wps1F29.tmp

Step6:雙擊邏輯門模塊,將其配置爲非功能。

wps1F2A.tmp

Step7:按如下電路,完善總體電路。

wps1F3A.tmp

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,雙擊建立一個系統調試。

wps1F3B.tmp

Step6:設置系統調試。

wps1F4C.tmp

打開系統自帶的窗口調試助手,點擊運行按鈕開始運行程序。

wps1F4D.tmp

系統運行結果以下圖所示:

wps1F4E.tmp

8.4 程序分析

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

wps1F5F.tmp

程序一開始的指針和測試程序就再也不囉嗦了。接下來的查找配置程序也與咱們上一章PL_PS中斷是同樣的,只是換了個函數名字與基地址而已。還未掌握的能夠看看咱們上一章的分析。

接下來看到定時器的初始化程序,直接跟蹤這個程序,查看其定義。以下圖所示:

wps1F60.tmp

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

接下來的一句是檢測定時器是否已經開始了(也就是有沒有初始化成功),若是沒有,就跳到if中的語句裏面。不然,返回一個已經初始化了的標誌。接下來咱們看到if語句裏面的程序。

wps1F61.tmp

一開始,程序把配置指針中的設備id拷貝進入了定時器的實例結構中的DeviceId。接着把程序的最後一個參數EffectiveAddress(能夠猜到這個是一個基地址,具體是什麼如今還不知曉)也傳遞到了定時器的實例結構中的BaseAddr,緊接着把實例結構裏的IsStarted標誌置爲0,再以後把實例結構中的IsReady標誌置爲XIL_COMPONENT_IS_READY。最後再給Status變量賦值爲XST_SUCCUSS。能夠看出來,定時器的一系列的初始化都是圍繞着這個實例結構來的。那麼,咱們就來看看這個實例結構究竟是什麼?咱們在主函數中找到這個實例結構。

wps1F71.tmp在這裏,這個實例結構是指向一個結構體的,咱們來看看這個結構體的內容。

wps1F72.tmp

能夠看到,這個結構體中又包含了一個結構體,咱們再繼續看看它包含的這個結構體。

wps1F73.tmp

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

wps1F84.tmp

這些參數就是經過查找配置這個程序獲取的。咱們回過來看看這個程序。

wps1F85.tmp

從上圖能夠看到,這些配置是存放在一個數組當中的,讓咱們繼續查看一下數組。

wps1F86.tmp

圖中的兩個對象,是咱們parameters.h中系統自動生成的定時器的設備地址和基地址。只要咱們在硬件電路上添加了定時器,那這兩個參數就會自動被添加,定時器的參數也將會自動生成。

回到main函數的分析,接下來的是一個創建中斷的函數,這個函數帶了三個參數:第一個參數指向了中斷控制器,第二個指向的是定時器,第三個是中斷號。將鼠標放在中斷號上面時,咱們能夠發現中斷號爲29。咱們能夠在ug585的中斷部分查看一下中斷號29是什麼類型的中斷。

wps1F87.tmp

能夠看到這是定時器中斷,上升沿觸發的。這樣定義是有必定依據的。這段程序與上一章PL_PS中斷是差很少的,咱們上一章對其進行過詳細的分析,你們可參照上一章介紹的方法對其進行分析。

回到main函數,接下來的這句是本章程序中的核心部分。它將程序的定時時間設置爲了1秒。那麼,系統是如何作到定時一秒的呢?定時器時間能夠經過下式計算:定時時間 = 1/定時器頻率*(預加載值+1),則能夠推算出:預加載值=定時時間*定時器頻率-1。定時時間是已知的,若是再知道定時器頻率則能夠計算出加載的值,查看xilinx的編程指導手冊ug585-zynq-7000-TRM的定時器篇得知:

wps1F97.tmp

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

回到main函數的分析,當咱們把鼠標停留在裝載加載值函數XScuTimer_LoadTimer上時,SDK會顯示關於這個函數的一些信息。

wps1F98.tmp

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

wps1F99.tmp

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

wps1FAA.tmp

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

wps1FAB.tmp

這段程序與上一句差很少一致,咱們經過分析寄存器,看看這段程序完成的功能。這段程序的寄存器地址爲:F8F00600+08=F8F00608。這段程序寫入的數據爲:(F8F00600+08)|0x00000002=F8F0060A。查找ug585看看寄存器的功能。

wps1FBC.tmp

wps1FBD.tmp

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

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

wps1FBE.tmp

能夠看出來,這也是一個經過讀寫寄存器的方式來操做定時器的過程,咱們依然是來分析一下寄存器。

wps1FCE.tmp

以前的一些初始化程序就跳過再也不講解了,直接看到上圖所示的程序,這個程序的分析與咱們剛纔講的裝載初值的方法是同樣的,這裏咱們能夠直接計算此程序讀出的寄存器地址:F8F00600+08=F8F00608。

wps1FCF.tmp

這一句的意思就是把剛纔獲得的寄存器的地址與0x00000001或操做。此時寄存器地址爲:F8F00608 | 0x00000001U =F8F00609。

wps1FD0.tmp

這裏咱們發現,上圖中這個函數的源程序中,第一個參數即爲咱們第一次獲得的寄存器地址,寫入的數據爲第二次獲得的寄存器地址。也就是說向F8F00608這個寄存器裏寫入數據F8F00609。查看ug585,看看這麼配置是什麼意思。

wps1FE1.tmp

此時就能夠清晰的知曉,經過控制這個寄存器的最後一位,就能夠控制定時器的工做與否,剛纔咱們寫入的是F8F00609,將最後一位置1,也就是啓動了定時器。

8.5 本章小結

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

相關文章
相關標籤/搜索