【ZYNQ】PS端CPU私有定时器产生定时中断

本篇文章包含的内容

  • [一、ZYNQ 7000 系列的 PS 私有定时器](#一、ZYNQ 7000 系列的 PS 私有定时器)
  • [二、ZYNQ PS 定时器的使用](#二、ZYNQ PS 定时器的使用)

  • Vivado:2024.2
  • Vitis:2024.2
  • ZYNQ芯片:xc7z020clg400-2
  • 开发板:Microphase Z7-Lite 7020版

一、ZYNQ 7000 系列的 PS 私有定时器

Zynq 7000系列PS端的定时器的结构框图如下图所示。 从图中可以看到系统所包含的定时器资源有:系统看门狗定时器(Syetem Watchdog Timer)、每个CPU处理器包含一个32位看门狗和32位的私有定时器、 两个Triple Timer Counter 定时器、一个64位共享定时器。所有定时器都与中断 控制器相连,可以产生中断。

ZYNQ 7000系列的私有定时器内部结构和STM32比较类似,甚至比STM32中的定时器还要简单。私有定时器的时钟频率为CPU时钟频率的一半,这个"一半"的比例是无法修改的,如 ARM 的工作时钟频率为 666.666Mhz(默认), 则私有定时器的时钟频率为 333.333Mhz。私有定时器有一个向下计数的 32 位计数器,计数到0时产生中断信号;8位预分频器(Prescaler),可控制定时器频率;可配置单次计时或自动重载(AutoReload)模式

二、ZYNQ PS 定时器的使用

PL端配置比较简单,直接在BlockDesign中定义一个PS的IP,仅保留DDR3和UART0功能即可。之后生成BD底层,生成HDL顶层,导出.xsa文件(没有PL逻辑不需要生成Bitstream),在Vitis中使用该.xsa文件创建平台部件和一个空的应用部件即可。具体的步骤可以参考我的文章:【Vivado/Vitis 2024.2】工程的组织和创建丨使用PS MIO读写GPIO

PS端私有定时器的相关库函数在xscutimer.h文件中,文件对库函数的相关使用方法都有较为清晰明了的说明。常用的库函数如下所示:

c 复制代码
// 初始化定时器
XScuTimer_Config *XScuTimer_LookupConfig(UINTPTR BaseAddr);
s32 XScuTimer_CfgInitialize(XScuTimer *InstancePtr,
			    XScuTimer_Config *ConfigPtr, u32 EffectiveAddress);

// 开启和关闭定时器
void XScuTimer_Start(XScuTimer *InstancePtr);
void XScuTimer_Stop(XScuTimer *InstancePtr);

// 获取当前计数值
XScuTimer_GetCounterValue(InstancePtr);

// 自动重装载
XScuTimer_LoadTimer(InstancePtr, Value);
XScuTimer_EnableAutoReload(InstancePtr);
XScuTimer_DisableAutoReload(InstancePtr);

// 预分频器操作
void XScuTimer_SetPrescaler(XScuTimer *InstancePtr, u8 PrescalerValue);
u8 XScuTimer_GetPrescaler(XScuTimer *InstancePtr);

// 使能/失能中断
XScuTimer_EnableInterrupt(XScuTimer *InstancePtr);
XScuTimer_DisableInterrupt(XScuTimer *InstancePtr);

// 清除中断标志位
XScuTimer_ClearInterruptStatus(XScuTimer *InstancePtr);

本次实验主要实现的实验现象是:每秒产生一次中断,并且在串口输出信息。虽然在中断服务函数中直接进行串口输出有可能会造成程序堵塞,但是在学习阶段,还是可以使用这种方法方便地观察到实验现象。

可以将Timer的功能抽象出来,单独写一个.c.h文件:

  • timer.h
c 复制代码
#ifndef __TIMER_H__
#define __TIMER_H__

#include "xparameters.h"
#include "xparameters_ps.h"

#include "xil_exception.h"
#include "xscutimer.h"
#include "xscugic.h"
#include "xstatus.h"

#define TIMER_BASE_ADDRESS  XPAR_SCUTIMER_BASEADDR
#define TIMER_LOAD_VALUE    XPAR_CPU_CORE_CLOCK_FREQ_HZ / 2     // 私有定时器的计数频率默认是CPU基频的一半

// #define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID     // 添加即报错,2024.2均采用BaseAddr进行初始化
#define INTC_BASE_ADDRESS   XPAR_INTC_BASEADDR

#define TIMER_INTR_ID       XPAR_SCUTIMER_INTR      // 私有定时器中断,定义来自xparameters_ps.h,类型号为29U

int Timer_Init(XScuTimer * timer_inst_ptr);
int Timer_Intr_Init(XScuGic * intc_inst_ptr, XScuTimer * timer_inst_ptr);
void TimerInitHandler(void * CallBackRef);

extern int second;

#endif

关于不同版本Vivado/Vitis程序的宏定义区别

应该引入xparameters.h还是xparameters_ps.h,这是一个问题。如果你和我一样,尝试在Zynq系列芯片的PS上直接运行老版本的例程,可能很多程序现象都无法准确复现。AMD官方似乎对Zynq使用的标准库进行了更新,特别是修改了初始化、中断ID的定义逻辑。如果你使用Vivado 2020甚至更早的版本进行开发,可能会发现本例程的初始化方式(应该使用DEVICE_ID还是BASEADDRESS)、中断ID宏定义方面会有所不同,这就是在使用早先版本的例程在最新版本的环境中可能无法复现实验现象的原因。Zynq库中,中断ID是进入中断程序的入口。笔者在调试过程中,遇到的最难以察觉的问题就是中断ID的定义错误,这时就要翻看手册,或者详细研究之前的代码,因为中断ID是由Zynq芯片中的电路结构决定的。宏定义的名称可能有所变化,但是宏定义的值一定不会改变(总不可能开发环境更新后无法实现功能)。

  • timer.c
c 复制代码
#include "timer.h"

int second = 10;

int Timer_Init(XScuTimer * timer_inst_ptr) {

    int Status;
    XScuTimer_Config * timer_cfg_ptr;
    timer_cfg_ptr = XScuTimer_LookupConfig(TIMER_BASE_ADDRESS);
    if (timer_cfg_ptr == NULL) {
        return XST_FAILURE;
    }
    Status = XScuTimer_CfgInitialize(timer_inst_ptr, timer_cfg_ptr, timer_cfg_ptr->BaseAddr);
    if (Status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    XScuTimer_LoadTimer(timer_inst_ptr, TIMER_LOAD_VALUE);      // 设置自动重装载值
    XScuTimer_EnableAutoReload(timer_inst_ptr);     // 开启自动重装载
    XScuTimer_EnableInterrupt(timer_inst_ptr);      // 使能定时器中断
    
    XScuTimer_Start(timer_inst_ptr);

    return XST_SUCCESS;

}

int Timer_Intr_Init(XScuGic * intc_inst_ptr, XScuTimer * timer_inst_ptr) {

    // 初始化中断控制器
    XScuGic_Config * intc_cfg_ptr;
    intc_cfg_ptr = XScuGic_LookupConfig(INTC_BASE_ADDRESS);
    if (intc_cfg_ptr == NULL) {
        return XST_FAILURE;
    }
    XScuGic_CfgInitialize(intc_inst_ptr, intc_cfg_ptr, intc_cfg_ptr->CpuBaseAddress);
    
    // 设置并打开中断异常处理功能
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, 
        (Xil_ExceptionHandler) XScuGic_InterruptHandler, 
        intc_inst_ptr);
    Xil_ExceptionEnable();

    // 关联中断源和中断服务函数
    XScuGic_Connect(intc_inst_ptr, 
        TIMER_INTR_ID, 
        (Xil_InterruptHandler) TimerInitHandler, 
        (void *)timer_inst_ptr);
    XScuGic_Enable(intc_inst_ptr, TIMER_INTR_ID);
   
    return XST_SUCCESS;

}

// 中断服务函数
void TimerInitHandler(void * CallBackRef) {

    XScuTimer * timer_ptr = (XScuTimer * ) CallBackRef;

    second --;
    xil_printf("second count:%d\r\n", second);
    XScuTimer_ClearInterruptStatus(timer_ptr);

}

添加文件后需要手动在cmake中设置你要编译的文件名称,否则无法编译。只设置timer.c即可。timer.c两侧的引号可加可不加。

  • main.c
c 复制代码
#include "timer.h"
#include "xil_printf.h"

XScuTimer timer_inst;
XScuGic Intc_inst;

int main() {

    int Status;
    xil_printf("SCU Timer Interrupt Example Test. \r\n");
    Status = Timer_Init(&timer_inst);
    if (Status != XST_SUCCESS) {
        xil_printf("[Err] Timer init failed! \r\n");
        return XST_FAILURE;
    }

    Status = Timer_Intr_Init(&Intc_inst, &timer_inst);
    if (Status != XST_SUCCESS) {
        xil_printf("[Err] Timer Interrupt init failed! \r\n");
        return XST_FAILURE;
    }

    while (1) {
        // xil_printf("Running... \r\n");
        if (second == 0) {
            break;
        }
    }
    XScuTimer_DisableInterrupt(&timer_inst);        // 失能定时器中断
    xil_printf("SCU Timer Interrupt Test is Finished! \r\n");

    return XST_SUCCESS;
}

串口输出信息:


*  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。*


相关推荐
lantiandianzi40 分钟前
基于单片机的多功能儿童书桌设计
单片机·嵌入式硬件
sayang_shao43 分钟前
STM32 使用ARM Compiler V6 编译裸机 LWIP协议栈报错的解决方法
arm开发·stm32·嵌入式硬件
LinuxST1 小时前
30、Firefly-rk3399定时器
linux·windows·stm32·嵌入式硬件·ubuntu
不能只会打代码2 小时前
51单片机从入门到精通:理论与实践指南入门篇(三)
单片机·嵌入式硬件·51单片机
DevinLGT2 小时前
石英晶体基本特性和等效电路:【图文讲解】
人工智能·单片机·嵌入式硬件
QQ_7781329742 小时前
ZYNQ详解
fpga开发
黑色叉腰丶大魔王4 小时前
《嵌入式硬件设计》
嵌入式硬件
我想发发发5 小时前
Ardusub源码剖析(1)——AP_Arming_Sub
c++·嵌入式硬件·无人机
lantiandianzi5 小时前
基于单片机的智慧小区人脸识别门禁系统
单片机·嵌入式硬件