打印
[开发工具]

看看与rt_timer的成员以及相关函数

[复制链接]
1017|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
elephant00|  楼主 | 2023-5-6 09:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
rt_timer实际就是RTT中实现的一个软定时器。MCU底层做的多的人一般对硬件定时器都很熟悉,软件定时器反而不太熟悉。这里稍微介绍下软件定时器。

回忆一下硬件定时器,设置一个通用的硬件定时器,步骤如下:

配置定时器时钟。

初始化定时寄存器,前两步就是为了确定超时时间。

写定时器中断函数,确定时间到了之后执行的代码。

再之后就是开启或者关闭定时器了。

软件定时器其实也一样,只是这些全部由程序来执行,软件定时器的时钟节拍由软件周期性的产生,在RTOS中一般由心跳提供。通过软定时器初始化函数来设置超时时间以及超时回调函数(对应硬件定时器的中断函数)。

在RTT中,软件定时器又被分为纯软定时器以及普通定时器。纯软定时器中,定时器是由一个专门的线程来执行超时回调,该线程默认优先级为0(既最高)。普通定时器的时钟节拍由心跳中断来提供,超时回调也在硬件ISR中执行,心跳中断一般是一个硬件定时器的中断。

使用特权

评论回复
沙发
elephant00|  楼主 | 2023-5-6 09:55 | 只看该作者
开启
#define RT_USING_TIMER_SOF

这个宏可以开启纯软定时器功能。

为了知道RTT如何实现这两种模式的软件定时器,先来看看rt_timer类
```struct rt_timer
{
struct rt_object parent; /*< inherit from rt_object /

rt_list_t        row[RT_TIMER_SKIP_LIST_LEVEL];
void (*timeout_func)(void *parameter);              /**< timeout function */
void            *parameter;                         /**< timeout function\'s parameter */
rt_tick_t        init_tick;                         /**< timer timeout tick */
rt_tick_t        timeout_tick;                      /**< timeout tick */
};
typedef struct rt_timer *rt_timer_t;```

rt_timer的成员比较少,

1.parent,是object类的一个实例,说明rt_timer也继承自rt_object。

2.row[RT_TIMER_SKIP_LIST_LEVEL],一个双向链表结构,用来把rt_timer加入各种链表中。RT_TIMER_SKIP_LIST_LEVEL被定义为1,暂时不清楚RT_TIMER_SKIP_LIST_LEVEL这个变量的作用。可能是为日后可以将timer加入更多的链表。

3.timeout_func,超时回调函数指针,指向定时器超时后执行的回调函数。

4.parameter,回调函数的参数指针,指向回调函数执行时输入的参数。

5.init_tick,超时周期。超时时间=超时周期*每个周期的时间

6.timeout_tick,超时剩余周期。每个周期,该变量会被减1,直到到达0,就会触发timeout回调函数。

只有6个成员,看来成员是真的少啊。接下来,还是一样,继续看看与rt_timer相关的函数。可以看见,rt_timer相关函数有很多都与rt_thread相同,如init,creat,delete,detach,

control等函数。

使用特权

评论回复
板凳
elephant00|  楼主 | 2023-5-6 09:56 | 只看该作者
```void rt_system_tick_init(void);
rt_tick_t rt_tick_get(void);
void rt_tick_set(rt_tick_t tick);
void rt_tick_increase(void);
int rt_tick_from_millisecond(rt_int32_t ms);

void rt_system_timer_init(void);
void rt_system_timer_thread_init(void);
rt_tick_t rt_timer_next_timeout_tick(void);
void rt_timer_check(void);

void rt_timer_init(rt_timer_t timer,
const char name,
void (timeout)(void parameter),
void parameter,
rt_tick_t time,
rt_uint8_t flag);
rt_err_t rt_timer_detach(rt_timer_t timer);
rt_timer_t rt_timer_create(const char name,
void (timeout)(void parameter),
void parameter,
rt_tick_t time,
rt_uint8_t flag);
rt_err_t rt_timer_delete(rt_timer_t timer);
rt_err_t rt_timer_start(rt_timer_t timer);
rt_err_t rt_timer_stop(rt_timer_t timer);
rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg);```

一、rt_timer_init()和rt_timer_create()

还是一个规律,rt_timer_init是针对静态的rt_timer进行初始化,rt_timer_create是动态创建rt_timer,并进行初始化。
```rt_timer_t rt_timer_create(const char name,
void (timeout)(void parameter),
void parameter,
rt_tick_t time,
rt_uint8_t flag)
{
struct rt_timer *timer;

/* allocate a object */
timer = (struct rt_timer *)rt_object_allocate(RT_Object_Class_Timer, name);
if (timer == RT_NULL)
{
    return RT_NULL;
}
_rt_timer_init(timer, timeout, parameter, time, flag);
return timer;
}```

