open_prj13_MPSoC 私有定时器

发布于:2025-09-12 ⋅ 阅读:(20) ⋅ 点赞:(0)

私有定时器中断

a)ZYNQ系统中的定时器资源包含了私有定时器,私有看门狗定时器、以及TTC定时器。
在这里插入图片描述
①PS侧的CPU0和CPU1各配有1个私有定时器和1个私有看门狗定时器,同时共享一个全局定时器。
②PS侧还包含TTC0和TTC1两个定时器模块,每个TTC模块内集成三个独立的定时器/计数器。
③私有定时器/看门狗定时器都有以下共同的特性:
i)32位定时计数器,计数归零时触发中断;
ii)参考时钟频率为CPU时钟的1/2(例如:CPU时钟666.6666MHz时,定时器参考时钟为333.3333MHz);
iii)支持8位预分频器对参考时钟进行分频;
iv)可配置预加载寄存器,支持单次触发或自动重载两种工作模式。

b)
/
①PS部分中的CPU0和CPU1各自配备了一个私有定时器。CPU0的定时器中断通过中断派发器分配给CPU0处理,其对应的中断请求(IRQ)标识号为29。

c)CPU私有定时器的4个寄存器

Field Name Bits Type Reset Value Description
XSCUTIMER_LOAD 31:0 至少可写 0x0 私有定时器的预加载值
XSCUTIMER_COUNTER 31:0 至少可读 0x0 私有定时器的计数器
XSCUTIMER_CONTROL 31:0 R/W 0x0 私有定时器的控制寄存器:预分频设置、中断使能、自动重载使能、定时器使能
XSCUTIMER_ISR 31:0 R/W 0x0 私有定时器的中断寄存器

私有定时器中断实现和代码赏析(米联客)

a)目标:使用ZYNQ自带的私有定时器资源,程序设计中使用私有定时器每1S中断1次,通过串口打印输。

b)
1)中断资源初始化
①init_intr_sys函数

/*
	1.1 系统中断初始化+功能中断初始化
*/
int init_intr_sys(void)
{
	Init_Intr_System(&Intc);                                     // 初始化中断系统
	TimerIntrSetup(&Intc,&Timer,TIMER_IRPT_INTR,TIMER_DEVICE_ID);// 初始定时器中断
	Setup_Intr_Exception(&Intc);
}

②init_intr_sys函数/TimerIntrSetup函数

/*
	1.2 定时器中断设置
*/
void TimerIntrSetup(XScuGic *GicInstancePtr, XScuTimer *TimerInstancePtr, u16 TimerIntrId, u16 DeviceId) {
    XScuTimer_Config *TMRConfigPtr;  // 声明定时器配置结构指针

    // 查找并初始化定时器配置
    TMRConfigPtr = XScuTimer_LookupConfig(DeviceId);  // 使用DeviceId查找定时器硬件配置
    XScuTimer_CfgInitialize(TimerInstancePtr, TMRConfigPtr, TMRConfigPtr->BaseAddr);  // 初始化定时器实例

    // 加载定时器计数值
    XScuTimer_LoadTimer(TimerInstancePtr, TIMER_LOAD_VALUE);  // 设置定时器初始计数值,TIMER_LOAD_VALUE是预定义常量

    // 启用自动重载功能
    XScuTimer_EnableAutoReload(TimerInstancePtr);  // 使定时器在计数到0时自动重载初始值,实现周期性中断

    // 设置中断处理函数
    XScuGic_Connect(GicInstancePtr, TimerIntrId, 
                     (Xil_ExceptionHandler)timer_callback,  // timer_callback是用户定义的中断服务函数
                     (void *)TimerInstancePtr);  // 传递定时器实例作为回调参数

    // 在GIC中使能中断
    XScuGic_Enable(GicInstancePtr, TimerIntrId);  // 激活GIC中的该中断ID

    // 使能定时器中断
    XScuTimer_EnableInterrupt(TimerInstancePtr);  // 开启定时器本身的中断功能
}

③init_intr_sys函数/TimerIntrSetup函数/XScuTimer_LookupConfig函数

