ZYNQ-定时器中断使用

简介: ZYNQ-定时器中断使用

学习内容


本文首先介绍了ZYNQ的定时器的相关内容,并学习使用ZYNQ芯片中的定时器进行操作测试。

开发环境


vivado 18.3&SDK,PYNQ-Z2开发板。

定时器简介


介绍


ZYNQ有两个Cortex-A9处理器,每个Cortex-A9处理器都有自己的专用32位计时器和32位看门狗计时器。 两个都处理器共享一个全局64位计时器。 这些计时器始终以1/2的CPU频率计时(CPU_3x2x)。

在系统层级,有一个24位看门狗定时器和两个16位三重定时器/计数器。系统看门狗定时器的时钟频率为CPU频率(CPU_1x)的1/4或1/6,时钟也可以由来自MIO引脚或PL的外部信号。两个三重计时器/计数器时钟频率为在CPU频率(CPU_1x)的1/4或1/6,可以用于计算来自MIO引脚或来自PL的脉冲宽度。

系统框图


下图为ZYNQ内部的定时器的系统框图。

image.png

在图中显示,定时器部分包括系统看门狗、CPU内部的看门狗定时器,CPU私有的定时器,全局定时器,三重定时器。其中所有的定时器都可以触发中断控制器进行中断控制的操作,看门狗定时器(无论是系统 的还是CPU内部的)都可以对系统进行一个复位操作。

CPU私有定时器和看门狗定时器


关于CPU私有定时器和看门狗定时器均具有以下功能:

  1. 32位计数器,在达到零时会产生中断
  2. 八位预分频器,可以更好地控制中断周期
  3. 可配置的单次或自动重装模式
  4. 计数器的可配置起始值
  1. 定时器和看门狗复位信号发送到PS复位子系统
  2. 所有私有计时器和看门狗计时器始终以CPU频率(CPU_3x2x)的1/2计时。

下图为CPU私有定时器和看门狗定时器寄存器表:

image.png

全局定时器(GT)


全局定时器是具有自动递增功能的64位递增计数器。 全局计时器在与专用计时器相同的地址空间中映射到内存中。 全局计时器仅在复位时以安全状态访问。 所有Cortex-A9处理器均可访问全局计时器。 每个Cortex-A9处理器都有一个64位比较器,用于在全局计时器达到比较器值时声明一个私有中断。全局定时器始终以CPU频率(CPU_3x2x)的1/2计时。

全局定时器(GT)寄存器功能表如下图:

image.png

系统看门狗定时器(SWDT)


除了两个CPU私有看门狗定时器之外,还有一个系统看门狗定时器(SWDT),可以在系统发生故障时,如PS PLL锁相失败,此时看门狗可以产生一个复位信号让程序重启,从而保证系统正常运行。 与CPU私有看门狗定时器不同的是,SWDT的时钟可以来自于外部设备或者PL端,用于提供一个复位信号输出到PL端口或者外部设备。

特征


SWDT的主要功能如下:

  1. 内部24位计数器。
  2. 可选时钟输入,时钟信号可以来自:1. 内部PS总线时钟(CPU_1x);2. 内部时钟(来自PL);3. 外部时钟(来自MIO)。
  3. 计时超时时,可以进行系统中断(PS)和系统重置(PS,PL,MIO)。
  4. 可编程超时时间:1. 超时范围32,760至68,719,476,736个时钟周期(在100 MHz时可配置范围为330 µs至687.2s)。
  5. 超时时,可编程的输出信号持续时间:系统中断脉冲(4、8、16或32个时钟周期(CPU_1x时钟))。

系统看门狗定时器寄存器功能表如图:

image.png

编程指南


系统看门狗定时器使能顺序如下:

1、使用slcr.WDT_CLK_SEL [SEL]位选择时钟输入源 :确保禁用SWDT(swdt.MODE [WDEN] = 0),并且将时钟输入源设置为选定的正在运行,然后继续此步骤。 更改时钟输入源时,启用SWDT会导致无法预测的行为。 时钟输入源更改为非运行时钟导致APB访问挂起。

2、设置超时时间(计数器控制寄存器) :swdt.CONTROL [CKEY]字段必须为0x248,才能写入此寄存器。

3、启用计数器; 使能输出脉冲 ; 设置输出脉冲长度(零模式寄存器):swdt.MODE [ZKEY]字段必须为0xABC,才能写入此寄存器。 确保IRQLN符合指定的最小值。

4、要使用其他设置运行SWDT,请首先禁用定时器(swdt.MODE [ZKEY]位)。 然后重复上述步骤。

三重计时器(TTC)


TTC包含三个独立的计时器/计数器。 PS端中有两个TTC模块,总共六个计时器/计数器。 可以使用nic301_addr_region_ctrl_registers.security_apb [ttc1_apb]寄存器位将TTC 1控制器配置为安全或非安全模式。 TTC控制器中的三个计时器具有相同的安全状态。

特征