两个函数最后都调用了_rt_timer_init()这个实际的初始化函数

使用特权

评论回复
地板
elephant00|  楼主 | 2023-5-6 09:56 | 只看该作者
这个函数做了以下一些操作:

1.设置flag,去掉RT_TIMER_FLAG_ACTIVATED的标志,rt_timer使用了基类object中的flag成员作为自己的标志位。主要有以下几种标志:
```#define RT_TIMER_FLAG_DEACTIVATED 0x0 /*< timer is deactive /

define RT_TIMER_FLAG_ACTIVATED 0x1 /*< timer is active /
define RT_TIMER_FLAG_ONE_SHOT 0x0 /*< one shot timer /
define RT_TIMER_FLAG_PERIODIC 0x2 /*< periodic timer /
define RT_TIMER_FLAG_HARD_TIMER 0x0 /*< hard timer,the timer’s callback function will be called in tick isr. /
define RT_TIMER_FLAG_SOFT_TIMER 0x4 /*< soft timer,the timer’s callback function will be called in timer thread. /```
可以看到有3个标志都是0x0,这点有点怪。

2.配置time_out回调函数,以及parameter参数

3.设置超时周期init_tick,并把timeout_tick置为0

4.初始化双向链表row
```static void _rt_timer_init(rt_timer_t timer,
void (timeout)(void parameter),
void *parameter,
rt_tick_t time,
rt_uint8_t flag)
{
int i;

/* set flag */
timer->parent.flag  = flag;
/* set deactivated */
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
timer->timeout_func = timeout;
timer->parameter    = parameter;
timer->timeout_tick = 0;
timer->init_tick    = time;
/* initialize timer list */
for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
{
    rt_list_init(&(timer->row*));
}
}```

二、rt_timer_detach()和rt_timer_delete()

同样的,这两个函数都是对rt_timer进行删除,rt_timer_detach()针对的是静态的。

主要执行以下操作:

1.参数检查

2.执行_rt_timer_remove()将rt_timer移除相应的timer队列。(ISR或软timer线程将不再执行它)

3.删除rt_timer(释放分配的内存,移出对象容器)
```rt_err_t rt_timer_delete(rt_timer_t timer)
{
register rt_base_t level;

/* timer check */
RT_ASSERT(timer != RT_NULL);
RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
RT_ASSERT(rt_object_is_systemobject(&timer->parent) == RT_FALSE);
/* disable interrupt */
level = rt_hw_interrupt_disable();
_rt_timer_remove(timer);
/* enable interrupt */
rt_hw_interrupt_enable(level);
rt_object_delete((rt_object_t)timer);
return RT_EOK;
}```

三、rt_timer_start()和rt_timer_stop()

从名字就可以看出这两个函数一个是启动定时器一个是停止定时器。先来看启动的代码,代码挺长的主要完成以下一些步骤:

1.参数检查

2.重置rt_timer(将timer移出当前队列,RT_TIMER_FLAG_ACTIVATED标志移除。

3.调用rt_object_take_hook钩子函数

4.设置timeout_tick。

5.将rt_timer加入到对应的定时器列表中(纯软定时器加入纯软队列,普通定时器加入普通队列)

这里详细解析下下面这段代码。

rt_list_entry是一个神奇的宏定义,这个宏定义我也看了很久,写的很复杂。。关于宏定义提一下,RTT中具有很多很神奇的宏定义,写的十分精彩,大大减小了代码的复杂度,可以说是很优雅了。
#define rt_container_of(ptr, type, member) \\ ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))

总的来说rt_list_entry会通过成员地址返回包含该成员的实例的地址。一般使用方式是,ptr为类中rt_list_t成员的地址,type为类名,member为成员名

这段代码搜寻timer_list这个列表中的所有timer,当
else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)

时停止,后面在这个位置,把当前timer加入列表。这条语句看似是一个相减小与 RT_TICK_MAX / 2的条件,其实真实的含义是t->timeout_tick - timer->timeout_tick>0,这里是利用的32位整型的表示方式,RT_TICK_MAX 为0xffffffff,在MCU中对于有符号的整型,最高为都是符号位,当两个数相减的时候实际是把减数按位取反+1再相加,所以其实只有一种情况t->timeout_tick - timer->timeout_tick>RT_TICK_MAX /2会成立,那就是为负数的时候。