/*
	1.3 查看硬件参数,并且把硬件的参数保持到一个指针中
*/
XScuTimer_Config *XScuTimer_LookupConfig(u16 DeviceId)
{
	XScuTimer_Config *CfgPtr = NULL;
	u32 Index;

	for (Index = 0U; Index < XPAR_XSCUTIMER_NUM_INSTANCES; Index++) {
		if (XScuTimer_ConfigTable[Index].DeviceId == DeviceId) {
			// 匹配则将CfgPtr设置为该元素的地址(即 &XScuTimer_ConfigTable[Index]),并通过break立即退出循环。
			CfgPtr = &XScuTimer_ConfigTable[Index];
			break;
		}
	}
	// 如果找到匹配项,它指向对应的配置结构;否则为NULL。
	return (XScuTimer_Config *)CfgPtr;
}

// ConfigTable参数保存了硬件的基本信息
XScuTimer_Config XScuTimer_ConfigTable[XPAR_XSCUTIMER_NUM_INSTANCES] =
{
	{
		XPAR_PS7_SCUTIMER_0_DEVICE_ID,
		XPAR_PS7_SCUTIMER_0_BASEADDR
	}
};

// 在系统头文件xparameters.h中保存了硬件参数的具体值
#define XPAR_XSCUTIMER_NUM_INSTANCES 1
#define XPAR_PS7_SCUTIMER_0_DEVICE_ID 0
#define XPAR_PS7_SCUTIMER_0_BASEADDR 0xF8F00600
#define XPAR_PS7_SCUTIMER_0_HIGHADDR 0xF8F0061F

④init_intr_sys函数/TimerIntrSetup函数/XScuTimer_CfgInitialize函数
在支持MMU的系统中:硬件寄存器的物理地址是固定的(例如Zynq的SCU定时器物理基地址为0xF8F00600),这个地址值会被固化在 ConfigPtr中(由 BSP 或硬件配置工具生成)。当操作系统或裸机程序启用MMU后,物理地址会被映射到虚拟地址空间(用于实现内存保护和地址隔离)。此时,CPU必须通过虚拟地址来访问硬件寄存器,而不能直接使用物理地址。 EffectiveAddress参数的作用就是接收经过 MMU映射后的虚拟地址,确保驱动程序能够正确访问寄存器。ConfigPtr中的物理地址仅作为硬件标识使用,并不直接参与寄存器访问。

/*
	1.4 配置定时器实例的参数,并确保设备在未启动状态下才能进行初始化。
		如果设备已经启动,函数会拒绝初始化并返回错误状态。
*/
s32 XScuTimer_CfgInitialize(XScuTimer *InstancePtr,
			 XScuTimer_Config *ConfigPtr, u32 EffectiveAddress)
{
	s32 Status;

	// Xilinx SDK 提供的调试断言宏,仅在调试模式下生效
	// 断言会触发程序暂停,强制开发者修正 “传入空指针” 的错误
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(ConfigPtr != NULL);

    // 设备未启动才能初始化
	if (InstancePtr->IsStarted != XIL_COMPONENT_IS_STARTED) {
		// 未启动:执行初始化
		// 复制设备ID到实例(绑定设备唯一标识)
		InstancePtr->Config.DeviceId = ConfigPtr->DeviceId;
		// 保存寄存器基地址(后续操作的“硬件入口”)
		InstancePtr->Config.BaseAddr = EffectiveAddress;
		// 标记实例未启动(初始化后需手动调用启动函数)
		InstancePtr->IsStarted = (u32)0;
		// 标记实例“就绪”(表示初始化完成,可调用其他API)
		InstancePtr->IsReady = XIL_COMPONENT_IS_READY;
		// 设置成功状态码
		Status =(s32)XST_SUCCESS;
	}
	else {
		// 已启动:返回错误
		Status = (s32)XST_DEVICE_IS_STARTED;
	}
	return Status;
}

⑤init_intr_sys函数/TimerIntrSetup函数/XScuTimer_LoadTimer函数

/*
	1.5 设置定时器的初始倒计时值
	    嵌入式驱动中优先使用宏实现这类简单操作,无函数调用开销.
*/
#define XScuTimer_LoadTimer(InstancePtr, Value)			    \
	XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr,		\
			XSCUTIMER_LOAD_OFFSET, (Value))

#define XScuTimer_WriteReg(BaseAddr, RegOffset, Data)	\
	Xil_Out32((BaseAddr) + (RegOffset), (Data))

⑥init_intr_sys函数/TimerIntrSetup函数/XScuTimer_EnableAutoReload函数

