學會Zynq(9)定時器使用示例(PPI)

FPGADesigner發表於2019-03-19

定時器資源

每個Cortex-A9處理器都有私有的32位定時器32位看門狗定時器。這兩種定時器都是32位的計數器,計數到0時產生中斷;帶有8位的預分頻器,能夠更好地控制中斷週期;可配置為單次過載或自動過載模式;可配置初始值。它們的工作時鐘固定為CPU頻率的1/2(CPU_3x2x)。

兩個CPU同時共享一個64位的全域性定時器GT,這是一個遞增的計數器,帶有自動遞增功能。全域性定時器與私有定時器在記憶體中對映的地址空間相同。兩個Cortex-A9有各自的64位的比較器,可以共同訪問全域性定時器,達到比較器值時產生一個私有中斷。它的時鐘也固定為CPU頻率的1/2(CPU_3x2x)。

除了兩個CPU的私有看門狗定時器,還有一個24位的系統看門狗SWDT,在發生災難性的系統故障時發出訊號(比如PS中的PLL工作失常)。SWDT可以工作在CPU頻率的1/4或1/6(CPU_1x),也可以工作在裝置外部或PL提供的時鐘下,並向它們輸出一個復位訊號。

此外PS中還有兩個TTC(Triple Timer Counters),每個TTC都有三個獨立的定時器/計數器。TTC只能工作在CPU頻率的1/4或1/6(CPU_1x),Zynq使用該定時器計算來自MIO管腳或PL的訊號脈衝寬度。

私有定時器和看門狗定時器、全域性定時器屬於PPI;SWDT和TTC屬於SPI。各種定時器資源之間的關係如下:
在這裡插入圖片描述


私有定時器的使用

幾種定時器中,私有定時器是最常用的,使用雙核時可能會用到全域性定時器。私有定時器是CPU五種PPI中斷源的一種,固定為上升沿敏感。

做一個簡單的設計體會私有定時器的使用,每隔1s串列埠輸出一次。Vivado中搭建硬體環境,使用最小系統即可,匯出硬體到SDK中。

timer.h檔案的程式碼如下:

#include <stdio.h>
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#include "xscutimer.h"

//---------------------------------------------------------
//        引數定義
//---------------------------------------------------------
#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  //定時器中斷ID號
#define TIMER_LOAD_VALUE    0x13D92D3F          //定時器裝載值

//---------------------------------------------------------
//        函式申明
//---------------------------------------------------------
void SetupInterruptSystem(XScuGic *GicInstancePtr,
        XScuTimer *TimerInstancePtr, u16 TimerIntrId);
void TimreInit(XScuTimer Timer, XScuGic Intc);

timer.c檔案的程式碼如下:

#include "timer.h"

//---------------------------------------------------------
//        定時器中斷處理程式
//---------------------------------------------------------
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;
    IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
    XScuGic_CfgInitialize(GicInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress);
    /* 設定中斷異常 */
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
    		(Xil_ExceptionHandler)XScuGic_InterruptHandler, GicInstancePtr);
    Xil_ExceptionEnable();
    /* 設定定時器中斷  */
    XScuGic_Connect(GicInstancePtr, TimerIntrId,
          (Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr);

    XScuGic_Enable(GicInstancePtr, TimerIntrId); //使能中斷
    XScuTimer_EnableInterrupt(TimerInstancePtr); //使能定時器中斷
}

//---------------------------------------------------------
//        定時器初始化程式
//---------------------------------------------------------
void TimreInit(XScuTimer Timer, XScuGic Intc)
{
	/* 私有定時器初始化  */
	XScuTimer_Config *TMRConfigPtr;
    TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
    XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);

    XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE); // 載入計數週期
    XScuTimer_EnableAutoReload(&Timer);            // 設定自動裝載模式
    SetupInterruptSystem(&Intc,&Timer,TIMER_IRPT_INTR); // 設定定時器中斷
    XScuTimer_Start(&Timer);                       // 啟動定時器
}

mian.c檔案的程式碼如下:

#include "timer.h"

static XScuTimer Timer;   //定時器
static XScuGic Intc;      //中斷控制器

int main()
{
	TimreInit(Timer, Intc);

    while(1);
    return 0;
}

相關API函式

查閱PPI列表,可以看到私有定時器的中斷號為29。

在這裡插入圖片描述

1.私有定時器初始化

初始化部分中,我們看到了似曾相識的XScuTimer、XscuTimer_Config兩個結構體、XScuTimer_LookupConfig()和XScuTimer_CfgInitialize()兩個函式,只是從“GPIO裝置”、“中斷裝置”換成了“定時器裝置”。這算是Zynq中各種裝置(device)初始化的套路,甚至傳入的引數型別都是十分相近的,這裡不再贅述。

/* 私有定時器初始化  */
	XScuTimer_Config *TMRConfigPtr;
    TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);

2.載入計數週期

使用XScuTimer_LoadTimer()函式載入定時器的計數器中的值,其本質上只是操作Timer Load暫存器。函式原型採用巨集定義,但也可以看作一個C語言風格的函式介面:

#define XScuTimer_LoadTimer(InstancePtr, Value)				\
	XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr,		\
			XSCUTIMER_LOAD_OFFSET, (Value))
//可視作如下C語言函式
void XScuTimer_LoadTimer(XScuTimer *InstancePtr, u32 Value)

私有定時器是從裝載值遞減到0時發出中斷。我們這裡預設使用666MHz的CPU時鐘,則私有定時器的工作時鐘為333MHz,每1/333M秒減1。因此若想定時1s,則裝載值為1/(1/333M)-1,將該值在timer.h中巨集定義。

3.設定裝載模式

本例程需要定時器一直工作,因此使用XScuTimer_EnableAutoReload()函式啟用自動裝載模式。不需要時使用XScuTimer_DisableAutoReload()函式禁用自動裝載。這兩個函式本質上也是巨集定義,操作相關暫存器中的相應bit位。下面只給出等效的C語言模型:

void XScuTimer_EnableAutoReload(XScuTimer *InstancePtr)
void XScuTimer_DisableAutoReload(XScuTimer *InstancePtr)

4.設定定時器中斷

這部分通過自定義函式來設定定時器的中斷,過程和第8篇中PL中斷的初始化過程基本相同。實際上用到中斷時基本都要有這個過程:初始化中斷控制器、設定中斷異常、連線中斷、使能中斷。
我們這裡使用的是定時器中斷,連線函式如下。在定時器中斷處理程式中,我們必須清除中斷標誌,因此XscuGic_Connect的第三個引數設定為定時器的控制裝置,在呼叫中斷狀態清除函式時會用到該引數。

XScuGic_Connect(GicInstancePtr, TimerIntrId,
   (Xil_ExceptionHandler)TimerIntrHandler, (void *)TimerInstancePtr);

5.清除定時器中斷

中斷處理程式中,除了計時和串列埠列印輸出,還要呼叫XScuTimer_ClearInterruptStatus()函式清除中斷狀態。相關程式碼如下:

XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);

該函式本質上也是巨集定義,等效的C語言介面如下:

void XScuTimer_ClearInterruptStatus(XScuTimer *InstancePtr)

傳入的XscuTimer*型別的引數在連線定時器中斷時設定。使用時首先要將其從void*型別轉換為XscuTimer*型別。

相關文章