MIPS微控制器控制LED灯带时短定时器周期偏差过大求助
问题分析与解决方案
核心错误:定时器未正确配置
你遇到的10000倍延时偏差,最根本原因是完全没有配置定时器2的周期寄存器(PR2)和预分频器:
- 80MHz时钟下,单个周期是12.5ns,0.3μs对应24个时钟周期,但你没有将这个值写入
PR2,定时器会默认计数到最大值(比如16位定时器的65535),溢出时间约为65536/80e6 ≈ 0.82ms,和预期的0.3μs差了2700倍;如果预分频器默认是256分频,溢出时间会进一步拉长到≈210ms,偏差就接近你看到的10000倍。 - 你只做了定时器的启停操作,但从未设置定时器的核心参数,导致延时逻辑完全失效。
次要问题:函数调用与定时器操作的开销
即使定时器配置正确,你的wait函数和调用逻辑也会引入大量额外延时:
switch分支判断、for循环、定时器启停操作,每个步骤都需要几十个时钟周期,对于ns级的高精度延时来说,这些开销会严重偏离预期。- 每次调用
wait函数的栈操作、参数传递,也会增加额外的指令周期。
修正方案
1. 正确配置定时器(如果坚持用定时器延时)
先初始化定时器2的核心参数,在程序启动时执行:
void initTimer2() { T2CONCLR = 0x8000; // 先停止定时器 T2CONCLR = 0x0030; // 设置预分频为1:1(T2CKPS1:T2CKPS0 = 00) PR2 = 23; // 0.3μs对应周期数:80e6 * 0.3e-6 - 1 = 23(从0计数到23共24个周期) TMR2 = 0; // 重置定时器计数器 }
然后简化wait函数,避免重复启停定时器:
void wait(int timer) { int cycles = timer / 3; // 3对应0.3μs,计算需要的溢出次数 IFSCLR(0) = 0x100; // 重置中断标志 T2CONSET = 0x8000; // 启动定时器 while (cycles-- > 0) { while (!(IFS(0) & 0x100)); // 等待溢出 IFSCLR(0) = 0x100; // 重置标志 } T2CONCLR = 0x8000; // 停止定时器 }
2. 更适合高精度延时的方案:内联汇编忙等循环
对于ns级延时,定时器启停的开销依然过大,直接用内联汇编写忙等循环是更可靠的方式(MIPS指令周期可预测):
// 延时0.3μs(24个时钟周期) static inline void wait_03us() { __asm__ volatile ( "li $t0, 5\n" // 调整循环次数,确保总周期为24(含指令本身) "1:\n" "addi $t0, $t0, -1\n" "bne $t0, $zero, 1b\n" ); } // 延时0.6μs(48个时钟周期) static inline void wait_06us() { __asm__ volatile ( "li $t0, 11\n" "1:\n" "addi $t0, $t0, -1\n" "bne $t0, $zero, 1b\n" ); } // 延时0.9μs(72个时钟周期) static inline void wait_09us() { __asm__ volatile ( "li $t0, 17\n" "1:\n" "addi $t0, $t0, -1\n" "bne $t0, $zero, 1b\n" ); }
修改send2LED函数,直接调用这些内联函数,消除函数调用开销:
void send2LED(long int data){ int i; for (i=0; i<32; i++){ if (data & 0x80000000) { PORTDSET = 0x100; wait_06us(); PORTDCLR = 0x100; wait_06us(); } else { PORTDSET = 0x100; wait_03us(); PORTDCLR = 0x100; wait_09us(); } data <<= 1; } }
额外注意事项
- 引脚操作(
PORTDSET/PORTDCLR)本身需要1-2个时钟周期,计算延时的时候要将这部分时间考虑进去,微调循环次数。 - MIPS的分支指令有延迟槽,编写内联汇编时要确认实际指令周期数,可通过示波器观测后调整循环次数。
内容的提问来源于stack exchange,提问作者lukas gamard
相关产品推荐
相关产品推荐

