Edit online

设计说明

4 Dec 2024
Read time: 7 minute(s)

HRTimer 源代码位于 bsp/artinchip/

  • bsp/artinchip/drv/hrtimer/drv_hrtimer.c,HRTimer Driver 层实现

  • bsp/artinchip/include/drv/drv_hrtimer.h,HRTimer Driver 层接口

  • bsp/artinchip/hal/pwmcs/hal_cap.c,PWMCS CAP 模块的 HAL 层实现

  • bsp/artinchip/include/hal/hal_cap.h,PWMCS CAP 模块的 HAL 层接口头文件

软件架构

HRTimer 驱动 Driver 层采用 RT-Thread 的 hwtimer 设备驱动框架,如果只使用 HAL 层也可以支持 baremetal 方式的应用场景。


sw_system6

1. HRTimer 驱动的软件架构图

关键流程设计

初始化流程

HRTimer 驱动的初始化接口通过 INIT_DEVICE_EXPORT(drv_hwtimer_init) 完成,主要是通过调用 hwtimer 子系统的接口 rt_device_hwtimer_register() 注册一个 hwtimer 设备。

CAP 控制器的配置过程主要步骤如下:

  1. 初始化 PWMCS 模块的 clk,确保 PWMCS 模块的时钟已经正确配置并启用。

  2. 使能 CAP 指定通道的 clk,并设置为 PWM 计数模式,以便能够进行脉冲宽度调制(PWM)信号的捕获和计数。。

  3. 设置 CAP 的初始值 (cnt)。初始值决定了计数器从哪个数值开始计数。

  4. 使能 CAP 的中断功能,以便在特定事件发生时触发中断处理程序。。

  5. 启动 CAP 计数,使其开始对输入信号进行计数。。

数据结构设计

struct aic_cap_arg
属于 HAL 层接口,记录每一个 CAP 通道的配置信息:
struct aic_cap_arg {
    u16 available;
    u16 id;
    u32 freq;
    u32 prd;
    u32 irq_cnt;
    };
struct hrtimer_info
属于 Driver 层接口,记录一个 HRTimer 设备的配置信息:
struct hrtimer_info {
                char name[12];
                u32 id;
                rt_hwtimer_t hrtimer;
                };

Driver 层接口设计

struct rt_hwtimer_ops 接口是 hwtimer 设备驱动框架的标准接口,定义了一组操作函数,用于初始化、启动、停止、获取计数值和控制硬件定时器:
struct rt_hwtimer_ops
{
    void (*init)(struct rt_hwtimer_device *timer, rt_uint32_t state);
    rt_err_t (*start)(struct rt_hwtimer_device *timer, rt_uint32_t cnt, rt_hwtimer_mode_t mode);
    void (*stop)(struct rt_hwtimer_device *timer);
    rt_uint32_t (*count_get)(struct rt_hwtimer_device *timer);
    rt_err_t (*control)(struct rt_hwtimer_device *timer, rt_uint32_t cmd, void *args);
};
1. drv_hrtimer_init
函数原型 void drv_hrtimer_init(rt_hwtimer_t *timer, rt_uint32_t state)
功能说明 初始化配置一路 Timer
参数定义
timer - 指向 rt_hwtimer_t 设备的指针
state - 1,表示打开;0,表示关闭
返回值
注意事项 -
2. drv_hrtimer_start
函数原型 rt_err_t drv_hrtimer_start(rt_hwtimer_t *timer, rt_uint32_t cnt, rt_hwtimer_mode_t mode)
功能说明 启动 Timer
参数定义
timer - 指向 rt_hwtimer_t 设备的指针
cnt - Timer 的超时计数,单位是:1/Freq 秒
mode - Oneshot、或 Period 类型
返回值 0,成功;<0,失败
注意事项 -
3. drv_hrtimer_stop
函数原型 void drv_hrtimer_stop(rt_hwtimer_t *timer)
功能说明 停止 Timer
参数定义
timer - 指向 rt_hwtimer_t 设备的指针
返回值
注意事项 -
4. drv_hrtimer_ctrl
函数原型 rt_err_t drv_hrtimer_ctrl(rt_hwtimer_t *timer, rt_uint32_t cmd, void *args)
功能说明 HRTimer 驱动的 ioctl 接口
参数定义
timer - 指向 rt_hwtimer_t 设备的指针
cmd - ioctl 命令码
args - ioctl 命令相应的参数
返回值 0,成功;<0,失败
注意事项 目前仅支持设置 Timer 的 Freq 值

HAL 层接口设计

HAL 层的函数接口声明存放在 hal_cap.h 中。hal_cap.h 是一个头文件,其中声明了与硬件抽象层 (HAL) 相关的函数接口。这些接口主要用于初始化、配置和控制捕获/比较单元 (CAP),如下所示:
void hal_cap_ch_init(u32 ch);
void hal_cap_ch_deinit(u32 ch);

