您现在的位置是:首页 >技术教程 >ZYNQ:【1】深入理解PS端的TTC定时器(Part1:原理+官方案例讲解)网站首页技术教程

ZYNQ:【1】深入理解PS端的TTC定时器(Part1:原理+官方案例讲解)

Alex-YiWang 2023-05-12 20:15:03
简介基于UG585详细讲解其中TTC的原理和使用案例。

碎碎念:好久不见,甚是想念!本期带来的是有关ZYNQ7020的内容,我们知道ZYNQ作为一款具有硬核的SOC,PS端很强大,可以更加便捷地实现一些算法验证。本文具体讲解一下里面的TTC定时器,之后发布的Part2将基于具体项目出发,实现PS端单核进行六路不等长占空比的PWM输出~

虽然最后对我自己毕业好像没有什么帮助QAQ,但是毕竟花费了一些时间阅读手册等内容,还是打算记录一下供大家参考。

目录

1 TTC原理分析

1.1 主要特点

1.2 结构框图

1.3 功能描述

1.3.1 操作模式

1.3.2 事件定时器/脉宽计数器(Event Timer)操作

1.4 寄存器概述

1.5 编程模型

1.5.1 计数器使能的步骤

1.5.2 计数器停止的步骤

1.5.3 计数器重启的步骤

1.5.4 事件计数器(脉宽计数器)使能的步骤

1.5.5 清除中断和确认的步骤

1.6 计数器时钟输入的选择

2 SDK分析

2.1 工程建立

2.2 案例分析

1.设置中断系统:SetupInterruptSystem()

2.设置Ticker定时器:SetupTicker() 

3.设置PWM定时器:SetupPWM()

4.逐渐修改占空比:WaitForDutyCycleFull()

5.停止计数器:XTtcPs_Stop()


1 TTC原理分析

这一部分我们直接按照UG585的思路,进行分析和介绍,由于原文的内容本身是英文理解起来还是需要一些经验(尽管更推荐去阅读原文部分)。

TTC包含了三个独立的定时器,分别是上图中的Timer/Clock 0、Timer/Clock 1、Timer/Clock 2。从左下角可以看到在PS端包含两个TTC,分别是TTC0和TTC1,因此两个TTC共包含6个独立的定时器。TTC控制器可以通过修改nic301_addr_region_ctrl_registers.security_apb [ttc1_apb]这个寄存器的位,来实现对于安全模式(secure mode)和非安全模式(non-secure mode)的切换。对于这两种模式的内容可以参考下面:

传送门

Secure mode and Non-secure mode:
这两种模式来源于ARM TrustZone技术,ARM在CPU的常规模式之外引入了一种称为“安全模式”的特殊CPU模式,建立了“安全世界”和“正常世界”之间的的概念。默认情况下,安全世界访问正常世界的所有状态,反之则不然。由于ARM基本使用的都是基于存储映射的结构,我的理解是通过这两种模式的分隔,来实现对于重要寄存器的保护。

1.1 主要特点

每个TTC有如下特点:

1.三个独立16位预分频器和16位的向上/向下计数器。(向上:0,1,2,3,4...  向下:9,8,7,6...)

2.可选的时钟源输入(内部PS总线时钟:CPU_1x,内部时钟:PL,外部时钟:MIO)

3.对于TTC内部每一个counter,都各自有一个中断

4.在一定间隔内的溢出中断,或者计数匹配到设定的值时会发出中断

5.产生波形输出,可以通过MIO或者PL(EMIO)

1.2 结构框图

通过上面的结构框图,对于TTC中第一个计数器Timer/Clock 0的时钟输入、波形输出信号的多路控制是通过slcr.MIO_PIN_xx寄存器实现的,默认情况下使用EMIO接口。

1.3 功能描述

每个预分频模块(Pre-scaler)都可以独立设置为使用PS内部总线时钟或者外部时钟(来自MIO或者PL)。对于外部时钟输入,通过使用SLCR寄存器来选择具体的信号输入。预分频模块可以将输入时钟在/2和/65536之间进行分频,当如分频寄存器为0的时候,会对时钟进行二分频,之后输出给后面的计数器。

计时器可以设置为增计数、减计数,并且通过设置间隔寄存器的值,可以控制计数的范围。同时可以比较三个匹配寄存器的值和计数器(一个TTC包含3个计数器Counter和3组匹配寄存器)的值,产生中断信号。

中断模块组合了各种类型的中断:计数器间隔中断(Interval Interrupt)、计数器匹配中断(Match Interrupt)、计数器溢出中断(Overflow Interrupt)、事件计时器溢出。每种类型都可以单独启用。

1.3.1 操作模式

一个TTC中的每个计数器模块,都可以独立编程,并以以下两种模式中的任何一种运行。

