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

    xiaoxiao2021-03-25  128

    http://blog.csdn.net/w371500241/article/details/53689507 

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

    标签: zynq   99人阅读  评论(0)  收藏  举报   分类: zynq(3) 

    URL: http://blog.csdn.NET/kkk584520/article/details/10034679

    本文系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,将内容改为:

     

    [cpp]  view plain copy /*   * 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,为了方便程序编写,我们也如此定义函数:

    [cpp]  view plain copy #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;   }  

    调用时非常简单:

    [cpp]  view plain copy tic();   my_algorithm();   toc();  

    运行时,程序输出和matlab完全一致。这里使用硬件计时,精度可以达到ns级别,具有普通软件计时无法比拟的特性,对于非常窄的脉冲,我们照样可以用上面的方法测量其脉宽。

     

    通过本节定时器的例子,相信童鞋们对PS开发有种驾轻就熟的感觉。没错,真正基于Zynq的PS开发流程就是如此,首先查阅文档,知道硬件寄存器定义,然后按照说明进行底层软件编写,并为上层程序提供较为简洁和直观的接口。掌握了这个技巧,后面进行PS与PL协同开发时,只要根据PL相应内存映射地址和寄存器定义,就可以完成PS端控制软件的设计,从而为后面进一步编写基于操作系统的驱动程序打下坚实的基础。

     

    大家可以读完本文后,进一步利用官方文档,熟悉一下PS的其他外设操作。

    转载请注明原文地址: https://ju.6miu.com/read-14528.html

    最新回复(0)