S02_CH08_ ZYNQ 定時器中斷實驗


S02_CH08_ ZYNQ 定時器中斷實驗

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

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和存儲器控制器產生。

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的工程,芯片類型根據自身情況設置。

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的中斷原理和中斷類型,詳細介紹了私有定時器,建立了完整的工程進行測試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM