ZED-Board從入門到精通係列例程——全局定時器
本文係ZED-Board從入門到精通(三):從傳統ARM開發到PS開發的轉變之後增加的PS例程。由於原文較長,在原帖後麵添加例程會使閱讀不便,於是單獨開一帖。
實際項目中幾乎離不開時間的測量。定時器是硬件係統運行狀態的忠實記錄者,它不受CPU直接幹預,自己獨立運行,可以完成計時、定時、中斷、實時時鍾等功能。
ARM Cortex-A9內部有一個64bit全局定時器,特性包括:
64bit,增計數;
內存映射至私有內存空間;
隻有複位後,在安全模式下才能訪問;
可被所有Cortex-A9核訪問,每個核有私有比較器;
時鍾源為PERIPHCLK;
定時器的精度是由其時鍾源決定的,而時鍾源來自ARM係統時鍾。我們先來看一下硬件係統時鍾分配情況,
係統PS_CLK為板上的晶振輸入,頻率為33.3333MHz
PS-CLK進入芯片後,又做如下分配(摘自Zynq-7000-TRM):
可見經過了3個PLL,最終生成的係統時鍾有cpu_6x4x,cpu_3x2x,cpu_2x,cpu_1x。具體的係統時鍾頻率值我們可以查看XPS中的時鍾選項,這裏不再詳述,隻要知道全局定時器的輸入時鍾為cpu_3x2x,它的頻率為CPU時鍾的一半(333.333MHz),定時精度為3ns,又由於其具有64bit範圍,最大定時值可達3e34s。
操作定時器需要訪問其對應寄存器,我們看一下TRM中的描述:
這裏隻給出了基地址,具體寄存器的分布需要查看ARM文檔cortex_a9_mpcore_r4p1_trm:
其中前兩個為定時器的計數值存放寄存器,兩個32bit湊成一個64bit實現連續增計數。
第三個寄存器為控製寄存器,位定義如下:
我們需要關注的是最低位(b0),即定時器使能位,該位為0時,定時器停止,這時可以讀寫計數值;而該位為1時,定時器運行,不能寫入計數值(隻能讀出)。
其它的寄存器我們暫時不用,不加解釋。需要的話可以自己翻一翻手冊。
相比基於操作係統的軟件計時器,我們采用硬件計時器具有非常高的精度,可以精確到ns級別!對於非常窄的脈衝,我們照樣可以通過計時器完成其脈寬測量。程序中有時需要精確延時(例如紅外通信,DS18b20單總線讀寫),我們先寫一個精確延時1s的函數,然後把它用在我們第一個流水燈實驗中。本節例程仍基於第一個例程進行,硬件部分不需要改動,隻需要改軟件,打開helloworld.c,將內容改為:
/* * Copyright (c) 2009 Xilinx, Inc. All rights reserved. * * Xilinx, Inc. * XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" AS A * COURTESY TO YOU. BY PROVIDING THIS DESIGN, CODE, OR INFORMATION AS * ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, APPLICATION OR * STANDARD, XILINX IS MAKING NO REPRESENTATION THAT THIS IMPLEMENTATION * IS FREE FROM ANY CLAIMS OF INFRINGEMENT, AND YOU ARE RESPONSIBLE * FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE FOR YOUR IMPLEMENTATION. * XILINX EXPRESSLY DISCLAIMS ANY WARRANTY WHATSOEVER WITH RESPECT TO * THE ADEQUACY OF THE IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO * ANY WARRANTIES OR REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE * FROM CLAIMS OF INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. * */ /* * helloworld.c: simple test application */ #include <stdio.h> #include "platform.h" #define MIO_BASE 0xE000A000 //MIO基地址 #define DATA1_RO 0x64 #define DATA2 0x48 #define DATA2_RO 0x68 #define DIRM_2 0x284 #define OEN_2 0x288 #define GTC_BASE 0xF8F00200 //Global Timer基地址 #define GTC_CTRL 0x08 //控製寄存器偏移量 #define GTC_DATL 0x00 //數據寄存器(低32bit) #define GTC_DATH 0x04 //數據寄存器(高32bit) #define CLK_3x2x 333333333 //定時器輸入時鍾頻率 void print(char *str); void delay_1s(int t) //t無實際意義 { int i = CLK_3x2x,j; *((volatile int*)(GTC_BASE+GTC_CTRL)) = 0x00; //清零定時器使能位,定時器停止 *((volatile int*)(GTC_BASE+GTC_DATL)) = 0x00000000; //寫入計數值(低32bit) *((volatile int*)(GTC_BASE+GTC_DATH)) = 0x00000000; //寫入計數值(高32bit) *((volatile int*)(GTC_BASE+GTC_CTRL)) = 0x01; //開啟定時器 do { j=*((volatile int*)(GTC_BASE+GTC_DATL)); } while(j<i); //判斷是否計時夠1s? } void print(char *str); int main() { int i; init_platform(); *((volatile int*)(MIO_BASE+OEN_2)) = 0xff; *((volatile int*)(MIO_BASE+DIRM_2)) = 0xff; print("Hello world!\r\nThe Leds are flowing...\r\n"); while(1) { for(i = 0;i < 8; i++) { *((volatile int*)(MIO_BASE+DATA2)) = 0x01<<i; delay_1s(1000); } } cleanup_platform(); return 0; }
上麵例子中,將原來的delay_1s改成了利用64bit全局定時器實現的精確定時(雖然這樣做有點浪費,嗬嗬)。
運行結果仍為流水燈,燈移一位的時間應該是標準的1s。
我們可以通過簡單的編程,實現對程序性能的監測,例如在運行算法程序之前,先開啟計時器,等算法程序結束,再停止計時,讀取計時器的計數值從而計算算法運行時間,這樣可以評估算法性能。這個功能有點像Matlab裏麵的tic,toc,為了方便程序編寫,我們也如此定義函數:
#define GTC_BASE 0xF8F00200 #define GTC_CTRL 0x08 #define GTC_DATL 0x00 #define GTC_DATH 0x04 #define CLK_3x2x 333333333 void tic(void) { *((volatile int*)(GTC_BASE+GTC_CTRL)) = 0x00; *((volatile int*)(GTC_BASE+GTC_DATL)) = 0x00000000; *((volatile int*)(GTC_BASE+GTC_DATH)) = 0x00000000; //清零定時器的計數值 *((volatile int*)(GTC_BASE+GTC_CTRL)) = 0x01; } double toc(void) { *((volatile int*)(GTC_BASE+GTC_CTRL)) = 0x00; long long j=*((volatile int*)(GTC_BASE+GTC_DATH)); double elapsed_time = j<<32; j=*((volatile int*)(GTC_BASE+GTC_DATL)); //讀取64bit定時器值,轉換為double elapsed_time+=j; elapsed_time/=CLK_3x2x; elapsed_time*=1000; printf("Elapsed time is %f ms.\r\n",elapsed_time); return elapsed_time; }
調用時非常簡單:
tic(); my_algorithm(); toc();
運行時,程序輸出和matlab完全一致。這裏使用硬件計時,精度可以達到ns級別,具有普通軟件計時無法比擬的特性,對於非常窄的脈衝,我們照樣可以用上麵的方法測量其脈寬。
通過本節定時器的例子,相信童鞋們對PS開發有種駕輕就熟的感覺。沒錯,真正基於Zynq的PS開發流程就是如此,首先查閱文檔,知道硬件寄存器定義,然後按照說明進行底層軟件編寫,並為上層程序提供較為簡潔和直觀的接口。掌握了這個技巧,後麵進行PS與PL協同開發時,隻要根據PL相應內存映射地址和寄存器定義,就可以完成PS端控製軟件的設計,從而為後麵進一步編寫基於操作係統的驅動程序打下堅實的基礎。
大家可以讀完本文後,進一步利用官方文檔,熟悉一下PS的其他外設操作。
最後更新:2017-04-03 16:48:57