4. 定时器中断实现秒表
任务目标
在玄铁C906/C910平台上,利用硬件定时器产生周期性中断,在中断服务函数中维护计时变量,实现一个具备启动、停止、清零功能的秒表,并通过串口打印当前计时值。通过本案例掌握定时器中断的配置流程与中断处理程序的编写方法。
硬件与依赖
- 开发板:玄铁C906/C910 EVB板
- SDK:T-Head玄铁嵌入式SDK(含驱动库)
- 外设:UART0(调试串口)、片上定时器(Timer0)
- 依赖头文件:
tmr.h(定时器驱动)、uart.h(串口驱动)、plic.h(中断控制器)
实现原理
定时器工作在周期模式,时钟源为系统主频分频后的固定频率。配置定时器溢出值,使中断频率为100Hz(即每10ms触发一次中断)。在中断服务函数中累加计数变量,当计数达到100时表示1秒,更新秒表显示值。主循环中通过串口命令控制秒表状态。
完整代码
#include <tmr.h>
#include <uart.h>
#include <plic.h>
#include <sysctl.h>
#include <stdio.h>
#include <string.h>
#define TIMER_NUM 0
#define TIMER_CHANNEL 0
#define TIMER_FREQ 100 // 100Hz中断
#define TICK_PER_SEC 100 // 每100次中断为1秒
volatile uint32_t tick_count = 0;
volatile uint32_t seconds = 0;
volatile uint8_t running = 0;
volatile uint8_t need_clear = 0;
// 定时器中断服务函数
void timer0_isr(void)
{
tick_count++;
if (tick_count >= TICK_PER_SEC) {
tick_count = 0;
seconds++;
}
// 清除中断标志
tmr_irq_clear(TIMER_NUM, TIMER_CHANNEL);
}
// 定时器初始化
void timer_init(void)
{
uint64_t clk_freq = sysctl_clock_get_freq(SYSCTL_CLOCK_CPU);
uint64_t interval = clk_freq / TIMER_FREQ;
tmr_init(TIMER_NUM);
tmr_set_clock(TIMER_NUM, TIMER_CHANNEL, clk_freq);
tmr_set_mode(TIMER_NUM, TIMER_CHANNEL, TMR_MODE_PERIOD);
tmr_set_count(TIMER_NUM, TIMER_CHANNEL, 0);
tmr_set_reload(TIMER_NUM, TIMER_CHANNEL, interval);
tmr_set_enable(TIMER_NUM, TIMER_CHANNEL, 1);
// 注册中断
plic_irq_register(TMR_IRQN(TIMER_NUM, TIMER_CHANNEL), timer0_isr);
plic_irq_enable(TMR_IRQN(TIMER_NUM, TIMER_CHANNEL));
}
// 串口命令处理
void process_command(char cmd)
{
switch (cmd) {
case 's': // 启动
running = 1;
break;
case 'p': // 暂停
running = 0;
break;
case 'c': // 清零
need_clear = 1;
break;
default:
break;
}
}
int main(void)
{
char rx_char;
uint32_t last_seconds = 0;
uart_init(UART_DEV0, 115200);
timer_init();
printf("秒表已启动,命令: s=启动 p=暂停 c=清零\r\n");
while (1) {
// 接收串口命令
if (uart_rx_poll(UART_DEV0, &rx_char)) {
process_command(rx_char);
}
// 清零处理(在非中断上下文执行)
if (need_clear) {
need_clear = 0;
tick_count = 0;
seconds = 0;
}
// 每秒打印一次当前值
if (running && seconds != last_seconds) {
last_seconds = seconds;
printf("计时: %u 秒\r\n", seconds);
}
}
return 0;
}
编译与运行
- 将上述代码保存为
stopwatch.c,放入SDK的samples/stopwatch/目录。 - 在SDK根目录执行交叉编译:
make stopwatch
- 将生成的
stopwatch.bin烧录至开发板,使用串口工具连接UART0(波特率115200)。
验证方法
- 启动秒表:发送字符
s,串口应每秒输出一次计时: N 秒,N从0开始递增。 - 暂停功能:发送
p,计时输出停止,数值保持不变。 - 清零功能:发送
c,再发送s,计时从0重新开始。 - 中断验证:若注释掉
tmr_irq_clear()行,程序将卡死在中断中(无法响应串口),反向证明中断服务函数被正确触发。
通过标准:串口输出每秒递增1,且启动/暂停/清零命令均能正确响应,即判定案例通过。
常见问题
- 无输出:检查UART引脚配置及波特率是否匹配。
- 计时不准确:确认
sysctl_clock_get_freq返回的是CPU主频,若时钟源不同需调整分频系数。 - 中断不触发:确认全局中断已使能(
plic_init()已在SDK启动代码中调用),且定时器通道号与中断号对应正确。