rt-thread icon indicating copy to clipboard operation
rt-thread copied to clipboard

关于硬件定时器框架和驱动的问题

Open hg0720 opened this issue 2 years ago • 0 comments

在使用 imxrt1061的timer做验证时出现的问题,以下是测试代码:

static rt_err_t timer1_callback(rt_device_t dev, rt_size_t size)
{
    static rt_uint32_t count = 0;
    rt_hwtimerval_t timeout = {0};

    rt_device_read(dev, 0, &timeout, sizeof(timeout));
    rt_kprintf("this is timer1 callback read: sec = %d, usec = %d\n", timeout.sec, timeout.usec);

    count++;
    if(count >= 10)
    {
        rt_kprintf("timer1 end\n");
        rt_device_close(dev);
        count=0;
    }

    return RT_EOK;
}

static rt_err_t timer2_callback(rt_device_t dev, rt_size_t size)
{
    rt_hwtimerval_t timeout = {0};

    rt_device_read(dev, 0, &timeout, sizeof(timeout));
    rt_kprintf("this is timer2 callback read: sec = %d, usec = %d\n\n", timeout.sec, timeout.usec);

    return RT_EOK;
}

static void timer_sample(int argc, char *argv[])
{
    rt_device_t timer1_dev = RT_NULL;
    rt_device_t timer2_dev = RT_NULL;
    rt_hwtimer_mode_t mode = HWTIMER_MODE_ONESHOT;
    rt_hwtimerval_t timeout = {0};

    /* configure timer0 */
    timer1_dev = rt_device_find("gpt1");
    if (timer1_dev == RT_NULL)
    {
        rt_kprintf("timer1_dev can't find\n");
        return;
    }
    rt_device_open(timer1_dev, RT_DEVICE_FLAG_RDWR);
    rt_device_set_rx_indicate(timer1_dev, timer1_callback);
    rt_device_control(timer1_dev, HWTIMER_CTRL_MODE_SET, (void *)&mode);

    /* configure timer1 */
    timer2_dev = rt_device_find("gpt2");
    if (timer2_dev == RT_NULL)
    {
        rt_kprintf("timer2_dev can't find\n");
        return;
    }
    rt_device_open(timer2_dev, RT_DEVICE_FLAG_RDWR);
    rt_device_set_rx_indicate(timer2_dev, timer2_callback);
    mode = HWTIMER_MODE_ONESHOT;
    rt_device_control(timer2_dev, HWTIMER_CTRL_MODE_SET, (void *)&mode);

    /* start timer2 period:1s*/
    timeout.sec = 1;
    timeout.usec = 0;
    if(rt_device_write(timer2_dev, 0, &timeout, sizeof(timeout)) == 0)
    {
        rt_kprintf("timer2 fail\n");
        return;
    }

    /* start timer1 period:2s */
    timeout.sec = 5;
    timeout.usec = 0;
    if(rt_device_write(timer1_dev, 0, &timeout, sizeof(timeout)) == 0)
    {
        rt_kprintf("timer1 fail\n");
        return;
    }
}
MSH_CMD_EXPORT(timer_sample, timer_sample);

理论上打印值应该为:

msh />timer_sample
this is timer2 callback read: sec = 1, usec = 0

this is timer1 callback read: sec = 5, usec = 0

实际上打印值为:

msh />timer_sample
this is timer2 callback read: sec = 2, usec = 4

this is timer1 callback read: sec = 10, usec = 2

忽略误差不计,出现了获取到的值翻倍的情况,但是定时时间准确的情况;

经过调试和排查,发现是因为设置单次触发模式时,芯片定时器的重载值并不会像 stm32 一样自动重载,由此导致框架获取的定时器计数值出现错误,计算出翻倍的值,再往下探究发现框架中有如下处理:


void rt_device_hwtimer_isr(rt_hwtimer_t *timer)
{
    rt_base_t level;

    RT_ASSERT(timer != RT_NULL);

    level = rt_hw_interrupt_disable();

    timer->overflow ++;

    if (timer->cycles != 0)
    {
        timer->cycles --;
    }

    if (timer->cycles == 0)
    {
        timer->cycles = timer->reload;

        rt_hw_interrupt_enable(level);

        if (timer->mode == HWTIMER_MODE_ONESHOT)
        {
            if (timer->ops->stop != RT_NULL)
            {
                timer->ops->stop(timer);
            }
        }

        if (timer->parent.rx_indicate != RT_NULL)
        {
            timer->parent.rx_indicate(&timer->parent, sizeof(struct rt_hwtimerval));
        }
    }
    else
    {
        rt_hw_interrupt_enable(level);
    }
}

在中断处理中,会判断定时器触发模式,如果是单次触发则会直接停止定时器,由此可得,就算直接将定时器配置为周期触发,也不会影响单次触发的功能,于是对驱动做如下修改:

static rt_err_t imxrt_hwtimer_start(rt_hwtimer_t *timer, rt_uint32_t cnt, rt_hwtimer_mode_t mode)
{
    GPT_Type *hwtimer_dev;
    hwtimer_dev = (GPT_Type *)timer->parent.user_data;

    RT_ASSERT(timer != RT_NULL);

    /* 原处理 */
    // hwtimer_dev->CR |= (mode != HWTIMER_MODE_PERIOD) ? GPT_CR_FRR_MASK : 0U;

    /* 修改后的处理 */
    hwtimer_dev->CR |= 0U;

    GPT_SetOutputCompareValue(hwtimer_dev, kGPT_OutputCompare_Channel1, cnt);

    GPT_EnableInterrupts(hwtimer_dev, kGPT_OutputCompare1InterruptEnable);

    NVIC_Configuration();

    GPT_StartTimer(hwtimer_dev);

    return RT_EOK;
}

再次测试上述测试代码功能,打印如下:

msh />timer_sample
this is timer2 callback read: sec = 1, usec = 4

this is timer1 callback read: sec = 5, usec = 1

可证明猜测是正确的。

所以是否应该移除硬件定时器 ops

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);
};

中 start 的参数 mode,避免对驱动开发造成误导

hg0720 avatar Aug 23 '23 03:08 hg0720