​ZYNQ从放弃到入门(七)-三重定时器计数器 (TTC)

2022-06-06 08:21:27 浏览数 (1)

之前重点介绍了 Zynq All Programmable SoC 处理器系统 (PS) 中可用的私有定时器和看门狗。Zynq SoC 的 PS 还包含两个三重定时器计数器 (TTC),可提供更加灵活的定时资源。您可以将这些 TTC 用作定时器或在 Zynq SoC 的 EMIO 或 MIO 引脚上输出波形。

介绍

下图取自Zynq SoC 技术参考手册 (UG585) — 显示了每个 TTC 的架构以及它们如何相互独立。

参考:

❝UG585

然而,该图没有清楚地表明,每个预分频器都可以由处理器时钟或通过来自 Zynq SoC 的 EMIO 或 MIO 引脚的信号通过可编程逻辑提供时钟。每个 TTC 的时钟源可通过时钟控制寄存器选择。

可以将 TTC 用作功能更强大的计时器或用作在指定计数值处生成不同中断的调度程序。还可以使用 TTC 生成具有设定占空比的波形。这种波形的最基本示例是切换 LED 以显示处理器正在运行并且正在运行应用程序代码。TTC 非常灵活的原因在于它能够生成 PWM(脉冲宽度调制)输出。嵌入式系统将 PWM 输出信号用于包括工业电机控制在内的多种应用。PWM 控制为嵌入式设计人员提供了许多优势,包括抗噪能力。

由于许多工业系统使用 PWM 进行通信(例如在传感器或仪器之间的旧智能电表中),Zynq SoC 的 TTC 还提供接收和计数 PWM 信号的能力(在处理器时钟周期内)。事件定时器可配置为在外部时钟信号为高电平或低电平期间计算处理器时钟周期数。Zynq SoC 的两个 TTC 实例中的每一个都有三个定时器/时钟单元。每个 TTC 都有以下寄存器:

  • 时钟控制(Clock Control):定义 TTC 的时钟源、预分频值和要使用的时钟边沿。
  • 计数器控制(Counter Control):定义生成的波形设置、定时器模式、计数方向、启用匹配值和间隔中断、重置计数器和禁用控件。
  • 计数器值(Counter Value): 包含定时器当前值的只读寄存器。
  • 间隔计数器(Interval Counter ):间隔模式中使用的中间值,作为根据计数方向(向上或向下)计数的值。
  • 匹配计数器(Match Counter)(三个寄存器): 启用匹配寄存器时,当计数器值等于存储在这些寄存器中的值时,会产生单独的中断。
  • 中断寄存器(Interrupt Register ):定义由 TTC 控制的六个中断的状态。允许的中断是 Match 1, Match 2, Match 3, Internal, Overflow, 和 Event.
  • 中断启用(Interrupt Enable): 启用 TTC 中断。
  • 事件控制定时器(Event Control Timer):启用定时器,复位定时器,指定计数的时钟相位,并指定定时器如何处理溢出条件。
  • 事件寄存器(Event Register):包含外部脉冲计数阶段结束时内部计数器的值。用于使用 CPU 时钟作为计数参考来测量外部脉冲宽度。

每个 TTC 有两种基本操作模式:间隔或溢出模式,以及事件定时器。

  • 间隔模式(Interval mode):计数器计数到包含在间隔寄存器中的值,向上或向下计数,并在计数达到零时生成间隔中断(启用时)。
  • 溢出模式(Overflow mode):计数器从 0 递增或递减到满量程。当计数器回绕时,TTC 产生一个溢出中断。

在这两种模式下,当计数器等于匹配寄存器中的值(如果启用)时,将产生匹配中断。

TTC 使用包含在匹配计数 1 寄存器中的计数值在间隔和溢出模式下生成具有所需占空比的波形。当计数器值等于存储在匹配计数器 1 寄存器中的值时,输出的波形将从 1 切换到 0 或从 0 切换到 1,具体取决于计数器控制寄存器中波形极性位的设置。根据选择的定时器模式,波形在产生间隔或溢出中断时再次反转其状态。

事件定时器只能与外部源一起使用,是一种可用于测量事件持续时间或解码 PWM 信号的资源。

接下来我们将使用 TTC 生成输出波形

Vivado设置