/*
	1.6 设置定时器控制器的重载值,使能重载
     	重载的含义是当定时计数器计数到0的时候,重新加载预加载值,这样无需程序里面重复加载预加载值
*/
#define XScuTimer_EnableAutoReload(InstancePtr)				   \
	XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr,		   \
			XSCUTIMER_CONTROL_OFFSET,			               \
			(XScuTimer_ReadReg((InstancePtr)->Config.BaseAddr, \
				XSCUTIMER_CONTROL_OFFSET) |		               \
				XSCUTIMER_CONTROL_AUTO_RELOAD_MASK))					

⑦init_intr_sys函数/TimerIntrSetup函数/XScuGic_Connect函数

/*
	1.7 建立中断ID与中断处理函数之间的关联
	    InstancePtr->Config->HandlerTable保存了所有全局中断函数,如果某一个全局中断对应的中断被CPU响应,
        CPU就会根据这个中断回调相应的中断函数
*/
s32  XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
				Xil_InterruptHandler Handler, void *CallBackRef)
{
	// 参数合法性断言(调试阶段防错)
	Xil_AssertNonvoid(InstancePtr != NULL);  // 实例指针非空
	Xil_AssertNonvoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS); // 确保中断ID在有效范围内(小于最大中断数)
	Xil_AssertNonvoid(Handler != NULL); // 确保中断处理函数指针不为空
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 确保GIC控制器已初始化就绪
 
    // 中断ID用作处理函数表的索引,将处理函数与该中断绑定
    // 将中断处理函数存入处理函数表中对应中断ID的条目
	InstancePtr->Config->HandlerTable[Int_Id].Handler = (Xil_InterruptHandler)Handler;
	// 将回调参数存入处理函数表中对应中断ID的条目,供处理函数使用
	InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = CallBackRef = CallBackRef;

	return XST_SUCCESS;
}	

⑧init_intr_sys函数/TimerIntrSetup函数/XScuGic_Enable函数

/*
	1.8 启用指定中断ID对应的中断,允许指定的中断源产生的中断被转发到CPU,
	    同时确保中断被映射到当前CPU,并通过自旋锁保证多CPU环境下的操作原子性。
*/
void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)
{
	u32 Mask;               // 中断使能位掩码,用于设置寄存器特定位
	u8 Cpu_Id = (u8)CpuId;  // 当前CPU的ID(用于将中断映射到该CPU)

#if defined (GICv3)
	u32 Temp;
#endif

	// 断言检查参数合法性(仅调试模式生效)
	Xil_AssertVoid(InstancePtr != NULL); // 确保GIC实例指针非空
	Xil_AssertVoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS); // 确保中断ID在有效范围
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); // 确保GIC已初始化就绪
	
	// 将中断映射到当前CPU(Cpu_Id),确保该CPU能接收此中断
	XScuGic_InterruptMaptoCpu(InstancePtr, Cpu_Id, Int_Id);
	// 自旋锁:保护临界区,防止多CPU/多线程同时修改中断寄存器导致错误
	XIL_SPINLOCK();
	// 计算中断使能掩码:GIC的使能寄存器按32位分组,每个位对应一个中断
	// Int_Id % 32 得到该中断在32位寄存器中的位索引,左移后得到只置位该位的掩码
	Mask = 0x00000001U << (Int_Id % 32U);
	// 向GIC分发器的"中断使能设置寄存器"写入掩码,启用指定中断
	XScuGic_DistWriteReg(InstancePtr, (u32)XSCUGIC_ENABLE_SET_OFFSET +
				((Int_Id / 32U) * 4U), Mask);
	// 释放自旋锁,结束临界区保护
	XIL_SPINUNLOCK();
}

⑨init_intr_sys函数/TimerIntrSetup函数/XScuTimer_EnableInterrupt函数

/*
	1.9 使能定时器的中断使能位
*/
#define XScuTimer_EnableInterrupt(InstancePtr)				\
	XScuTimer_WriteReg((InstancePtr)->Config.BaseAddr,		\
			XSCUTIMER_CONTROL_OFFSET,			\
			(XScuTimer_ReadReg((InstancePtr)->Config.BaseAddr, \
					XSCUTIMER_CONTROL_OFFSET) |	\
					XSCUTIMER_CONTROL_IRQ_ENABLE_MASK))						

网站公告

今日签到

点亮在社区的每一天
去签到