每个三重计时器/计数器都具有以下特征:

  1. 三个独立的16位预分频器和16位向上/向下计数器。
  2. 可选时钟输入,来自:1. 内部PS总线时钟(CPU_1x);2. 内部时钟(来自PL);3. 外部时钟(来自MIO)。
    •每个计数器有一个中断。
    •可以产生溢出中断,定时中断或计数中断,可编程初始值。
    •可以生成通过MIO到PL的波形输出(例如PWM)。

image.png

计数器编程启用顺序


  1. 选择时钟输入源,设置预分频值。(slcr.MIO_MUX_SEL寄存器,TTC时钟控制)在继续执行此操作之前,请确保已禁用TTC(ttc.Counter_Control_x [DIS] = 1)。
  2. 设置间隔值(间隔寄存器)。 此步仅适用于间隔模式。(可选不配置)
  3. 设置匹配值(匹配寄存器)。 如果要启用匹配,可以配置此操作。(可选不配置)
  4. **使能中断(中断使能寄存器)。**如果要启用中断,可以配置此操作。(可选不配置)
  5. 启用/禁用波形输出,启用/禁用匹配,设置计数方向,设置模式,使能计数器(TTC计数器控制寄存器)。 此步骤完成后将启动计数器。

计数器编程停止顺序


  1. 读回计数器控制寄存器的值。
  2. 将DIS位设置为1,同时保留其他位。
  1. 写回计数器控制寄存器。

计数器编程重启顺序


  1. 读回计数器控制寄存器的值。
  2. 将RST位设置为1,同时保留其他位。
  1. 写回计数器控制寄存器。

事件计时器启用序列


  1. 选择外部脉冲源(slcr.MIO_MUX_SEL寄存器)。选定外部待测脉冲。
  2. 设置溢出处理,选择外部脉冲电平,启用事件计时器(事件控制计时器寄存器)。 此步骤开始测量外部所选电平(高或低)的宽度脉冲。
  3. 使能中断(中断使能寄存器)。 如果要启用中断,可以配置此操作。(可选不配置)
  1. 读取测量的宽度(事件寄存器)。

中断清除和应答序列


  1. 读取中断寄存器:读取时清除中断寄存器中的所有位。

系统框图


本次工程系统框图如下图所示。

image.png

调用UART、TIMER、GPIO资源进行开发设计,UART串口引脚直接连接到MIO,GPIO引脚连接到PL端的EMIO引脚,由ARM核进行配置定时器中断操作,实现定时中断进行流水灯操作。

硬件平台搭建


新建工程,创建 block design。添加ZYNQ7 ip,根据本次工程需要对IP进行配置。勾选本次工程使用的资源。

image.png

硬件系统构建完成如下:

image.png

然后我们进行generate output product 然后生成HDL封装。这里用到了UART和GPIO,所以需要进行管脚分配。这里使用的是PYNQZ2开发板,该板卡可直接引用以下约束文件:

#LEDs
set_property -dict { PACKAGE_PIN R14   IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[0] }]; #IO_L6N_T0_VREF_34 Sch=led[0]
set_property -dict { PACKAGE_PIN P14   IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[1] }]; #IO_L6P_T0_34 Sch=led[1]
set_property -dict { PACKAGE_PIN N16   IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[2] }]; #IO_L21N_T3_DQS_AD14N_35 Sch=led[2]
set_property -dict { PACKAGE_PIN M14   IOSTANDARD LVCMOS33 } [get_ports { EMIO_LED_tri_io[3] }]; #IO_L23P_T3_35 Sch=led[3]

完成约束后,进行综合和布局布线,生成bit流,然后点击导出硬件资源(包含bit流文件),接着launch SDK。

SDK软件部分


打开SDK后,新建application project。

在system.mss中可以打开相关参考文档辅助设计。

image.png

可以选择timer中断的例程进行参考设计,导入uart_intr_example例程模板,

image.png

在main.c中输入以下代码:

#include <stdio.h>
#include "xparameters.h"
#include "xscutimer.h"
#include "xscugic.h"
#include "xgpiops.h"
#include "xil_exception.h"
#include "xil_printf.h"
//声明定义
#define GPIO_ID             XPAR_PS7_GPIO_0_DEVICE_ID
#define TIMER_ID            XPAR_PS7_SCUTIMER_0_DEVICE_ID
#define INTR_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID
#define TIMER_IRPT_INTR   XPAR_SCUTIMER_INTR
#define LOAD_VALUE 0X9AF8D9F//0.5s流水 系统时钟650M  定时器时钟325M 周期3ns
#define LED0 54
#define LED1 55
#define LED2 56
#define LED3 57
//声明示例结构体
XGpioPs GpioPs;
XScuTimer Timer;
XScuGic ScuGic;
//函数定义
void Emio_init();
void Timer_init();
void Timer_intr_init(XScuGic *intr, XScuTimer *time);
void Timer_IntrHandler(void *CallBackRef);
int main(){
  //EMIO初始化
  Emio_init();
  //初始化定时器
  Timer_init();
  //初始化中断
  Timer_intr_init(&ScuGic,&Timer);
  //启动定时器
  XScuTimer_Start(&Timer);
  while(1);
  return 0;
}
void Emio_init(){
  XGpioPs_Config *XGpioPs_Cfg;
  XGpioPs_Cfg = XGpioPs_LookupConfig(GPIO_ID);
  XGpioPs_CfgInitialize(&GpioPs,XGpioPs_Cfg,XGpioPs_Cfg->BaseAddr);
  XGpioPs_SetDirectionPin(&GpioPs,LED0,0x01);
  XGpioPs_SetOutputEnablePin(&GpioPs,LED0,0x01);
  XGpioPs_SetDirectionPin(&GpioPs,LED1,0x01);
  XGpioPs_SetOutputEnablePin(&GpioPs,LED1,0x01);
  XGpioPs_SetDirectionPin(&GpioPs,LED2,0x01);
  XGpioPs_SetOutputEnablePin(&GpioPs,LED2,0x01);
  XGpioPs_SetDirectionPin(&GpioPs,LED3,0x01);
  XGpioPs_SetOutputEnablePin(&GpioPs,LED3,0x01);
}
void Timer_init(){
  int Status;
  XScuTimer_Config *ConfigPtr;
  //初始化定时器
  ConfigPtr = XScuTimer_LookupConfig(TIMER_ID);
  XScuTimer_CfgInitialize(&Timer, ConfigPtr,ConfigPtr->BaseAddr);
  //定时器自检测
  Status = XScuTimer_SelfTest(&Timer);
  if (Status != XST_SUCCESS) {
    printf("timer selftest failed!\n");
  }
  printf("timer selftest success!\n");
  //装载初值
  XScuTimer_LoadTimer(&Timer, LOAD_VALUE);
  //使能自动装载模式
  XScuTimer_EnableAutoReload(&Timer);
}
void Timer_intr_init(XScuGic *intr, XScuTimer *time){
  XScuGic_Config *IntcConfig;
  //初始化定时器中断
  IntcConfig = XScuGic_LookupConfig(INTR_DEVICE_ID);
  XScuGic_CfgInitialize(intr,IntcConfig,IntcConfig->CpuBaseAddress);
  //初始化中断异常函数
  Xil_ExceptionInit();
  Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT,
      (Xil_ExceptionHandler)XScuGic_InterruptHandler,
      intr);
  Xil_ExceptionEnable();
  //设置中断服务函数
  XScuGic_Connect(intr, TIMER_IRPT_INTR,
          (Xil_ExceptionHandler)Timer_IntrHandler,
          (void *)time);
  //使能中断控制器
  XScuGic_Enable(intr, TIMER_IRPT_INTR);
  //使能定时器
  XScuTimer_EnableInterrupt(time);
}
//中断处理函数
void Timer_IntrHandler(void *CallBackRef){
  static char led_status=0x01;
  XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
  if(XScuTimer_IsExpired(TimerInstancePtr)){
    led_status = led_status<<1;
    if(led_status == 0X10)
      led_status =0x01;
    XGpioPs_WritePin(&GpioPs,LED0,led_status);
    XGpioPs_WritePin(&GpioPs,LED1,led_status>>1);
    XGpioPs_WritePin(&GpioPs,LED2,led_status>>2);
    XGpioPs_WritePin(&GpioPs,LED3,led_status>>3);
    XScuTimer_ClearInterruptStatus(TimerInstancePtr);
  }
}

部分代码讲解


在主函数中引用了Emio_init();Timer_init();Timer_intr_init(&ScuGic,&Timer);

分别完成初始化GPIO操作,初始化定时器,初始化中断定时器等操作。最后用XScuTimer_Start(&Timer);启动定时器。

对于定时器初始化设置主要要对计数模式进行设置,对初始值进行装载配置

也就是调用下面的函数:

  //装载初值
  XScuTimer_LoadTimer(&Timer, LOAD_VALUE);
  //使能自动装载模式
  XScuTimer_EnableAutoReload(&Timer);

这里LOAD_VALUE表示需要计时或者计数的次数,因为这里的代码实现的是led的0.5s定时中断,系统CPU时钟为650M,所以定时器的时钟频率为325M,也就是周期为3.07ns,计算0.5s下的计数次数得到这里需要装载的初值。(也即为0X9AF8D9F)

Reference


  1. UG585
  2. 正点原子ZYNQ开发视频
目录
相关文章
|
10月前
|
传感器
单片机定时器中断
单片机定时器中断
130 0
STM32:定时器定时中断
STM32:定时器定时中断
210 0
STM32:定时器定时中断
STM32中断与事件的理解
STM32中断与事件的理解
428 1
|
数据采集 开发工具
ZYNQ-实现GPIO的中断控制
ZYNQ-实现GPIO的中断控制
456 0
ZYNQ-实现GPIO的中断控制
stm32之中断系统
概述: 提供中断控制器,用于总体管理异常,称之为“嵌套向量中断控制器:Nested Vectored Interrupt Controller (NVIC)   VIC:中断管理器;   NVIC:内嵌中断管理器,将中断嵌套进入内核;     带来的优势:1、响应速度提高;        ...
718 0
STM32F103C8 TIM定时器中断
STM32F103C8 TIM定时器中断
205 0