ZYNQ-定时器中断

通过定时器产生中断,每200ms控制PS LED灯亮灭。

block design和之前的GPIO-MIO配置一致。

代码设计:

#include <stdio.h>
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"
#include "xscugic.h"
#include "xil_exception.h"
#include <xil_printf.h>
#include "xplatform_info.h"
#include "xscutimer.h"

#define GPIO_DEVICE_ID		XPAR_XGPIOPS_0_DEVICE_ID
#define INTC_DEVICE_ID		XPAR_SCUGIC_SINGLE_DEVICE_ID
//GPIO的中断号:XPAR_XGPIOPS_0_INTR,即52
#define TIMER_DEVICE_ID		XPAR_XSCUTIMER_0_DEVICE_ID
#define MIO_LED            0 //MIO 0
#define TIMER_IRPT_INTR		XPAR_SCUTIMER_INTR
// UG585手册中设定的私有定时器的频率是1/2的CPU频率 = 333.3333435Mhz  计数0.2s,则计数器:0.2*333.3333435=3F940AC
#define TIMER_LOAD_VALUE	0x3F940AC  //0.2s

//定义变量
XGpioPs Gpio;  //GPIO器件的驱动实例
XScuGic Intc;  //GIC器件的驱动实例
XScuTimer Timer;	/* Cortex A9 Scu Private Timer Instance */

void mio_init(XGpioPs *gpio_ptr);
void timer_init(XScuTimer *timer_ptr);
void timer_intr_setup(XScuGic *intr_ptr,XScuTimer *timer_ptr);
void timer_intr_handler(void *CallBackRef);

int main(){
	xil_printf("SCU Timer Interrupt Example Test \r\n");

	//GPIO MIO初始化和配置
	mio_init(&Gpio);
	//定时器的初始化和配置
	timer_init(&Timer);
	//定时器中断的初始化和配置
	timer_intr_setup(&Intc,&Timer);
	//开启定时器
	XScuTimer_Start(&Timer);
	return 0;
}

void mio_init(XGpioPs *gpio_ptr)
{
		XGpioPs_Config *ConfigPtr;
		//对GPIO进行初始化(两步)    /* Initialize the GPIO driver. */
		//第一步:根据器件ID去查找器件的配置信息
		ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
		//第二步:对GPIO的驱动进行初始化
		XGpioPs_CfgInitialize(gpio_ptr, ConfigPtr,ConfigPtr->BaseAddr);
		//设置引脚的方向,0为输入,1为输出
		XGpioPs_SetDirectionPin(gpio_ptr, MIO_LED, 1);

		//设置输出使能,1:使能输出,0:不使能输出
		XGpioPs_SetOutputEnablePin(gpio_ptr, MIO_LED, 1);
}

//定时器的初始化和配置
void timer_init(XScuTimer *timer_ptr)
{
	XScuTimer_Config *ConfigPtr;
	//根据定时器器件ID来查找器件的配置信息
	ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
	//对定时器的驱动进行初始化
	XScuTimer_CfgInitialize(timer_ptr, ConfigPtr,
						ConfigPtr->BaseAddr);
	//对定时器进行配置
	//1.使能自动重新装载模式
	XScuTimer_EnableAutoReload(timer_ptr);
	//2.配置定时器的装载值(初始值
	XScuTimer_LoadTimer(timer_ptr, TIMER_LOAD_VALUE);
};

void timer_intr_setup(XScuGic *intr_ptr,XScuTimer *timer_ptr)
{
		XScuGic_Config *IntcConfig;
		//根据中断控制器(GIC)的器件ID来查找配置信息
		IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
		//根据查找到的配置信息初始化中断控制器(GIC)
		XScuGic_CfgInitialize(intr_ptr, IntcConfig,
						IntcConfig->CpuBaseAddress);

		//异常初始化(初始化ARM处理器异常句柄)
		Xil_ExceptionInit();
		//注册中断请求异常的处理程序
		Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
					(Xil_ExceptionHandler)XScuGic_InterruptHandler,
					intr_ptr);
	    //使能处理器的中断
		Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);

		//关联定时器中断处理程序
		XScuGic_Connect(intr_ptr, TIMER_IRPT_INTR,
					(Xil_ExceptionHandler)timer_intr_handler,
					(void *)timer_ptr);
		//使能GIC的定时器中断
		XScuGic_Enable(intr_ptr, TIMER_IRPT_INTR);
		//使能定时器的中断
		XScuTimer_EnableInterrupt(timer_ptr);
};


