閱讀595 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

  上一篇:go NHOI 2004 寵物收養所 splay解法
  下一篇:go POJ 2421 最小生成樹