为了使用 TTC 生成输出波形,我们需要同时使用 Vivado Design Suite 和 SDK。首先要做的是确保使用我们在 Vivado 中创建的框图启用了 TTC。确保 TTC 框中有复选标记。

图 1:确保启用 TTC

一旦我们确定 TTC 已启用,下一步就是确保选择 TTC 的输出位于 Zynq SoC 可编程逻辑 (PL) 端的扩展 MIO (EMIO) 上,因为两个 Zynq TTC 可用的 MIO 位置已被以太网、USB 和 SD 卡接口使用。要使用 EMIO,我们需要在 Zynq 重新定制 MIO 配置中选择 EMIO 选项:

图 2:为 Watchdog 和 TTC 选择 I/O

当我们在重新定制对话框中时,我们将还可以使用时钟配置页面将 TTC 的时钟设置为内部时钟:

图 3:选择 TTC 驱动时钟

完成这两项任务后,现在可以关闭 Vivado 中的重新自定义窗口。现在,会注意到在 Zynq SoC 的 PS 图标中的 PS 块中出现了许多新端口。

这些端口用于 TTC 时钟输入和 TTC 波形输出。因为我们只对使用此示例中的三个 TTC 波形输出之一感兴趣,所以我们将只使用标记为 TTC0_WAVE0_OUT 的波形输出。下一步是创建一个输出端口并将其连接到 Zynq PS。这很简单,只需在 Block Design 窗口中的 Zynq PS 图标内单击鼠标右键,然后选择 Create Port 选项。

图 4:创建输出端口命名此端口

将其连接到 Zynq 框图上的 TTC_WAVE0_OUT 输出.

我们要使用的端口不在 Zynq SoC 的 PS 端。因此,我们需要创建一个约束文件来定义器件 PL 侧的哪个引脚将用于 TTC 波形输出。

我们通过在 Sources 窗口中选择约束选项来创建一个约束文件,右键单击 constrs_1,然后选择 Edit Constraints Sets:

图 6:创建约束文件

因为我们目前没有约束文件,所以在下一个对话框中我们必须选择 Create New File 而不是 Add Files 选项:

创建一个新的约束文件。我们得到这个打开文件创建对话框。

为约束文件输入所需的文件名,Vivado 将打开一个空白约束文件,准备进行编辑。Vivado 对约束使用 XDC 格式,而不是与 ISE 一起使用的 UCF 格式。如果不确定新语法,可以使用一个有用的语言模板来探索和查找 XDC 文件所需的语法。

SDK设置

在上一节中,我们通过在 Vivado 中定义硬件来实现 Zynq SoC 的 TTC(三重定时器计数器)。在这节中,我们将使用 SDK 来驱动 TTC。显然,我们需要做的第一件事就是将硬件导出到 SDK,以确保软件环境具有最新的定义。

将硬件导出到 SDK 是第一件事。此步骤引入操作系统提供的所需功能和宏。这些可以通过以下方式访问:

代码语言:javascript复制
#include "xttcps.h"

我们还需要包含Xscugic.h和Xil_exception.h以便我们可以使用中断控制器。

与往常一样,下一步是使用 xparamters.h 文件获取 TTC 设备 ID、TTC 中断 ID 和中断控制器设备 ID。

与我们之前使用 Zynq SoC 的私有计时器的示例不同,我们需要声明一个数据结构来包含输出频率、间隔、预分频器和 TTC 选项。

代码语言:javascript复制
typedef struct {
      u32 OutputHz;     /* Output frequency */
      u16 Interval;     /* Interval value */
      u8 Prescaler;     /* Prescaler value */
      u16 Options;      /* Option settings */
} TmrCntrSetup;
 

这种数据结构使得驱动 TTC 变得非常容易。还使用这个结构定义了一个预配置的设置表:

代码语言:javascript复制
static TmrCntrSetup SettingsTable[1] = {
      {10, 0, 0, 0},    /* Ticker timer counter initial setup, only output freq */
};

此设置表最初将输出频率设置为 10Hz,同时将其他一切初始化为零。在初始化和设备配置之后,我们需要定义 TTC 操作的选项模式。在 xttcps.h 中有用地定义了定时器控制寄存器的所有选项。因此,我们可以通过将这些选项组合在一起来配置 TTC。对于这个例子,我将在间隔模式下运行定时器,并禁用外部波形。

