Skip to content

概念先知道

  • 定时器(TIMER):芯片里的“秒表”,按设定频率计数,计到设定值(比较值)就触发一次中断。
  • 分频(clock_div):把高频时钟“减速”后再计数。例程用 40MHz 时钟分频 40(39+1)得到 1MHz,比较值 64000 → 每 64ms 触发一次。
  • 比较中断:计数器到达比较通道(comp0/1/2)的设定值时产生的中断,常用于周期任务、PWM 时基、计时。

例程功能简介

本页对应博流官方 SDK 的 timer_int 例程(examples/peripherals/timer/timer_int),演示硬件定时器的比较中断功能:

  • 定时器时钟 = XCLK /(分频 + 1),BL616/BL618 上 XCLK 为 40MHz,例程分频 39,得到 1MHz 计数时钟;
  • 比较值 comp0_val = 64000,即每计数 64000 次(约 64 毫秒)触发一次比较中断;
  • 中断回调中打印 timer0 comp0 trigger,同时主循环每 1.5 秒打印 hello 表示程序仍在运行。
  • 同族例程examples/peripherals/timer/):timer_capture(输入捕获)、timer_clksource_check(时钟源校验)、timer_gpio_clock(GPIO 时钟输出)。

操作步骤

1
进入例程目录

本页不需要额外接线,直接使用板载资源。在终端进入 SDK 的定时器例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/timer/timer_int
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 2000000),可以看到:串口大约每 64 毫秒打印一次 timer0 comp0 trigger(定时器比较中断),同时主循环每 1.5 秒打印一次 hello

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):

例程调用的 API 介绍

bflb_device_get_by_name("timer0")

获取定时器外设的设备句柄,本例使用 timer0

参数

  • name:外设名称字符串,如 "timer0"

返回值struct bflb_device_s * 设备句柄

bflb_timer_init(timer, config)

按配置初始化定时器。struct bflb_timer_config_s 中关键字段:

  • counter_mode:计数模式,TIMER_COUNTER_MODE_PROLOAD 表示匹配后自动重载
  • clock_source:时钟源,TIMER_CLKSRC_XTAL 使用晶振时钟
  • clock_div:分频值,计数时钟 = XCLK /(clock_div + 1),BL616/BL618 填 39
  • trigger_comp_id:触发比较通道,TIMER_COMP_ID_0
  • comp0_val/comp1_val/comp2_val:三个比较通道的匹配值,本列程只使用 comp0
  • preload_val:重载初值,0

参数

  • timer:定时器设备句柄
  • configstruct bflb_timer_config_s 配置结构体指针

返回值:成功返回 0;失败返回负值错误码

bflb_irq_attach(timer->irq_num, isr, NULL)

注册定时器中断回调函数,并把中断使能(bflb_irq_enable)。

参数

  • irq_num:定时器中断号,使用 timer->irq_num
  • isr:回调函数,形如 void isr(int irq, void *arg)

返回值:无

bflb_timer_start(timer)

启动定时器,开始计数。配置完成后调用本函数定时器才会运行。

参数

  • timer:定时器设备句柄

返回值:成功返回 0;失败返回负值错误码

bflb_timer_get_compint_status(timer, comp_id)

查询指定比较通道的中断状态,用于判断是哪个通道触发了中断。

参数

  • timer:定时器设备句柄
  • comp_id:比较通道,TIMER_COMP_ID_0 / TIMER_COMP_ID_1 / TIMER_COMP_ID_2

返回值true 该通道已触发;false 未触发

bflb_timer_compint_clear(timer, comp_id)

清除指定比较通道的中断标志,中断回调中必须调用,否则中断会反复触发。

参数

  • timer:定时器设备句柄
  • comp_id:比较通道号

返回值:无

完整代码

以下为 timer_int/main.c 完整源码,与官方示例(examples/peripherals/timer/timer_int)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:

📜 点击展开 timer_int/main.c 完整代码
c
#include "bflb_mtimer.h"
#include "bflb_timer.h"
#include "board.h"

#define TEST_TIMER_COMP_ID TIMER_COMP_ID_0

struct bflb_device_s *timer0;
struct bflb_device_s *timer1;