间隔模式(Interval mode):

通过修改计数器控制寄存器(Counter Control register)的DEC位,可以控制计数器的计数方向是+1还是-1。通过修改间隔计数器(Interval mode)的值可以控制计数的范围是0到间隔计数器的值。当计数值经过0的时候,会产生一个计数器间隔中断(Interval Interrupt)。当计数器的值等于匹配计数器(Match register)值的时候,会产生一个匹配中断(Match Interrupt)。

溢出模式(Overflow mode):

计数器在0x0000和0xFFFF之间连续的+1或者-1变化,并通过修改计数器控制寄存器的DEC位来控制计数的方向。当计数值经过0的时候,会产生一个溢出中断(Overflow Interrupt)。当计数器的值等于匹配计数器值的时候,会产生一个匹配中断。

1.3.2 事件定时器/脉宽计数器(Event Timer)操作

从名字脉宽计数器,可以推断出其功能是对外部输入信号的脉宽进行测量,原理有一些类似电机差分编码器的M法测速。

事件定时器内部有一个对用户不可见的16位内部计数器(Internal Counter),该计数器被CPU_1x的时钟控制,其满足如下两个条件:

1.当外部脉冲的非计数阶段,被重置为0

2.在外部脉冲的计数阶段,开始增加

修改事件定时器控制内部计数器的行为,主要通过三个位来控制:

1 E_En bit 使能位,等于0时,将内部计数器复位到0,并停止计数

2 E_Lo bit 指定外部脉冲的计数相位

3 E_Ov bit 指定如何处理当内部计数器溢出时,如何处理。

     当为0的时候,溢出导致E_En置为0;

当为1的时候,溢出导致内部计数器继续循环计数;

在另一个寄存器的控制下,可以决定溢出时是否产生中断(而与E_Ov bit本身的值无关)。

当外部计数脉冲的计数相位结束的时候,会使用内部计数器的非零计数值对事件寄存器(Event Register)的值进行更新。因此,这个值展示了外部脉冲的宽度。由于内部计数器被CPU_1x的时钟控制,因此脉冲宽度是由CPU_1x的时钟周期数来衡量的。

当外部计数脉冲的计数相位阶段,如果内部计数器由于溢出被重置为0,那么事件寄存器将不会被更新,并保持上次非溢出计数操作的旧值。

1.4 寄存器概述

功能

名称

概述

时钟控制

时钟控制寄存器

控制预分频器,选择时钟输入,选择边沿

计数器控制寄存器

使能计数器,设置操作模式,设置计数方向,使能匹配,使能波形输出

状态

计数器数值寄存器

返回计数器的当前值

计数器控制

间隔寄存器

设置间隔值

匹配寄存器1

匹配寄存器2

匹配寄存器3

设置匹配值,一共有3组,对应了一个TTC内部的3个Counter(这种说法不准确,其实每个Counter都有自己的一组三个匹配寄存器)

中断

中断寄存器

显示当前中断状态

中断使能寄存器

使能中断

事件

事件控制计时器寄存器

使能事件计时器,停止计时器,设置计数相位

事件寄存器

显示外部脉冲的宽度(即内部计数器的计数值)

1.5 编程模型

1.5.1 计数器使能的步骤

  1. 选择时钟输入源,设置预分频值(slcr.MIO_MUX_SEL registers, TTC Clock Control register),进行这一步前需要保证TTC处于不使能状态(slcr.MIO_MUX_SEL registers, TTC Clock Control register)
  2. 设置间隔值(Interval register),这一步骤是可选的,仅在间隔模式进行
  3. 设置匹配值(Match registers),这一步是可选的,如果匹配是使能状态则需要设置
  4. 使能中断(Interrupt Enable register),这一步是可选的,如果需要中断则需要使能
  5. 设置波形输出的使能状态,设置匹配的使能状态,设置计数的方向,设置模式,使能计数器 (TTC Counter Control register),这一步开启计数器

1.5.2 计数器停止的步骤

  1. 读取当前计数器控制器的值
  2. 设置DIS位为1,保持其他位不变
  3. 将上面修改了DIS位的数值写回计数器控制器

1.5.3 计数器重启的步骤

  1. 读取计数器控制器的值
  2. 设置RST位为1,保持其他位不变
  3. 将上面修改了DIS位的数值写回计数器控制器

1.5.4 事件计数器(脉宽计数器)使能的步骤

  1. 选择外部脉冲源(slcr.MIO_MUX_SEL registers),所选择的外部脉冲的脉宽将会被时钟CPU_1x的周期所衡量
  2. 设置计数溢出时的处理,选择外部脉冲的电平,使能事件计数器(select external pulse level),这一步开始测量选择的外部脉冲的脉宽(高电平或者低电平)
  3. 使能中断(Interrupt Enable register),这一步是可选的,如果需要中断则需要使能
  4. 读取测量到的脉冲宽度(Event register),注意当计数溢出发生的时候,返回来的脉宽计数值是不准确的。具体可以看前文对事件计数器的叙述