void hal_cap_int_enable(u32 ch, int enable);
u32 hal_cap_is_pending(u32 ch);
int hal_cap_set_freq(u32 ch, u32 freq);
int hal_cap_set_cnt(u32 ch, u32 cnt);
int hal_cap_get(u32 ch);
int hal_cap_enable(u32 ch);
int hal_cap_disable(u32 ch);
void hal_cap_cnt_start(u32 ch);
void hal_cap_cnt_stop(u32 ch);

int hal_cap_init(void);
int hal_cap_deinit(void);

void hal_cap_status_show(void);

Demo

以下代码示例展示了一个用于测试高精度定时器 (HRTIMER) 的 Demo,包括了定时器的回调函数和命令行接口,用于配置和启动定时器。本 Demo 仅截取 test_hrtimer 的部分源码,完整源码位于 bsp/examples/test-hrtimer/test_hrtimer.c
/* Timer timeout callback function */
static rt_err_t hrtimer_cb(rt_device_t dev, rt_size_t size)
{
    struct hrtimer_info *info = (struct hrtimer_info *)dev->user_data;

    if (g_debug)
        printf("%d/%d hrtimer%d timeout callback! Elapsed %ld us\n",
               g_loop_cnt, g_loop_max,
               info->id, aic_timer_get_us() - g_start_us);

    g_start_us = aic_timer_get_us();
    g_loop_cnt++;
    if ((g_loop_max > 1) && (g_loop_cnt > g_loop_max))
        rt_device_control(g_hrtimer_dev[info->id], HWTIMER_CTRL_STOP, NULL);

    return RT_EOK;
}

static void cmd_test_hrtimer(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    u32 c, ch = 0;
    rt_hwtimerval_t tm = {0};
    rt_hwtimer_mode_t mode = HWTIMER_MODE_ONESHOT;

    optind = 0;
    while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
        switch (c) {
        case 'm':
            if (strncasecmp("period", optarg, strlen(optarg)) == 0)
                mode = HWTIMER_MODE_PERIOD;
            continue;

        case 'c':
            ch = atoi(optarg);
            if (ch > AIC_HRTIMER_CH_NUM) {
                pr_err("Channel number %s is invalid\n", optarg);
                return;
            }
            continue;

        case 's':
            tm.sec = atoi(optarg);
            continue;

        case 'u':
            tm.usec = atoi(optarg);
            continue;

        case 'd':
            g_debug = 1;
            continue;

        case 'h':
            usage(argv[0]);
            return;

        default:
            pr_err("Invalid argument\n");
            usage(argv[0]);
            return;
        }
    }

    if ((tm.sec == 0) && (tm.usec == 0)) {
        pr_err("Invalid argument\n");
        usage(argv[0]);
        return;
    }

    if (!g_hrtimer_dev[ch]) {
        char name[10] = "";

        snprintf(name, 10, "hrtimer%d", ch);
        /* find timer device */
        g_hrtimer_dev[ch] = rt_device_find(name);
        if (g_hrtimer_dev[ch] == RT_NULL) {
            pr_err("Can't find %s device!\n", name);
            return;
        }

        /* Open the device in read-write mode */
        ret = rt_device_open(g_hrtimer_dev[ch], RT_DEVICE_OFLAG_RDWR);
        if (ret != RT_EOK) {
            pr_err("Failed to open %s device!\n", name);
            return;
        }
    }

    /* set timeout callback function */
    rt_device_set_rx_indicate(g_hrtimer_dev[ch], hrtimer_cb);

    ret = rt_device_control(g_hrtimer_dev[ch], HWTIMER_CTRL_MODE_SET, &mode);
    if (ret != RT_EOK) {
        pr_err("Failed to set mode! ret is %d\n", ret);
        return;
    }

    printf("hrtimer%d: Create a timer of %d.%06d sec, %s mode\n",
           ch, tm.sec, tm.usec,
           mode == HWTIMER_MODE_ONESHOT ? "Oneshot" : "Period");
    if (mode != HWTIMER_MODE_ONESHOT) {
        g_loop_max = HRTIMER_MAX_ELAPSE / (tm.sec * USEC_PER_SEC + tm.usec);
        printf("\tWill loop %d times\n", g_loop_max);
    }
    g_loop_cnt = 0;
    g_start_us = aic_timer_get_us();
    if (!rt_device_write(g_hrtimer_dev[ch], 0, &tm, sizeof(tm))) {
        pr_err("set timeout value failed\n");
        return;
    }

}
MSH_CMD_EXPORT_ALIAS(cmd_test_hrtimer, test_hrtimer, test hrtimer);