基于Lemon ZYNQ的PS实验五 定时器中断实验

和所有的MCU一样ZYNQ的PS端也有自己的定时器中断功能,本节将对该功能进行演示。

  • 此章节内容适用于Lemon ZYNQ主板,如是其他板子请看对应板子目录
  • 本文在 vivado2018.3版本上演示

定时器中断可以为系统提供标准的时钟,心跳,也可以用来作为为一些计算的时间基准(如脉冲宽度,脉冲周期等)在实际项目中都是必不可少的。

ZYNQ中每个ARM core都有自己的私有定时器,私有定时器的工作频率为CPU的一半,如ZYNQ的工作频率为666MHZ,则定时器的频率为333MHz,后面需要注意这个时钟。

本节内容我们将完成定时器中断实验,并了解PS 端私有定时器的使用方法。

一、实验内容:

我们在PS端设置一个定时器,并让定时器每计数满1ms触发一次中断,并且每累计一秒钟的时候点亮或者熄灭一个LED灯,来达到演示的效果。

所以整个系统我们只需要在BLOCK DESIGN 中 zynq核内 拓展一个EMIO 的GPIO口,并用PS的SDK 程序的定时器每秒去驱动这个GPIO即可。

二、工程创建

工程创建的过程可以参考试验一中的内容,这里不再详细描述了。基于Lemon ZYNQ的PS实验一 GPIO之用EMIO方式点亮LED(完整图文)(芯片型号选XC7Z020CLG400-1)

三、Vivado 中的设置

1)在BLOCK DESIGN 中搜索并添加一个ZYNQ模块

2)在ZYNQ 设置界面,设置 DDR MT41K256M16RE-125的型号 和位宽16bit

3)因为Lemon ZYNQ主板的PS时钟是50M的晶振输入的,所以这里需要把默认的PS输入时钟33.33M改成50M

4)添加一个GPIO的接口(EMIO方式)为后面点亮LED做准备

5)记录下 CPU的时钟(这里不需要修改 只需要看一下记录下当前时钟频率就好)

6)对GPIO make external,并且连接AXI的时钟(AXI默认开启的,这里用不到AXI,也可以去设置里关闭AXI功能,就不需要连接了)

7)点选Create HDL Wrapper

8)之后创建和添加约束文件对管脚进行定义:

set_property PACKAGE_PIN R14 [get_ports {GPIO_0_0_tri_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[0]}]

9) 之后对程序进行正常的编译综合,并且导出 Export Hardware (勾选 include bitstream)供SDK加载

四、PS部分程序设计:

在SDK环境下建立一个TIMER_TEST空的工程,并且添加main.c 添加如下代码:

#include "xparameters.h"
#include "xgpiops.h"
#include "xstatus.h"
#include "xplatform_info.h"
#include "xscutimer.h"
#include "Xscugic.h"

#define LED    	54

#define GPIO_DEVICE_ID  	XPAR_XGPIOPS_0_DEVICE_ID
XGpioPs Gpio;


void Gpio_Init(void){
	XGpioPs_Config *ConfigPtr;

	ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
	XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr);

	XGpioPs_SetDirectionPin(&Gpio, LED, 1);
	XGpioPs_SetOutputEnablePin(&Gpio, LED, 1);
	XGpioPs_WritePin(&Gpio, LED, 0);
}


#define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR
#define TIMER_LOAD_VALUE 0x514c7 //666*1000*1/2 666mhz
static XScuGic Intc; //GIC
static XScuTimer Timer;//timer

static void SetupInterruptSystem(XScuGic *GicInstancePtr,
XScuTimer *TimerInstancePtr, u16 TimerIntrId);
static void TimerIntrHandler(void *CallBackRef);

void SetupInterruptSystem(XScuGic *GicInstancePtr,XScuTimer *TimerInstancePtr, u16 TimerIntrId){
	XScuGic_Config *IntcConfig; //GIC config
	Xil_ExceptionInit();
	//initialise the GIC
	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,IntcConfig->CpuBaseAddress);
	//connect to the hardware
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
		(Xil_ExceptionHandler)XScuGic_InterruptHandler,
		GicInstancePtr);
	//set up the timer interrupt
	XScuGic_Connect(GicInstancePtr, TimerIntrId,
		(Xil_ExceptionHandler)TimerIntrHandler,
		(void *)TimerInstancePtr);
	//enable the interrupt for the Timer at GIC
	XScuGic_Enable(GicInstancePtr, TimerIntrId);
	//enable interrupt on the timer
	XScuTimer_EnableInterrupt(TimerInstancePtr);
	// Enable interrupts in the Processor.
	Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
}

