私有定时器中断
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))