//定时器中断处理函数
void timer_intr_handler(void *CallBackRef)
{
	static u8 led_state = 0;
	XScuTimer *timer_ptr = (XScuTimer *) CallBackRef;

	if(led_state == 0)
		led_state = 1;
	else
		led_state = 0;
	//向GPIO MIO写入led_state
	XGpioPs_WritePin(&Gpio, MIO_LED, led_state);
	//清除定时器的中断
	XScuTimer_ClearInterruptStatus(timer_ptr);
}

上板验证:成功执行中断打印信息,用于显示定时器中断的LED灯以200ms的时间间隔进行闪烁

关于程序在线调试的运用:

<think>好的,我现在需要帮助用户将现有的C代码中的时间单位从秒转换为微秒。用户之前已经询问过如何将定时器计数转换为秒,现在的问题是如何进一步调整以得到微秒。让我先仔细看看用户提供的代码和之前的对话历史。 首先,回顾之前的回答。用户原来的代码中,使用了一个固定的除数166500000,而之前的建议是将这个除数改为实际的CPU频率666666000 Hz,并调整了代码结构。现在用户的新问题是,要将转换后的时间单位从秒改为微秒(us),也就是百万分之一秒。因此,我需要调整转换公式,将原来的秒转换为微秒。 微秒是秒的百万倍,所以原来的公式是时间(秒)= 时钟周期数 / 频率。要得到微秒,就需要乘以1,000,000。因此,新的公式应该是时间(微秒)= (时钟周期数 / 频率) * 1e6。不过,也可以简化这个计算,直接写成时间(微秒)= (时钟周期数 * 1e6) / 频率。这样可以避免先除以大数再乘以大数可能带来的精度问题,尤其是在使用浮点数的情况下。 接下来,检查用户提供的代码。代码中有一个注释提到,定时器的频率是CPU频率的一半,比如333MHz转换为166.5MHz。但用户之前的CPU频率是666.666 MHz,这可能意味着定时器实际运行在333.333 MHz,也就是CPU频率的一半。所以在计算时,用户原来的代码中将elapsed_ticks乘以2,以补偿定时器频率减半的情况。例如,如果定时器频率是CPU的一半,那么每个定时器的tick相当于两个CPU周期,因此需要乘以2来得到正确的CPU周期数。 现在,用户希望将结果转换为微秒。假设定时器的频率仍然是CPU的一半,那么正确的步骤应该是: 1. 计算经过的定时器tick数:0xFFFFFFFF - timer_count,这已经考虑了定时器是递减计数器。 2. 将这个tick数转换为CPU周期数,这里因为定时器频率是CPU的一半,所以每个tick对应2个CPU周期,所以需要乘以2。 3. 将CPU周期数转换为秒:周期数 / CPU频率(666666000 Hz)。 4. 转换为微秒:秒数 * 1e6。 或者,合并步骤3和4,得到:(周期数 * 1e6) / CPU频率。 在代码中,用户原来的代码是: clock_t time = (clock_t)(2 * elapsed_ticks / CPU_FREQ); 这里的乘以2是因为定时器频率是CPU的一半,所以elapsed_ticks是定时器的tick数,乘以2得到实际的CPU周期数。然后除以CPU频率得到秒数。现在要转换为微秒,需要乘以1e6,所以公式变为: (2 * elapsed_ticks * 1e6) / CPU_FREQ 但需要确保运算顺序正确,避免整数溢出或精度丢失。因为elapsed_ticks是u64类型,乘以2和1e6可能会导致很大的数值,但使用double类型可以处理较大的值,尽管可能会有精度损失。如果用户使用的是32位系统,可能需要特别注意,但u64通常可以处理较大的乘法。 接下来,检查用户提供的代码中的变量类型。原来的代码使用clock_t来存储秒数,但clock_t通常是long类型,可能不足以存储微秒数,尤其是当时间较长时。所以可能需要更改为更大的类型,比如uint64_t,或者在返回时使用不同的类型。不过用户当前函数的返回类型是clock_t,这可能限制了微秒的表示范围,需要注意潜在的溢出问题。但用户可能希望保持函数签名不变,或者需要调整返回类型。不过根据用户的问题,可能只是要修改计算部分,而不是改变函数结构。 另外,用户代码中的CPU_FREQ被定义为666666000.0,而原来的注释提到定时器频率是CPU的一半,所以这里可能需要确认是否正确。例如,如果CPU频率是666.666 MHz,定时器频率是333.333 MHz,那么正确的CPU_FREQ应该是666666000.0,而定时器频率是333333000.0。但是用户之前的代码中将elapsed_ticks乘以2,以转换为CPU周期数,这样计算的是实际的CPU周期数,再除以CPU频率得到秒数。所以现在转换为微秒,只需要在原来的秒数基础上乘以1e6,或者在计算时合并。 现在,具体修改代码的步骤: 原代码中的转换部分是:2 * elapsed_ticks / CPU_FREQ,结果转换为秒。 要转换为微秒,需要乘以1e6,所以变为:(2 * elapsed_ticks * 1e6) / CPU_FREQ 或者,等效于 (2 * elapsed_ticks / CPU_FREQ) * 1e6。这两种方式在数学上是等价的,但在计算时可能会有不同的精度,尤其是当数值很大时。使用浮点运算时,先乘以1e6再除以CPU_FREQ可能更优,因为避免了大数相乘,但需要具体分析。 考虑到CPU_FREQ是666666000.0,而1e6是1,000,000,所以整个分母可以简化为666666000.0 / 1e6 = 666.666。因此,公式可以简化为: (2 * elapsed_ticks) / (CPU_FREQ / 1e6) ) = (2 * elapsed_ticks) / 666.666 这可能更高效,但需要确认是否正确。因为微秒是秒的百万分之一,所以原来的秒数乘以1e6得到微秒数,即: 微秒数 = (周期数 / 频率) * 1e6 = 周期数 * 1e6 / 频率 这里周期数是2 * elapsed_ticks,所以: 微秒数 = (2 * elapsed_ticks * 1e6) / 666666000.0 将数值代入计算,666666000.0等于666.666 MHz,乘以1e6得到Hz。所以,简化分母: 666666000.0 = 666.666e6,因此: (2 * elapsed_ticks * 1e6) / 666.666e6 = (2 * elapsed_ticks) / 666.666 因为1e6 / 666.666e6 = 1 / 666.666,所以: 微秒数 = (2 * elapsed_ticks) / 666.666 这可能更高效,但需要注意浮点运算的精度。不过,原代码中使用的是double类型,所以没有问题。 在代码中,用户原来的代码是: clock_t time = (clock_t)(2 * elapsed_ticks / CPU_FREQ); 现在要改为微秒,所以应该修改为: clock_t time = (clock_t)( (2 * elapsed_ticks * 1000000.0) / CPU_FREQ ); 或者,可能更高效的方式是先进行乘法: (2 * elapsed_ticks) * 1e6 / CPU_FREQ 但需要注意运算顺序,避免溢出。由于elapsed_ticks是u64,乘以2和1e6可能会导致数值非常大,超过u64的范围。例如,假设elapsed_ticks是最大的u64值,那么乘以2*1e6会溢出,但在这里代码中,elapsed_ticks是32位定时器的值,所以0xFFFFFFFF,即大约4.2e9,乘以2得到8.4e9,再乘以1e6得到8.4e15,这仍然在u64的范围内(因为u64最大约1.8e19),所以不会有溢出问题。但是,当转换为double时,可能需要先转换为浮点数再进行乘法,以避免整数溢出。 不过,在代码中,用户已经将elapsed_ticks声明为u64,并且在计算时转换为double,所以应该不会有问题。例如: double us = (2.0 * elapsed_ticks * 1000000.0) / CPU_FREQ; 然后转换为clock_t。不过,clock_t通常是long类型,可能不足以存储较大的微秒数值,如果时间较长的话,可能会溢出。例如,如果经过的时间是1秒,即1,000,000微秒,在32位系统上,long的最大值是2,147,483,647,所以可以存储约35分钟左右的微秒数,超过的话会溢出。但用户可能需要使用更大的类型,比如uint64_t,但函数返回类型是clock_t,可能无法更改。不过用户的问题只是如何修改转换部分,所以可能暂时不考虑这个问题,或者提示用户注意溢出。 回到代码,原函数返回clock_t,这可能不适合微秒,但用户可能希望保持接口一致。因此,修改后的代码部分是将秒转换为微秒,即乘以1e6,并调整注释和相关常量。 总结修改步骤: 1. 修改注释中的单位说明,将秒改为微秒。 2. 在计算公式中乘以1,000,000,将秒转换为微秒。 3. 考虑是否需要调整变量类型以避免溢出,尤其是当elapsed_ticks较大时。 具体到代码: 原代码行: clock_t time = (clock_t)(2 * elapsed_ticks / CPU_FREQ); 修改为: clock_t time = (clock_t)( (2 * elapsed_ticks * 1000000.0) / CPU_FREQ ); 或者,更清晰的写法: clock_t time = (clock_t)( (2.0 * elapsed_ticks / CPU_FREQ) * 1000000.0 ); 这两种写法在数学上是等价的,但运算顺序可能影响精度。先进行乘法可能会更优,因为先乘以1e6再除以频率,可能减少浮点数的舍入误差,尤其是当elapsed_ticks较小时。 另外,需要确认CPU_FREQ的定义是否正确。用户提到定时器频率是CPU的一半,所以原来的代码中使用2 * elapsed_ticks来得到CPU周期数。例如,如果定时器每递减一个tick需要两个CPU周期,那么elapsed_ticks是定时器的tick数,乘以2得到对应的CPU周期数,再除以CPU频率得到秒数。现在转换为微秒,同样是基于CPU周期数,所以同样的逻辑适用:先乘以2得到CPU周期数,再转换为微秒。 因此,修改后的公式正确。 另外,用户代码中的注释提到“转换为秒”,需要修改为“转换为微秒”。 接下来,检查变量类型。elapsed_ticks是u64,乘以2后是u64,再乘以1e6(作为double)转换为浮点运算,然后除以666666000.0,结果转换为clock_t。这可能会有精度损失,但可能可以接受。或者,建议使用更高精度的类型,如double,但在代码中已经这样处理。 此外,用户可能需要注意,clock_t通常用于表示处理器时间,单位可能是时钟周期,但在这里被用来存储微秒,可能导致混淆。但根据用户的需求,可能只是需要修改计算部分,而函数接口保持不变。 最后,给出修改后的代码示例,并指出关键修改点,例如乘以1e6,修改注释,以及可能的溢出问题提示。</think>要将时间单位从秒转换为微秒(μs),需在原有计算基础上增加百万倍放大。以下是分步修改说明: ```c clock_t _times(struct tms *buf) { u32 timer_count = XScuTimer_GetCounterValue(&TimerInstance); // 关键修改点:新增微秒转换系数 const double US_PER_SEC = 1000000.0; // 1秒=1,000,000微秒 const double TIMER_MULTIPLIER = 2.0; // 定时器分频系数 const double CPU_FREQ = 666666000.0; // CPU频率(Hz) u64 elapsed_ticks = 0xFFFFFFFF - timer_count; // 修改计算公式:添加微秒转换 double time_us = (TIMER_MULTIPLIER * elapsed_ticks / CPU_FREQ) * US_PER_SEC; // 建议使用更合适的数据类型(原clock_t可能溢出) clock_t time = (clock_t)time_us; buf->tms_utime = time; buf->tms_stime = 0; return time; } ``` **核心修改解析**: 1. **单位换算原理**: $$1\text{秒} = 10^6 \mu s \Rightarrow \text{微秒} = \text{秒} \times 10^6$$ 2. **公式重构**: ```c // 原秒计算: (2 * ticks) / 666666000.0 // 新微秒计算: (2 * ticks / 666666000.0) * 1000000.0 ``` 3. **精度优化**: - 使用`double`类型保留小数运算 - 显式定义转换常量提高可读性 4. **潜在问题处理**: ```c // 针对32位系统建议改用uint64_t: uint64_t time_us = (uint64_t)((2.0 * elapsed_ticks * 1e6) / CPU_FREQ); // 添加溢出检查(假设最大允许值): if(time_us > CLOCK_MAX) { // 错误处理逻辑 } ``` 5. **完整优化版**: ```c #include <stdint.h> #define TIMER_FREQ_HZ 333333000.0 // 实际定时器频率 #define US_PER_SEC 1000000.0 // 微秒转换系数 clock_t _times(struct tms *buf) { uint32_t timer_current = XScuTimer_GetCounterValue(&TimerInstance); static uint32_t timer_prev = 0; // 处理定时器溢出 uint64_t elapsed_ticks = (timer_prev > timer_current) ? (0xFFFFFFFFULL - timer_prev + timer_current) : (timer_prev - timer_current); // 带溢出保护的微秒计算 double time_us = (double)elapsed_ticks / TIMER_FREQ_HZ * US_PER_SEC; // 更新基准值 timer_prev = timer_current; buf->tms_utime = (clock_t)time_us; return (clock_t)time_us; } ``` **验证方法**: 1. 已知定时器周期时测试: ```c // 当定时器配置为1ms间隔时: assert(fabs(time_us - 1000.0) < 1e-6); ``` 2. 使用示波器测量实际GPIO翻转时间 3. 增加调试输出: ```c printf("Raw ticks:%llu -> Calculated us:%.2f\n", elapsed_ticks, time_us); ``` **注意事项**: 1. 定时器工作模式确认(递减/递增计数器) 2. 中断服务程序中的上下文切换时间补偿 3. 32位系统下clock_t类型上限(通常为2147483647 ≈ 35.8分钟) 4. 浮点运算的原子性保护(若在中断上下文使用)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值