void timer0_isr(int irq, void *arg)
{
    bool status = bflb_timer_get_compint_status(timer0, TIMER_COMP_ID_0);
    if (status) {
        bflb_timer_compint_clear(timer0, TIMER_COMP_ID_0);
        printf("timer0 comp0 trigger\r\n");
    }
    status = bflb_timer_get_compint_status(timer0, TIMER_COMP_ID_1);
    if (status) {
        bflb_timer_compint_clear(timer0, TIMER_COMP_ID_1);
        printf("timer0 comp1 trigger\r\n");
    }
    status = bflb_timer_get_compint_status(timer0, TIMER_COMP_ID_2);
    if (status) {
        bflb_timer_compint_clear(timer0, TIMER_COMP_ID_2);
        printf("timer0 comp2 trigger\r\n");
    }
}

void timer1_isr(int irq, void *arg)
{
    bool status = bflb_timer_get_compint_status(timer1, TIMER_COMP_ID_0);
    if (status) {
        bflb_timer_compint_clear(timer1, TIMER_COMP_ID_0);
        printf("timer1 comp0 trigger\r\n");
    }
    status = bflb_timer_get_compint_status(timer1, TIMER_COMP_ID_1);
    if (status) {
        bflb_timer_compint_clear(timer1, TIMER_COMP_ID_1);
        printf("timer1 comp1 trigger\r\n");
    }
    status = bflb_timer_get_compint_status(timer1, TIMER_COMP_ID_2);
    if (status) {
        bflb_timer_compint_clear(timer1, TIMER_COMP_ID_2);
        printf("timer1 comp2 trigger\r\n");
    }
}

int main(void)
{
    board_init();
    printf("Timer basic test\n");

    /* timer clk = XCLK/(div + 1 )*/
    struct bflb_timer_config_s cfg0;
    cfg0.counter_mode = TIMER_COUNTER_MODE_PROLOAD; /* preload when match occur */
    cfg0.clock_source = TIMER_CLKSRC_XTAL;
    cfg0.clock_div = 39; /* for bl616/bl618dg/bl618dg is 39, for bl702 is 31 */
    cfg0.trigger_comp_id = TEST_TIMER_COMP_ID;
    cfg0.comp0_val = 64000; /* match value 0  */
    cfg0.comp1_val = 0xFFFFFFFF; /* match value 1 */
    cfg0.comp2_val = 0xFFFFFFFF; /* match value 2 */
    cfg0.preload_val = 0;    /* preload value */

    #if 0
    struct bflb_timer_config_s cfg1;
    cfg1.counter_mode = TIMER_COUNTER_MODE_PROLOAD;
    cfg1.clock_source = TIMER_CLKSRC_XTAL;
    cfg1.clock_div = 39; /* for bl616/bl618dg/bl618dg is 39, for bl702 is 31 */
    cfg1.trigger_comp_id = TEST_TIMER_COMP_ID;
    cfg1.comp0_val = 1000000; /* match value 0  */
    cfg1.comp1_val = 1500000; /* match value 1 */
    cfg1.comp2_val = 2500000; /* match value 2 */
    cfg1.preload_val = 0;    /* preload value */
    #endif

    timer0 = bflb_device_get_by_name("timer0");
    // timer1 = bflb_device_get_by_name("timer1");

    /* Timer init with default configuration */
    bflb_timer_init(timer0, &cfg0);
    // bflb_timer_init(timer1, &cfg1);

    bflb_irq_attach(timer0->irq_num, timer0_isr, NULL);
    // bflb_irq_attach(timer1->irq_num, timer1_isr, NULL);
    bflb_irq_enable(timer0->irq_num);
    // bflb_irq_enable(timer1->irq_num);

    /* Enable timer */
    bflb_timer_start(timer0);
    // bflb_timer_start(timer1);

    printf("case success.\r\n");
    while (1) {
        bflb_mtimer_delay_ms(1500);
        printf("hello\n\r");
    }
}

FAQ

打印的 timer0 comp0 trigger 频率不对

触发周期由分频与比较值决定:周期 =(clock_div + 1)× comp0_val / XCLK。例程为 40 × 64000 / 40MHz ≈ 64ms;修改 comp0_val 即可改变周期。

只打印 hello 没有 comp 触发信息

确认 bflb_timer_start(timer0) 已调用、中断已使能(bflb_irq_enable);如果修改过 clock_div,注意 BL616/BL618 上 XCLK 为 40MHz,不要照搬其他芯片的 32 分频示例。

中断里没有清除标志导致异常

在中断回调中,对每个触发的比较通道都要调用 bflb_timer_compint_clear 清除标志,否则会反复进入中断。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23