volatile int ms_count;
unsigned char led_state=0;

static void TimerIntrHandler(void *CallBackRef){
	static int ms_count = 0; //计数
	XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
	XScuTimer_ClearInterruptStatus(TimerInstancePtr);

	ms_count++;
	if(ms_count>=1000){
		ms_count=0;
		if(led_state==0)led_state=1;
		else led_state=0;
		XGpioPs_WritePin(&Gpio, LED, led_state);
	}
}


void Timer_Init(){
	XScuTimer_Config *TMRConfigPtr;
	TMRConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
	XScuTimer_CfgInitialize(&Timer, TMRConfigPtr,TMRConfigPtr->BaseAddr);
	XScuTimer_SelfTest(&Timer);
	XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE);
	//自动装载
	XScuTimer_EnableAutoReload(&Timer);
	//启动定时器
	XScuTimer_Start(&Timer);
	//set up the interrupts
	SetupInterruptSystem(&Intc,&Timer,TIMER_IRPT_INTR);
}
int main(void)
{
	Gpio_Init();
	Timer_Init();

	while(1);

	return 0;
}

五、代码简单介绍

Timer_Init(); 是定时器的初始化函数,在系统运行的时候,需要对定时器初始化才能正常工作,其中TIMER_LOAD_VALUE 用来控制 每一次定时器触发的时间, 还记得我们前面的CPU时钟为666mhz吗,那我们的定时器时钟就相当于工作在1/2的CPU时钟即333mhz频率下。 我们的系统设置是每1ms进入一次中断,那1ms就相当于需要计数(333000000)*1/1000=333000个周期,那我们就设置TIMER_LOAD_VALUE 为 333000-1,即0x514c7 (这里代表1ms的数值)

TimerIntrHandler 是定时器的回调函数,相当于单片机的中断服务函数,每当中断被触发,就会调用一次该回调函数。 因为是1ms进入一次,所以假设我们需要计数1S钟,就需要额外增加一个变量ms_count

每次进入该回调函数 ms_count都自增,当大于等于1000的时候让ms_count归零完成一次循环,同时执行我们的LED变换操作。 如果我们有其他程序需要添加,也可以增加到 TimerIntrHandler的回调函数钟。

ms_count++;
if(ms_count>=1000){
    ms_count=0;

    if(led_state==0)led_state=1;
    else led_state=0;
    XGpioPs_WritePin(&Gpio, LED, led_state);
}

对于main函数来说,我们只需要执行GPIO 和定时器的初始化就好

int main(void){
	Gpio_Init();
	Timer_Init();
	while(1);
	return 0;
}

五、下载与验证

1)新创建的工程最好先对Run as 进行配置:右键工程,并点选Run As -> Run Configurations

2) 在弹出的窗口中对Reset entire system 和 Program FPGA两个选项进行勾选操作,这样就不会出现下载程序debug的时候概率性不工作的问题了。(这样操作后系统会自动对FPGA进行配置,不需要按之前工程手动对FPGA进行编程了)

PS如果没有出现下图对话框,可以直接双击左侧的applicationo (System Debugger)。

3)设置好之后,选择Run As -> Launch on Hardware (System Debugger) 进行调试。

下载到我们的板子上,可以看到 主板上的LED 灯每秒钟闪烁一次, 说明我们的定时器程序正常在工作了。程序比较简单,仅供参考学习,对于ZYNQ的定时器来说,还有很多功能(包括用PL去触发定时器中断等),需要实际做项目的时候自行去拓展了。

  • 本文的完整工程下载:05_PS_TIMER_TEST
  • VIVADO的版本:2018.3
  • 工程创建目录: E:\Lemon_ZYNQ\SDK\05_PS_TIMER_TEST

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注