ZED-Board从入门到精通系列例程——全局定时器

  1. 云栖社区>
  2. 博客>
  3. 正文

ZED-Board从入门到精通系列例程——全局定时器

以亭 2013-08-17 22:43:00 浏览1542
展开阅读全文

本文系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的其他外设操作。

网友评论

登录后评论
0/500
评论
以亭
+ 关注