本篇文章包含的内容
- [一、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方面的学习笔记。*