综上,这个for循环,其实是在rt_timer列表中搜寻到了刚好超时时间大于启动的rt_timer的那个索引,作为之后rt_timer插入列表的位置。所以整个rt_timer的列表(软timer一个列表,普通timer一个列表),都是按照timeout_tick 这个参数的值进行排序的,是一个有序列表。
``` row_head[0] = &timer_list[0];
for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
{
for (; row_head[row_lvl] != timer_list[row_lvl].prev;
row_head[row_lvl] = row_head[row_lvl]->next)
{
struct rt_timer t;
rt_list_t p = row_head[row_lvl]->next;

        /* fix up the entry pointer */
        t = rt_list_entry(p, struct rt_timer, row[row_lvl]);
        /* If we have two timers that timeout at the same time, it\'s
         * preferred that the timer inserted early get called early.
         * So insert the new timer to the end the the some-timeout timer
         * list.
         */
        if ((t->timeout_tick - timer->timeout_tick) == 0)
        {
            continue;
        }
        else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
        {
            break;
        }
    }
    if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
        row_head[row_lvl + 1] = row_head[row_lvl] + 1;
}```
至于之后函数,功能其实是把一个完整的rt_timer列表拆分成几份,这样做的作用我也还没有看出来。。。

使用特权

评论回复
5
elephant00|  楼主 | 2023-5-6 09:56 | 只看该作者
6.若启动的定时器为纯软的定时器,那么检测定时器线程是否启动,若未启动则重新启动线程。
```rt_err_t rt_timer_start(rt_timer_t timer)
{
unsigned int row_lvl;
rt_list_t timer_list;
register rt_base_t level;
rt_list_t row_head[RT_TIMER_SKIP_LIST_LEVEL];
unsigned int tst_nr;
static unsigned int random_nr;

/* timer check */
RT_ASSERT(timer != RT_NULL);
RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
/* stop timer firstly */
level = rt_hw_interrupt_disable();
/* remove timer from list */
_rt_timer_remove(timer);
/* change status of timer */
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
rt_hw_interrupt_enable(level);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));
/*
* get timeout tick,
* the max timeout tick shall not great than RT_TICK_MAX/2
*/
RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
timer->timeout_tick = rt_tick_get() + timer->init_tick;
/* disable interrupt */
level = rt_hw_interrupt_disable();
ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
    /* insert timer to soft timer list */
    timer_list = rt_soft_timer_list;
}
else
endif
{
    /* insert timer to system timer list */
    timer_list = rt_timer_list;
}
row_head[0]  = &timer_list[0];
for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
{
    for (; row_head[row_lvl] != timer_list[row_lvl].prev;
         row_head[row_lvl]  = row_head[row_lvl]->next)
    {
        struct rt_timer *t;
        rt_list_t *p = row_head[row_lvl]->next;
        /* fix up the entry pointer */
        t = rt_list_entry(p, struct rt_timer, row[row_lvl]);
        /* If we have two timers that timeout at the same time, it\'s
         * preferred that the timer inserted early get called early.
         * So insert the new timer to the end the the some-timeout timer
         * list.
         */
        if ((t->timeout_tick - timer->timeout_tick) == 0)
        {
            continue;
        }
        else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
        {
            break;
        }
    }
    if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
        row_head[row_lvl + 1] = row_head[row_lvl] + 1;
}
/* Interestingly, this super simple timer insert counter works very very
* well on distributing the list height uniformly. By means of \"very very
* well\", I mean it beats the randomness of timer->timeout_tick very easily
* (actually, the timeout_tick is not random and easy to be attacked). */
random_nr++;
tst_nr = random_nr;
rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],
                     &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
{
    if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))
        rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],
                             &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl]));
    else
        break;
    /* Shift over the bits we have tested. Works well with 1 bit and 2
     * bits. */
    tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
}
timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;
/* enable interrupt */
rt_hw_interrupt_enable(level);
ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
    /* check whether timer thread is ready */
    if ((timer_thread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_READY)
    {
        /* resume timer thread to check soft timer */
        rt_thread_resume(&timer_thread);
        rt_schedule();
    }
}
endif
return RT_EOK;
}```

stop函数就相对来说简单了很多,主要就是:

1.参数检查

2.调用rt_object_put_hook钩子函数

3.将rt_timer移出列表。
```rt_err_t rt_timer_stop(rt_timer_t timer)
{
register rt_base_t level;

/* timer check */
RT_ASSERT(timer != RT_NULL);
RT_ASSERT(rt_object_get_type(&timer->parent) == RT_Object_Class_Timer);
if (!(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED))
    return -RT_ERROR;
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(timer->parent)));
/* disable interrupt */
level = rt_hw_interrupt_disable();
_rt_timer_remove(timer);
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* change stat */
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
return RT_EOK;
}```

四、rt_timer_control()函数

改函数可以通过参数中的cmd来实现不同的功能,cmd总共有4中取值:
```#define RT_TIMER_CTRL_SET_TIME 0x0 /*< set timer control command /

define RT_TIMER_CTRL_GET_TIME 0x1 /*< get timer control command /
define RT_TIMER_CTRL_SET_ONESHOT 0x2 /*< change timer to one shot /
define RT_TIMER_CTRL_SET_PERIODIC 0x3 /*< change timer to periodic /```
分别对应:

设置定时器时间

获取定时器时间

设置定时器为单触发模式

设置定时器为周期性触发模式

当rt_timer被标记为周期性定时器后,当time_out回调触发后会执行start重新启动该定时器。

使用特权

评论回复
6
elephant00|  楼主 | 2023-5-6 09:56 | 只看该作者
五、其他函数。

剩下的函数还有
(1)void rt_system_tick_init(void); (2)rt_tick_t rt_tick_get(void); (3)void rt_tick_set(rt_tick_t tick); (4)void rt_tick_increase(void); (5)int rt_tick_from_millisecond(rt_int32_t ms); (6)void rt_system_timer_init(void); (7)void rt_system_timer_thread_init(void); (8)rt_tick_t rt_timer_next_timeout_tick(void); (9)void rt_timer_check(void);

(1)是个空函数。

(2)返回系统tick数,这个数在系统启动后就随着心跳不断的增加。

(3)设置系统tick数

(4)
```void rt_tick_increase(void)
{
struct rt_thread *thread;

/* increase the global tick */
++ rt_tick;
/* check time slice */
thread = rt_thread_self();
-- thread->remaining_tick;
if (thread->remaining_tick == 0)
{
    /* change to initialized tick */
    thread->remaining_tick = thread->init_tick;
    /* yield */
    rt_thread_yield();
}
/* check timer */
rt_timer_check();
}```

函数会对rt_tick加1,并检查线程时间片时间是否过期,若过期,则调用rt_thread_yield()。这个函数在上一篇分析过。

之后会执行(9)rt_timer_check()函数

(5)是个工具函数,将ms数换算成tick数

(6)初始化普通定时器列表

(7)若开启了纯软定时器,启动定时器线程,纯软定时器的time_out回调将在这个线程中执行,该线程默认优先级为0

(8)返回下一个最近会到期的普通定时器的time_out值。

(9)
```void rt_timer_check(void)
{
struct rt_timer *t;
rt_tick_t current_tick;
register rt_base_t level;

RT_DEBUG_LOG(RT_DEBUG_TIMER, (\"timer check enter
\"));
current_tick = rt_tick_get();
/* disable interrupt */
level = rt_hw_interrupt_disable();
while (!rt_list_isempty(&rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
{
    t = rt_list_entry(rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
                      struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);
    /*
     * It supposes that the new tick shall less than the half duration of
     * tick max.
     */
    if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
    {
        RT_OBJECT_HOOK_CALL(rt_timer_timeout_hook, (t));
        /* remove timer from timer list firstly */
        _rt_timer_remove(t);
        /* call timeout function */
        t->timeout_func(t->parameter);
        /* re-get tick */
        current_tick = rt_tick_get();
        RT_DEBUG_LOG(RT_DEBUG_TIMER, (\"current tick: %d
\", current_tick));
        if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
            (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
        {
            /* start it */
            t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            rt_timer_start(t);
        }
        else
        {
            /* stop timer */
            t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
        }
    }
    else
        break;
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
RT_DEBUG_LOG(RT_DEBUG_TIMER, (\"timer check leave
\"));
}```

该函数在ISR中执行,检测是否有普通定时器到期,对于到期的定时器先执行time_out回调,并判断是否是周期定时器,若是周期定时器,重新启动这个定时器。

那么对于rt_timer的解读就这么多,篇幅感觉越来越长呀。。

使用特权

评论回复
7
Pulitzer| | 2024-3-22 07:14 | 只看该作者

输入电源电流环路

使用特权

评论回复
8
童雨竹| | 2024-3-22 09:10 | 只看该作者

单片机一般都有内部程序区和数据区

使用特权

评论回复
9
Wordsworth| | 2024-3-22 10:13 | 只看该作者

输入和输出电流环路连接的位置只能是相应的输入 输出电容的接线端

使用特权

评论回复
10
公羊子丹| | 2024-3-22 12:09 | 只看该作者

CPLD解密,DSP解密都习惯称为单片机解密

使用特权

评论回复
11
万图| | 2024-3-22 13:12 | 只看该作者

引线越长,它能接收和传送的干扰信号频率就越低

使用特权

评论回复
12
Uriah| | 2024-3-22 14:15 | 只看该作者

输入电压端上测得的值比它实际

使用特权

评论回复
13
帛灿灿| | 2024-3-22 16:11 | 只看该作者

引线的长和宽影响它的电阻和电感量

使用特权

评论回复
14
Bblythe| | 2024-3-22 17:14 | 只看该作者

它们的放置要尽可能靠近

使用特权

评论回复
15
周半梅| | 2024-3-22 19:10 | 只看该作者

大部分单片机都带有加密锁定位或者加密字节

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

988

主题

3089

帖子

7

粉丝