1.5.5 清除中断和确认的步骤

  1. 读取中断寄存器,将会自动读取并清除中断寄存器中的所有位

1.6 计数器时钟输入的选择

下面展示了如何设置SoC选择对于TTC0中Counter/timer 0 的时钟源,使用的是一组if else 语句来实现。

if slcr.MIO_PIN_19[6:0] is 1100000, use MIO pin 19

else if slcr.MIO_PIN_31[6:0] is 1100000, use MIO pin 31

else if slcr.MIO_PIN_43[6:0] is 1100000, use MIO pin 43

else use EMIOTTC0CLKI0

TTC0 的 Counter/timer 1只能使用EMIOTTC0CLKI1

TTC0 的 Counter/timer 2只能使用EMIOTTC0CLKI2

下面展示了如何设置SoC选择对于TTC1中Counter/timer 0 的时钟源,使用的是一组if else 语句来实现。

if slcr.MIO_PIN_17[6:0] is 1100000, use MIO pin 17

else if slcr.MIO_PIN_29[6:0] is 1100000, use MIO pin 29

else if slcr.MIO_PIN_41[6:0] is 1100000, use MIO pin 41

else use EMIOTTC1CLKI0

TTC1 的 Counter/timer 1只能使用EMIOTTC1CLKI1

TTC1 的 Counter/timer 2只能使用EMIOTTC1CLKI2

IMPORTANT:当选择MIO引脚或EMIOTTCxCLKIx作为时钟源时,如果时钟停止运行,相应的计数值寄存器将保留旧值,而不管时钟已经停止的事实。在这种情况下必须谨慎。这句话理解为,时钟停止运行这件事可能是很容易被忽略的。

2 SDK分析

2.1 工程建立

在任意一个ZYNQ工程中进行如下配置:

这里对TTC0和TTC1都打上对勾,在Block Design就会多出6个Pin脚。(为下一期的6占空比PWM输出做准备~)

右键每一个Pin脚,设置Make External:

之后在xdc文件中对输出的引脚进行绑定即可。

同时可以在Clock Configuration看到时钟的频率,这里显示TTC1和TTC2的时钟源都是来自CPU_1x的内部时钟,频率是133.333333MHz

至此,Block Design部分就设置完毕了。

首先修改完Block Design之后,需要先点F6,进行Validate Design操作,验证Block Design的正确性。

之后点击Generate Output Products生成输出。

下一步点击左侧的Generate Bitstream输出比特流文件。

之后点击File-Export-Export Hardware,将硬件信息导出。

之后点击File-Lauch SDK,新建一个空的工程。(这一步的流程可以参考正点原子的领航者ZYNQ系列视频的嵌入式开发系列)

打开Vivado工程对应的SDK文件后,我们可以在左侧找到所提供的一些ttc参考文件:

我们只需要关注xttcps.h这个头文件即可,他是PS中TTC模块驱动头文件,给出了比较详细的函数定义

同时也可以找到一些示例文件:

这里主要对第一个案例进行代码的讲解。

2.2 案例分析

这里需要读者自行打开上述的案例文件~由于不需要额外硬件设置,可以直接在SDK中看到上述内容。

这里我们针对这一文件简单介绍一下ttc的设置流程。

这个文件内部给出了利用TTC产生中断的案例,共分成了几个步骤。

1.设置中断系统:SetupInterruptSystem()

Line547+552:初始化中断控制器

Line562:注册中断处理

Line569:使能中断

2.设置Ticker定时器:SetupTicker() 

需要注意的是SetupTicker里面包含了信息的初始化,对单个定时器的设置,中断的设置。相当于将这几部分结合在了一起。

该函数从Line257开始,这里引用了一个数据结构TmrCntrSetup,定义在Line100

typedef struct {
    u32 OutputHz;    /* Output frequency */
    XInterval Interval;    /* Interval value */
    u8 Prescaler;    /* Prescaler value */
    u16 Options;    /* Option settings */
} TmrCntrSetup;

并且Line261调用了Line131定义的数组: 

static TmrCntrSetup SettingsTable[2] = {
    {100, 0, 0, 0},    /* Ticker timer counter initial setup, only output freq */
    {200, 0, 0, 0}, /* PWM timer counter initial setup, only output freq */
}; 

可以看到这个数组分别用来设置Ticker timer和PWM timer的状态,分别包括:输出频率;间隔值;预分频系数;输出选项设置。

