學習內容
本文首先介紹了ZYNQ的定時器的相關內容,并學習使用ZYNQ芯片中的定時器進行操作測試。
開發環境
vivado 18.3&SDK,PYNQ-Z2開發板。
定時器簡介
介紹
ZYNQ有兩個Cortex-A9處理器,每個Cortex-A9處理器都有自己的專用32位計時器和32位看門狗計時器。兩個都處理器共享一個全局64位計時器。這些計時器始終以1/2的CPU頻率計時(CPU_3x2x)。在系統層級,有一個24位看門狗定時器和兩個16位三重定時器/計數器。系統看門狗定時器的時鐘頻率為CPU頻率(CPU_1x)的1/4或1/6,時鐘也可以由來自MIO引腳或PL的外部信號。兩個三重計時器/計數器時鐘頻率為在CPU頻率(CPU_1x)的1/4或1/6,可以用于計算來自MIO引腳或來自PL的脈沖寬度。
系統框圖
下圖為ZYNQ內部的定時器的系統框圖。
在圖中顯示,定時器部分包括系統看門狗、CPU內部的看門狗定時器,CPU私有的定時器,全局定時器,三重定時器。其中所有的定時器都可以觸發中斷控制器進行中斷控制的操作,看門狗定時器(無論是系統 的還是CPU內部的)都可以對系統進行一個復位操作。
CPU私有定時器和看門狗定時器
關于CPU私有定時器和看門狗定時器均具有以下功能:
- 32位計數器,在達到零時會產生中斷
- 八位預分頻器,可以更好地控制中斷周期
- 可配置的單次或自動重裝模式
- 計數器的可配置起始值
- 定時器和看門狗復位信號發送到PS復位子系統
- 所有私有計時器和看門狗計時器始終以CPU頻率(CPU_3x2x)的1/2計時。
下圖為CPU私有定時器和看門狗定時器寄存器表:
全局定時器(GT)
全局定時器是具有自動遞增功能的64位遞增計數器。全局計時器在與專用計時器相同的地址空間中映射到內存中。全局計時器僅在復位時以安全狀態訪問。 所有Cortex-A9處理器均可訪問全局計時器。每個Cortex-A9處理器都有一個64位比較器,用于在全局計時器達到比較器值時聲明一個私有中斷。全局定時器始終以CPU頻率(CPU_3x2x)的1/2計時。全局定時器(GT)寄存器功能表如下圖:
系統看門狗定時器(SWDT)
除了兩個CPU私有看門狗定時器之外,還有一個系統看門狗定時器(SWDT),可以在系統發生故障時,如PS PLL鎖相失敗,此時看門狗可以產生一個復位信號讓程序重啟,從而保證系統正常運行。與CPU私有看門狗定時器不同的是,SWDT的時鐘可以來自于外部設備或者PL端,用于提供一個復位信號輸出到PL端口或者外部設備。
特征
SWDT的主要功能如下:
- 內部24位計數器。
- 可選時鐘輸入,時鐘信號可以來自:1. 內部PS總線時鐘(CPU_1x);2. 內部時鐘(來自PL);3. 外部時鐘(來自MIO)。
- 計時超時時,可以進行系統中斷(PS)和系統重置(PS,PL,MIO)。
- 可編程超時時間:1. 超時范圍32,760至68,719,476,736個時鐘周期(在100 MHz時可配置范圍為330 µs至687.2s)。
- 超時時,可編程的輸出信號持續時間:系統中斷脈沖(4、8、16或32個時鐘周期(CPU_1x時鐘))。
系統看門狗定時器寄存器功能表如圖:
編程指南
系統看門狗定時器使能順序如下:
- 使用slcr.WDT_CLK_SEL [SEL]位選擇時鐘輸入源 :確保禁用SWDT(swdt.MODE [WDEN] = 0),并且將時鐘輸入源設置為選定的正在運行,然后繼續此步驟。更改時鐘輸入源時,啟用SWDT會導致無法預測的行為。時鐘輸入源更改為非運行時鐘導致APB訪問掛起。
- 設置超時時間(計數器控制寄存器) :swdt.CONTROL [CKEY]字段必須為0x248,才能寫入此寄存器。
- 啟用計數器;使能輸出脈沖 ;設置輸出脈沖長度(零模式寄存器):swdt.MODE [ZKEY]字段必須為0xABC,才能寫入此寄存器。確保IRQLN符合指定的最小值。
- 要使用其他設置運行SWDT,請首先禁用定時器(swdt.MODE [ZKEY]位)。然后重復上述步驟。
三重計時器(TTC)
TTC包含三個獨立的計時器/計數器。PS端中有兩個TTC模塊,總共六個計時器/計數器。可以使用 nic301_addr_region_ctrl_registers.security_apb [ttc1_apb]寄存器位將TTC 1控制器配置為安全或非安全模式。TTC控制器中的三個計時器具有相同的安全狀態。
特征
每個三重計時器/計數器都具有以下特征:
- 三個獨立的16位預分頻器和16位向上/向下計數器。
- 可選時鐘輸入,來自:1. 內部PS總線時鐘(CPU_1x);2. 內部時鐘(來自PL);3. 外部時鐘(來自MIO)。•每個計數器有一個中斷。•可以產生溢出中斷,定時中斷或計數中斷,可編程初始值。•可以生成通過MIO到PL的波形輸出(例如PWM)。
三重計時器/計數器寄存器功能表如圖:
計數器編程啟用順序
- 選擇時鐘輸入源,設置預分頻值。(slcr.MIO_MUX_SEL寄存器,TTC時鐘控制)在繼續執行此操作之前,請確保已禁用TTC(ttc.Counter_Control_x [DIS] = 1)。
- 設置間隔值(間隔寄存器)。 此步僅適用于間隔模式。(可選不配置)
- 設置匹配值(匹配寄存器)。 如果要啟用匹配,可以配置此操作。(可選不配置)
- **使能中斷(中斷使能寄存器)。**如果要啟用中斷,可以配置此操作。(可選不配置)
- 啟用/禁用波形輸出,啟用/禁用匹配,設置計數方向,設置模式,使能計數器(TTC計數器控制寄存器)。 此步驟完成后將啟動計數器。
計數器編程停止順序
- 讀回計數器控制寄存器的值。
- 將DIS位設置為1,同時保留其他位。
- 寫回計數器控制寄存器。
計數器編程重啟順序
- 讀回計數器控制寄存器的值。
- 將RST位設置為1,同時保留其他位。
- 寫回計數器控制寄存器。
事件計時器啟用序列
- 選擇外部脈沖源(slcr.MIO_MUX_SEL寄存器)。選定外部待測脈沖。
- 設置溢出處理,選擇外部脈沖電平,啟用事件計時器(事件控制計時器寄存器)。此步驟開始測量外部所選電平(高或低)的寬度脈沖。
- 使能中斷(中斷使能寄存器)。如果要啟用中斷,可以配置此操作。(可選不配置)
- 讀取測量的寬度(事件寄存器)。
中斷清除和應答序列
- 讀取中斷寄存器:讀取時清除中斷寄存器中的所有位。
系統框圖
本次工程系統框圖如下圖所示。
調用UART、TIMER、GPIO資源進行開發設計,UART串口引腳直接連接到MIO,GPIO引腳連接到PL端的EMIO引腳,由ARM核進行配置定時器中斷操作,實現定時中斷進行流水燈操作。
硬件平臺搭建
新建工程,創建 block design。添加ZYNQ7 ip,根據本次工程需要對IP進行配置。勾選本次工程使用的資源。
硬件系統構建完成如下:
然后我們進行generate output product 然后生成HDL封裝。這里用到了UART和GPIO,所以需要進行管腳分配。這里使用的是PYNQZ2開發板,該板卡可直接引用以下約束文件:
#LEDs
set_property -dict { PACKAGE_PIN R14 IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[0] }]; #IO_L6N_T0_VREF_34 Sch=led[0]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[1] }]; #IO_L6P_T0_34 Sch=led[1]
set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[2] }]; #IO_L21N_T3_DQS_AD14N_35 Sch=led[2]
set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[3] }]; #IO_L23P_T3_35 Sch=led[3]
完成約束后,進行綜合和布局布線,生成bit流,然后點擊導出硬件資源(包含bit流文件),接著launch SDK。
SDK軟件部分
打開SDK后,新建application project。在system.mss中可以打開相關參考文檔輔助設計。
可以選擇timer中斷的例程進行參考設計,導入uart_intr_example例程模板,
在main.c中輸入以下代碼:
#include
#include "xparameters.h"
#include "xscutimer.h"
#include "xscugic.h"
#include "xgpiops.h"
#include "xil_exception.h"
#include "xil_printf.h"
//聲明定義
#define GPIO_ID XPAR_PS7_GPIO_0_DEVICE_ID
#define TIMER_ID XPAR_PS7_SCUTIMER_0_DEVICE_ID
#define INTR_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR
#define LOAD_VALUE 0X9AF8D9F//0.5s流水 系統時鐘650M 定時器時鐘325M 周期3ns
#define LED0 54
#define LED1 55
#define LED2 56
#define LED3 57
//聲明示例結構體
XGpioPs GpioPs;
XScuTimer Timer;
XScuGic ScuGic;
//函數定義
void Emio_init();
void Timer_init();
void Timer_intr_init(XScuGic *intr, XScuTimer *time);
void Timer_IntrHandler(void *CallBackRef);
int main(){
//EMIO初始化
Emio_init();
//初始化定時器
Timer_init();
//初始化中斷
Timer_intr_init(&ScuGic,&Timer);
//啟動定時器
XScuTimer_Start(&Timer);
while(1);
return0;
}
void Emio_init(){
XGpioPs_Config *XGpioPs_Cfg;
XGpioPs_Cfg = XGpioPs_LookupConfig(GPIO_ID);
XGpioPs_CfgInitialize(&GpioPs,XGpioPs_Cfg,XGpioPs_Cfg->BaseAddr);
XGpioPs_SetDirectionPin(&GpioPs,LED0,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,LED0,0x01);
XGpioPs_SetDirectionPin(&GpioPs,LED1,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,LED1,0x01);
XGpioPs_SetDirectionPin(&GpioPs,LED2,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,LED2,0x01);
XGpioPs_SetDirectionPin(&GpioPs,LED3,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,LED3,0x01);
}
void Timer_init(){
int Status;
XScuTimer_Config *ConfigPtr;
//初始化定時器
ConfigPtr = XScuTimer_LookupConfig(TIMER_ID);
XScuTimer_CfgInitialize(&Timer, ConfigPtr,ConfigPtr->BaseAddr);
//定時器自檢測
Status = XScuTimer_SelfTest(&Timer);
if (Status != XST_SUCCESS) {
printf("timer selftest failed!\n");
}
printf("timer selftest success!\n");
//裝載初值
XScuTimer_LoadTimer(&Timer, LOAD_VALUE);
//使能自動裝載模式
XScuTimer_EnableAutoReload(&Timer);
}
void Timer_intr_init(XScuGic *intr, XScuTimer *time){
XScuGic_Config *IntcConfig;
//初始化定時器中斷
IntcConfig = XScuGic_LookupConfig(INTR_DEVICE_ID);
XScuGic_CfgInitialize(intr,IntcConfig,IntcConfig->CpuBaseAddress);
//初始化中斷異常函數
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
intr);
Xil_ExceptionEnable();
//設置中斷服務函數
XScuGic_Connect(intr, TIMER_IRPT_INTR,
(Xil_ExceptionHandler)Timer_IntrHandler,
(void *)time);
//使能中斷控制器
XScuGic_Enable(intr, TIMER_IRPT_INTR);
//使能定時器
XScuTimer_EnableInterrupt(time);
}
//中斷處理函數
void Timer_IntrHandler(void *CallBackRef){
staticchar led_status=0x01;
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
if(XScuTimer_IsExpired(TimerInstancePtr)){
led_status = led_status<<1;
if(led_status == 0X10)
led_status =0x01;
XGpioPs_WritePin(&GpioPs,LED0,led_status);
XGpioPs_WritePin(&GpioPs,LED1,led_status>>1);
XGpioPs_WritePin(&GpioPs,LED2,led_status>>2);
XGpioPs_WritePin(&GpioPs,LED3,led_status>>3);
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
}
}
部分代碼講解
在主函數中引用了Emio_init();
、Timer_init();
、Timer_intr_init(&ScuGic,&Timer);
分別完成初始化GPIO操作,初始化定時器,初始化中斷定時器等操作。最后用XScuTimer_Start(&Timer);
啟動定時器。
對于定時器初始化設置主要要對計數模式進行設置,對初始值進行裝載配置 也就是調用下面的函數:
//裝載初值
XScuTimer_LoadTimer(&Timer, LOAD_VALUE);
//使能自動裝載模式
XScuTimer_EnableAutoReload(&Timer);
這里LOAD_VALUE表示需要計時或者計數的次數,因為這里的代碼實現的是led的0.5s定時中斷,系統CPU時鐘為650M,所以定時器的時鐘頻率為325M,也就是周期為3.07ns,計算0.5s下的計數次數得到這里需要裝載的初值。(也即為0X9AF8D9F)
Reference
- UG585
- 正點原子ZYNQ開發視頻