代码语言:javascript复制
TimerSetup->Options |= (XTTCPS_OPTION_INTERVAL_MODE |
                                          XTTCPS_OPTION_WAVE_DISABLE);
 
XTtcPs_SetOptions(&Timer, TimerSetup->Options);
 
XTtcPs_CalcIntervalFromFreq(&Timer, TimerSetup->OutputHz,&(TimerSetup->Interval), &(TimerSetup->Prescaler));
 
XTtcPs_SetInterval(&Timer, TimerSetup->Interval);
XTtcPs_SetPrescaler(&Timer, TimerSetup->Prescaler);

使用 TTC 的下一步是将其连接到中断控制器并启用中断。每个 TTC 都有几个中断可供选择。在这种情况下,只启用了间隔中断:

代码语言:javascript复制
XTtcPs_EnableInterrupts(TtcPsInt, XTTCPS_IXR_INTERVAL_MASK);

由 xttcps.h 调用的文件 xttcps_hw.h 包括所有可能的 TTC 中断的定义,允许使用上述函数根据需要启用它们。设置中断后,启动计时器并将ZYNQ 连接到电脑,这样每次中断发生时都可以看到打印出的消息。

在中断服务程序中,只是简单地读回中断状态寄存器以确定发生了哪个中断,然后将其清除。读取 ISR 中的中断状态寄存器很重要,因为可能有几种不同的中断。在更复杂的 TTC 使用中,希望确保根据中断采取正确的操作。

从这个基本示例(参见附加代码)中,可以添加匹配寄存器或更复杂的功能的使用。我们下次再看。

源代码:

❝https://gitee.com/openfpga/zynq-chronicles/blob/master/main_part19.c

使用 TTC 生成输出波形

当我们上节查看 Zynq SoC 的 TTC(三重定时器计数器)时,已将 TTC 中的三个定时器之一配置为以简单间隔模式运行,以所需频率生成中断。然而,我们可以使用 TTC 做更多的事情,所以在这节中,会探索 TTC 更复杂的用途。我们将研究使用匹配寄存器为不同的计数器值发出中断。然后,此讨论使我们能够轻松生成输出波形。启用波形输出后,当匹配值与计数器值匹配时,其输出反转。

第一步是在定时器设置选项中启用匹配模式。我们可以对 xttcps.h 文件中定义的 XTTCPS_OPTION_MATCH_MODE 进行或运算,以启用匹配模式以及禁用的波形和间隔模式。设置匹配模式后,接下来的步骤非常简单,将匹配寄存器配置为我们希望使用以下函数触发中断的值。

代码语言:javascript复制
XTtcPs_SetMatchValue(&Timer, 0, (interval/3));

在上面的示例中,匹配寄存器一的匹配值被定义为在间隔计数器内定义的值的三分之一处触发。我使用函数 XTtcPs_GetInterval() 来访问间隔计数器的值。

下一步是启用匹配中断。在本例中,我们只需要启用匹配中断一,但可以再次使用 xttcps.h 中提供的定义

代码语言:javascript复制
XTtcPs_EnableInterrupts(TtcPsInt, XTTCPS_IXR_MATCH_0_MASK); 

在中断服务程序(ISR)中,我们需要确定中断的原因(因为也可以为间隔中断调用该程序)。通过在中断状态寄存器和中断定义之间执行 AND 操作,可以很容易地确定是什么事件导致了中断。

代码语言:javascript复制
if (0 != (XTTCPS_IXR_MATCH_0_MASK & StatusEvent)) {
printf("match interrupt eventnr");
}

这允许 ISR 根据中断源采取不同的操作。在这种情况下,操作是通过 STDOUT 打印不同的消息。

如果我们决定输出波形,只需在 TTC 选项寄存器中启用波形输出并再次检查 TTC 输出是否正确连接到选定的 Zynq 输出引脚即可。也可以使用 XTTCPS_OPTION_WAVE_POLARITY 选项来选择极性。其他高级 TTC 用途包括创建实时时钟 (RTC),将 TTC 配置为以所需的时间分辨率产生中断,然后在每次中断发生时增加计数。RTC 在嵌入式系统中非常有用,其中一个例子是系统事件的时间戳。

源代码:

❝https://gitee.com/openfpga/zynq-chronicles/blob/master/main_part20.c

0 人点赞