对于输出选项的设置,是在Line267通过或操作来实现,具体的可选参数可以看xttcps.h中的定义:

回到xttcps_intr_example中,Line267定义当前模式为间隔模式,同时设置了不输出波形。(如果这里设置输出波形,那么当计数器值等于匹配值的时候,会将输出进行翻转,实现波形输出)。

之后在Line275调用了SetupTimer函数,实现对单个定时器的具体设置。这一函数定义在Line469。注意信息的传递是通过TTC_TICK_DEVICE_ID来实现的。

主要实现的功能就是初始化设备(XTtcPs_LookupConfig、XTtcPs_CfgInitialize),将SetupTicker中的设置传递过来,分别包括(设置选项模式XTtcPs_SetOptions、计算间隔值XTtcPs_CalcIntervalFromFreq、设置间隔值XTtcPs_SetInterval、设置预分频系数XTtcPs_SetPrescaler)

设置完成后,就获得了设置好的设备TtcPsTick。

回到SetupTicker中,Line285实现对中断控制器的设置,将设备以及中断处理函数进行定义。

中断处理函数TickHandler定义在Line592,首先在Line599获取中断的类型,之后在Line600清除中断。在Line602对中断的类型进行判断,这里检测的是XTTCPS_IXR_INTERVAL_MASK,其定义以及其他类型的中断,我们可以在xttcps_hw.h找到,这里面定义了六种中断类型:

当检测到对应类型的中断,我们就利用TickCount对间隔终端出现的次数进行累加处理。

回到SetupTicker中,在Line294和Line300进行了中断的使能操作,之后在Line305设置开启定时器。

3.设置PWM定时器:SetupPWM()

这个相信也是很多人比较关注的部分。

Line328实现一些设置信息,主要是将前面的数组第二个元素存储过来。

Line334设置间隔模式、匹配模式、并使能wave的输出,这里就保证了当计数值与匹配值相同的时候,输出信号就会发生翻转。从而可以利用间隔值设置PWM的周期,利用匹配值设置PWM的占空比。

Line342同样调用SetupTimer来实现对单个定时器的具体设置,具体说明可以看上面的部分。这一函数定义在Line469。注意信息的传递是通过TTC_PWM_DEVICE_ID来实现的。

主要实现的功能就是初始化设备(XTtcPs_LookupConfig、XTtcPs_CfgInitialize),将SetupPWM中的设置传递过来,分别包括(设置选项模式XTtcPs_SetOptions、计算间隔值XTtcPs_CalcIntervalFromFreq、设置间隔值XTtcPs_SetInterval、设置预分频系数XTtcPs_SetPrescaler)

设置完成后,就获得了设置好的设备TtcPsPWM

Line352通过调用XScuGic_Connect实现对中断控制器,设备ID,中断处理函数以及匹配值指针的设置。

中断处理函数PWMHandler定义在Line637,当检测到中断信号是间隔中断(也就是表示输出了一个周期),就会在Line653调用XTtcPs_SetMatchValue进行匹配值的设置,在代码注释中提到,匹配寄存器0是特殊的,如果输出被使能,当匹配值与计数值相等时,会修改输出的极性。

但是这个地方的注释是容易被误解的,TTC定时器的三个匹配寄存器功能是不一致的,并不是都可以用于波形的输出,在ZYNQ官方实例中,register0被指定为Special,也就是当计数器与register0的匹配值相等时,会触发特殊中断事件并翻转输出电平。经试验表明,对于同一个Counter来说,register1和register2并不会影响PWM输出的翻转,但是由于每个TTC中有三个Counter,其实可以设置三个register0的值来实现三个不同占空比PWM的输出。

回到SetupPWM中,在Line361和Line367进行了中断的使能操作,之后在Line372设置开启定时器。

4.逐渐修改占空比:WaitForDutyCycleFull()

这个函数定义在Line397,主要是通过当每一次循环输出一次PWM波之后,TickHandler函数会在Line607将PWM_UpdateFlag更新为TRUE,之后会在WaitForDutyCycleFull中满足if的条件,从而修改全局变量MatchValue的值。通过中断处理函数PWMHandler中的Line643,就实现了MatchValue对MatchReg的赋值,从而在Line653可以修改MatchReg的值,进而就修改了占空比。

整体逻辑只要抓住下面三点:1.间隔模式中断:在PWM周期结束更新flag;2.修改占空比:检测flag修改全局变量,改变占空比;3.PWM输出中断处理函数(也是间隔模式中断)将匹配值设置进去。

5.停止计数器:XTtcPs_Stop()

这里直接在Line236和Line238调用了XTtcPs_Stop实现了计数器的停止。


这就是本期的全部内容啦,如果你喜欢我的文章,不要忘了点赞+收藏+关注,分享给身边的朋友哇~

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。