精华内容
下载资源
问答
  • 一个定时器实现多个虚拟定时器具体代码实现
  • C++定时器实现

    2016-07-11 10:23:43
    C++定时器实现,简单易懂,高效
  • 定时器实现图片轮播

    2016-08-31 19:20:27
    定时器实现图片轮播
  • C 定时器实现

    2015-02-10 11:47:37
    linux下的基于rbtree、域套接字的(支持多线程)定时器实现源码,定时回调函数。
  • 主要介绍了javascript基于定时器实现进度条功能,简单分析了javascript定时器的功能、使用方法并给出了基于定时器实现的进度条功能实例,需要的朋友可以参考下
  • 主要介绍了Android 定时器实现图片的变换的相关资料,利用到定时器和handler,message的结合实现改功能,需要的朋友可以参考下
  • Android 闹铃定时器实现,注意:此demo在原生Android系统上没问题,但在国内大多数定制机上,如果关闭后台,闹铃一般不会触发 ╮(╯_╰)╭
  • 主要介绍了C#定时器实现自动执行的方法,实例分析了C#定时器参数的设置及方法的调用与实现,需要的朋友可以参考下
  • javascript定时器实现的蛇形文字,有点好笑的,使用任意浏览器打开就可以看到效果,最好别用IE,推荐使用谷歌,QQ,火狐浏览器
  • 主要介绍了Jmeter多种定时器实现方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • C语言海量定时器实现

    2014-01-23 11:22:33
    C语言海量定时器实现, 支持超过100W的定时器容量,速度很快
  • 主要为大家详细介绍了js定时器实现倒计时效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 主要为大家详细介绍了Qt基于定时器实现简单动图展示,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 本文介绍如何用STM32定时器实现PWM输出波形等定制波形,以STM32F334 Nucleo开发板验证,说明利用定时器的比较输出切换模式,结合DMA外设,可以灵活地输出各种自定义波形。
  • android定时器实现每天定时执行任务.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • 主要介绍了使用js函数定时器实现定时读取系统实时连接数,需要的朋友可以参考下
  • 主要介绍了微信小程序用定时器实现倒计时效果,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 8253定时器实现方波输出 课程设计 8253定时器实现方波输出 课程设计
  • STM32定时器实现跑马灯
  • FreeRTOS 软定时器实现

    万次阅读 多人点赞 2016-10-20 16:29:39
    定时器实现 数据结构 定时器控制块 定时器管理链表 命令队列 定时器服务任务 回调定时器 处理节拍计数器溢出 命令处理 参考 FreeRtos 简述考虑平台硬件定时器个数限制的, FreeRTOS 通过一个 Daemon 任务(启动调度...

    @(嵌入式)

    Freertos
    FreeRtos

    简述

    考虑平台硬件定时器个数限制的, FreeRTOS 通过一个 Daemon 任务(启动调度器时自动创建)管理软定时器, 满足用户定时需求. Daemon 任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。

    对于硬件定时器的中断服务程序, 我们知道不应该在里面执行复杂,可能导致阻塞的工作,相应的, 虽然软定时器实际是在定时Daemon 任务中执行,但是阻塞的话会导致其他定时器调用被延时, 所以实际使用也应该避免。

    软定时器是通过一个任务来辅助实现,该功能时刻裁剪的 , 只有设置 FreeRTOSConfig.hconfigUSE_TIMERS == 1 将相关代码编译进来, 才能正常使用相关功能。

    分析的源码版本是 v9.0.0

    使用定时器

    开始先介绍下如何在自己的工程中使用 FreeRTOS 的软件定时器。

    配置定时器服务任务

    程序中需要使用到软件定时器, 需要先在 FreeRTOSConfig.h 中正确配置如下宏 :
    * configUSE_TIMERS
    是否编译定时器相关代码, 如需要使用定时器, 设置为 1
    * configTIMER_TASK_PRIORITY
    设置定时器Daemon 任务优先级, 如果优先级太低, 可能导致定时器无法及时执行
    * configTIMER_QUEUE_LENGTH
    设置定时器Daemon 任务的命令队列深度, 设置定时器都是通过发送消息到该队列实现的。
    * configTIMER_TASK_STACK_DEPTH
    设置定时器Daemon 任务的栈大小

    创建 启动 停止定时器

    如下示例代码所示

    TimerHandle_t xTimerUser; // 定义句柄
    
    // 定时器回调函数格式
    void vTimerCallback( TimerHandle_t xTimer )
    {
        // do something no block
        // 获取溢出次数
        static uin32_t ulCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
        // 累积溢出次数
        ++ulCount; 
        // 更新溢出次数
        vTimerSetTimerID( xTimer, ( void * ) ulCount );
    
        if (ulCount == 10) {
            // 停止定时器
            xTimerStop( xTimer, 0 );
        }
    }
    
    void fun()
    {
        // 申请定时器, 配置
        xTimerUser = xTimerCreate
                       /*调试用, 系统不用*/
                       ("Timer's name",
                       /*定时溢出周期, 单位是任务节拍数*/
                       100,   
                       /*是否自动重载, 此处设置周期性执行*/
                       pdTRUE,
                       /*记录定时器溢出次数, 初始化零, 用户自己设置*/
                      ( void * ) 0,
                       /*回调函数*/
                      vTimerCallback);
    
         if( xTimerUser != NULL ) {
            // 启动定时器, 0 表示不阻塞
            xTimerStart( xTimerUser, 0 );
        }
    }

    如上所示, 调用函数 xTimerCreate申请,配置定时器, 通过 xTimerStart 启动定时器, 当定时器计数溢出时, 系统回调注册的函数。

    定时器可以设置为一次性 One-shot 或者自动重载 Auto-reload 两种, 第一种溢出后停止定时器, 第二种溢出后会再次启动定时器。

    定时器重复

    修改定时器

    在申请定时器的时候设置的定时器周期, 可以通过函数 xTimerChangePeriod 修改, 如下示例 :

     void vAFunction_2( TimerHandle_t xTimer )
     {
         // 判断定时器是否处于运行状态
         if( xTimerIsTimerActive( xTimer ) != pdFALSE )
         {
             /* xTimer is active, do something. */
         }
         else
         {
             // 处于这个状态的定时器, 可能由于 : 
             // 1 定时器 create 后没有start
             // 2 一次性定时器执行溢出后
    
             // 修改定时器周期
             if( xTimerChangePeriod( xTimer, 
                    /*修改定时周期*/
                    500 / portTICK_PERIOD_MS, 
                /*允许阻塞最大时间 100 ticks*/
                100 ) == pdPASS )
             {
                 // update fail
                 // 阻塞 100 tick 仍然无法发送命令
    
                 // 删除定时器 释放对应内存!
                 xTimerDelete( xTimer );
             }
             else 
             {
                 // 定时器配置更新成功, 并已经启动 !!
             }
        } 
     }

    如上, 该函数会修改定时器并使定时器 开始运行!!!

    另外, 可以通过函数 xTimerReset 重启定时器, 如果已经启动计数, 重新开始计数; 如果没有启动,启动定时器。

    定时器使用系统提供 API,涉及 Queue 操作, 如果是在中断程序中调用,需要调用对应带 FromISR的接口。

    获取定时器状态

    其他获取定时器信息的函数

    // 获取名称 , 申请是设置的字符串
    pcTimerGetName()
    // 定时器溢出周期
    xTimerGetPeriod()
    // 返回定时器溢出的时间点 (--> xTaskGetTickCount())
    xTimerGetExpiryTime()

    定时器实现

    FreeRTOS 软定时器的实现在源码目录 Source/include/timers.h, 涉及 链表 和 消息队列(后续文章分析)。

    数据结构

    使用定时器前,需要先申请定时器, 见 配置定时器服务任务 中, 通过函数 xTimerCreate获取一个定时器, 实际上是向系统申请了一块内存存储定时器控制块的数据结构, 并将参数填写到该结构体中。

    定时器控制块

    xTIMER

    typedef struct tmrTimerControl
    {
        // 定时器名 方便调试
        const char *pcTimerName;
        // 链表项 用于插入定时链表
        ListItem_t xTimerListItem;
        // 定时器中断周期
        TickType_t xTimerPeriodInTicks;
        // 是否自动重置, 如果 =pdFalse 为一次性
        UBaseType_t uxAutoReload;
        // 溢出计数 需自己设置
        void *pvTimerID;
        // 定时器溢出回调函数
        TimerCallbackFunction_t pxCallbackFunction;
        #if( configUSE_TRACE_FACILITY == 1 )
            UBaseType_t uxTimerNumber;
        #endif
        #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && 
            ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
            // 标记定时器使用的内存, 删除时判断是否需要释放内存
            uint8_t ucStaticallyAllocated;  #endif
    } xTIMER;

    成功申请定时器后, 定时器并没有开始工作, 需要调用函数将该定时器中的 xTimerListItem 插入到定时器管理链表中, Daemon 任务才能在该定时器设定的溢出时刻调用其回调函数。

    定时器管理链表

    timers.c 中定义了如下几个链表变量用于管理定时器, 定时器根据其溢出时刻从小到大插入链表进行管理。
    使用两个链表是为了应对系统 TickCount 溢出的问题,在 FreeRTOS 任务调度 系统节拍 介绍过。

    PRIVILEGED_DATA static List_t xActiveTimerList1;
    PRIVILEGED_DATA static List_t xActiveTimerList2;
    // 当前节拍计数器对应的定时器管理链表指针
    PRIVILEGED_DATA static List_t *pxCurrentTimerList;
    // 溢出时间到了下一个节拍计数阶段(当前节拍计数器溢出后)的定时器管理链表指针 
    PRIVILEGED_DATA static List_t *pxOverflowTimerList;

    命令队列

    文章开头提到的使用定时器的函数, 大部分都带有一个参数,用于设置调用后允许阻塞的最大时间, 原因是, 这些函数并没有直接操作定时器管理链表, 而是向定时器Daemon 任务的消息队列 xTimerQueue 发送消息命令。 之后, 定时器Daemon 任务会从消息队列取出消息并响应操作。

    定时器服务任务

    此处,从系统启动的定时器Daemon 任务展开分析 FreeRTOS 的软定时器的实现 。
    该任务主体的执行流程如下所示 :

    TimeLoop

    永久循环部分的代码 :

    for( ;; )
    {
        // 读取定时器队列第一个链表项的值 -> 即将溢出的定时器时间(ticks)
        // 如果链表空, 返回的是 0
        xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
    
        // 处理溢出的定时器
        // 阻塞直到下一个定时器溢出 或 消息队列有新命令
        prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
    
        // 读取消息队列,执行命令
        prvProcessReceivedCommands();
    }

    回调定时器

    定时器任务中, 取出下一个定时器溢出的时间,并把它传递给函数prvProcessTimerOrBlockTask, 该函数负责处理溢出定时器, 应对节拍计数器溢出问题等, 并设置合适的时间阻塞 Daemon 任务, 让出 CPU 使用权直到下一个定时器溢出或者接收到新的命令。

    static void prvProcessTimerOrBlockTask(
        const TickType_t xNextExpireTime,
        BaseType_t xListWasEmpty )
    {
        TickType_t xTimeNow;
        BaseType_t xTimerListsWereSwitched;
        // 挂起调度器 避免任务切换
        vTaskSuspendAll();
        {
            // 判断系统节拍计数是否溢出
            // 如果是,处理溢出定时器, 并切换定时器链表
            xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
    
            // 系统节拍计数器没有溢出
            if( xTimerListsWereSwitched == pdFALSE )
            {
                // 判断是否有定时器溢出
                if( ( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow ) )
                {
                    // 恢复调度
                    ( void ) xTaskResumeAll();
                    //执行相应定时器的回调函数
                    // 对于需要自动重载的定时器, 更新下一次溢出时间, 插回链表
                    prvProcessExpiredTimer( xNextExpireTime, xTimeNow );
                }
                else
                {
                    // 当前链表没有定时器
                    if( xListWasEmpty != pdFALSE )
                    {
                        // 判断溢出链表上是否有定时器
                        xListWasEmpty = listLIST_IS_EMPTY( pxOverflowTimerList );
                    }
                    // 阻塞挂起直到 : 下一个定时器溢出 或 新命令消息
                    // 下面这个queue函数是内核专用, 调用后不会直接阻塞,但是会把任务加入到阻塞链表中
                    vQueueWaitForMessageRestricted( xTimerQueue, 
                            ( xNextExpireTime - xTimeNow ), /*转换阻塞时间*/
                            xListWasEmpty );
    
                    if( xTaskResumeAll() == pdFALSE )
                    {
                        // 触发任务切换
                        portYIELD_WITHIN_API();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
            }
            else
            {
                // 恢复调度
                ( void ) xTaskResumeAll();
            }
        }
    }

    处理节拍计数器溢出

    上面提到, 通过函数 prvSampleTimeNow判断节拍计数器是否发发生溢出, 并执行相应处理, 此处看看该函数内容 :

    static TickType_t prvSampleTimeNow( BaseType_t * const pxTimerListsWereSwitched )
    {
        TickType_t xTimeNow;
        // 静态变量 记录上一次调用时系统节拍值
        PRIVILEGED_DATA static TickType_t xLastTime = ( TickType_t ) 0U;
        // 获取本次调用节拍结束器值
        xTimeNow = xTaskGetTickCount();
    
        // 判断节拍计数器是否溢出过
        // 比如 8bit : 0xFF+1 -> 0
        if( xTimeNow < xLastTime )
        {
            // 发生溢出, 处理当前链表上所有定时器并切换管理链表
            prvSwitchTimerLists();
            *pxTimerListsWereSwitched = pdTRUE;
        }
        else
        {
            *pxTimerListsWereSwitched = pdFALSE;
        }
        // 更新记录
        xLastTime = xTimeNow;
        return xTimeNow;
    }

    可以看到, 该函数每次调用都会记录节拍值, 下一次调用,通过比较相邻两次调用的值判断节拍计数器是否溢出过。
    当节拍计数器溢出, 需要处理掉当前链表上的定时器(应为这条链表上的定时器都已经溢出了), 然后切换链表。

    对于处理这部分任务的函数, 主要要注意其对于需要重载的定时器的处理 :

    类比一下 , 一个自动重载的定时器, 每月需要执行一次, 上次调用是2016 年6月, 之后由于优先级问题,导致下一次调用时间等到第二年2017年 1月了,也就是跨年了(节拍计数器溢出了), 切换日历(链表)前, 需要把旧的先处理掉, 那么实际该定时器在2016年 7~ 12月每月都需要执行一次,所以要补偿回来,直到第二年1月, 才发送消息,插到新日历里面(链表)。

    即使时间延迟了,但是该调用几次,是保证的!!

    static void prvSwitchTimerLists( void )
    {
        TickType_t xNextExpireTime, xReloadTime;
        List_t *pxTemp;
        Timer_t *pxTimer;
        BaseType_t xResult;
    
        // 切换链表前, 需要先处理当前链表上的所有执行定时器
        while( listLIST_IS_EMPTY( pxCurrentTimerList ) == pdFALSE )
        {
            // 获取第一个定时器溢出时间
            xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
            // 取出定时器并从链表移除
            pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );
            ( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
            traceTIMER_EXPIRED( pxTimer );
            // 执行定时器回调函数
            pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
    
            // 对于自动重载的定时器 计算下一次溢出时间
            if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
            {
                // 如果重载后定时器的时间没有溢出, 还在当前链表范围内, 继续插回到当前链表
                // 保证执行的次数
                xReloadTime = ( xNextExpireTime + pxTimer->xTimerPeriodInTicks );
                if( xReloadTime > xNextExpireTime )
                {
                    // 设置下一次溢出时间
                    listSET_LIST_ITEM_VALUE( &( pxTimer->xTimerListItem ), xReloadTime );
                    listSET_LIST_ITEM_OWNER( &( pxTimer->xTimerListItem ), pxTimer );
                    vListInsert( pxCurrentTimerList, &( pxTimer->xTimerListItem ) );
                }
                else
                {
                    // 重载后定时器的时间同节拍计数器一样溢出了
                    // 需要插入到新的链表中, 通过消息发送
                    // 等到处理消息时,链表已经切换了
                    xResult = xTimerGenericCommand( 
                        pxTimer, 
                        tmrCOMMAND_START_DONT_TRACE, 
                        xNextExpireTime, 
                        NULL, 
                        tmrNO_DELAY );
    
                    configASSERT( xResult );
                    ( void ) xResult;
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    
        // 切换链表
        pxTemp = pxCurrentTimerList;
        pxCurrentTimerList = pxOverflowTimerList;
        pxOverflowTimerList = pxTemp;
    }

    函数 prvProcessTimerOrBlockTask 中, 当节拍计数器没有溢出, 判断当前管理链表上溢出定时器并进行处理的函数 prvProcessExpiredTimer 整体和上面介绍差别不大, 执行函数回调, 判断是否需要重载等。

    命令处理

    用户将需要处理的定时器命令发送到定时器的消息队列, Daemon 任务每次执行期间回去读取并执行, 这部分工作有任务主体中的函数 prvProcessReceivedCommands完成, 下面看看这个函数如何实现, 对应平时使用定时器控制函数更加有底。
    以下代码做了简化

    static void prvProcessReceivedCommands( void )
    {
        DaemonTaskMessage_t xMessage;
        Timer_t *pxTimer;
        BaseType_t xTimerListsWereSwitched, xResult;
        TickType_t xTimeNow;
    
        while( xQueueReceive( xTimerQueue, &xMessage, tmrNO_DELAY ) != pdFAIL )
        {
            #if ( INCLUDE_xTimerPendFunctionCall == 1 )
            // 延期执行函数命令
            // 执行注册的函数  
            #endif
    
            // 定时器命令消息
            if( xMessage.xMessageID >= ( BaseType_t ) 0 )
            {
                // 命令处理的定时器
                pxTimer = xMessage.u.xTimerParameters.pxTimer;
    
                if( listIS_CONTAINED_WITHIN( NULL, &( pxTimer->xTimerListItem ) ) == pdFALSE )
                {
                    // 如果定时器已经在链表中, 不管37 21, 移除
                    ( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
                }
    
                // 判断节拍计数器是否溢出过 处理 切换
                // 因为下面可能有新项插入 确保链表对应 
                xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
    
                switch( xMessage.xMessageID )
                {
                    case tmrCOMMAND_START :
                    case tmrCOMMAND_START_FROM_ISR :
                    case tmrCOMMAND_RESET :
                    case tmrCOMMAND_RESET_FROM_ISR :
                    case tmrCOMMAND_START_DONT_TRACE :
                        // 以上 ,都是让定时器跑起来
                        // 设置定时器溢出时间并插到链表中
                        if( prvInsertTimerInActiveList( pxTimer,
                            xMessage.u.xTimerParameters.xMessageValue +
                            pxTimer->xTimerPeriodInTicks, xTimeNow,
                             xMessage.u.xTimerParameters.xMessageValue ) 
                             != pdFALSE )
                        {
                            // 处理定时器慢了, 该定时器已经溢出
                            // 赶紧执行其回调就看看函数
                            pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
                            // 重载定时器 重新启动
                            if( pxTimer->uxAutoReload 
                                == ( UBaseType_t ) pdTRUE )
                            {
                                xResult = xTimerGenericCommand( pxTimer,
                                     tmrCOMMAND_START_DONT_TRACE,
                                      xMessage.u.xTimerParameters.xMessageValue+pxTimer->xTimerPeriodInTicks,
                                      NULL, tmrNO_DELAY );
                                configASSERT( xResult );
                                ( void ) xResult;
                            }
                        }
                        break;
    
                    case tmrCOMMAND_STOP :
                    case tmrCOMMAND_STOP_FROM_ISR :
                        // 停止定时器 开头已经从链表移除
                        // 不需要做其他
                        break;
    
                    case tmrCOMMAND_CHANGE_PERIOD :
                    case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
                        // 更新定时器配置
                        pxTimer->xTimerPeriodInTicks =
                             xMessage.u.xTimerParameters.xMessageValue;
                        // 插入到管理链表 也就启动了定时器
                        ( void ) prvInsertTimerInActiveList( pxTimer,
                             ( xTimeNow + pxTimer->xTimerPeriodInTicks ),
                             xTimeNow, xTimeNow );
                        break;
                    case tmrCOMMAND_DELETE :
                        // 删除定时器
                        // 判断定时器内存是否需要释放(动态的释放)
                        break;
                    default :
                        /* Don't expect to get here. */
                        break;
                }
            }
        }
    }

    函数处理定时器,开头不管后面命令是什么,如果定时器原本在运行, 直接移除。


    参考

    展开全文
  • 主要介绍了JavaScript学习笔记之基于定时器实现图片无缝滚动功能,结合实例形式分析了javascript定时器与页面元素属性动态设置等相关操作技巧,需要的朋友可以参考下
  • QT学习示例,QT 5.12 定时器实现秒表演示程序源代码,利用QTimer类,通过信号与槽机制实现秒表功能,包含全部源代码和可执行程序。源代码有详细注释,程序很简单,主要是学习QTimer类和QTime类的使用,以及复习信号...
  • 本例子是为了实现使用Linux下的一个定时器实现任一数量的定时器功能。对linux使用一个定时器设置任意数量定时器实现代码感兴趣的朋友一起学习吧
  • C#定时器实现例子,包括三种实现方式System.Windows.Forms、System.Timers、System.Threading.Timer
  • 一种新的嵌入式Linux高性能定时器实现方法.pdf
  • MFC 通过定时器实现space键的暂停和开始 , 该方法原理能够实现MFC图片幻灯片暂停/开始播放.
  • Android 定时器实现的几种方式和removeCallbacks失效问题详解.pdf

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 202,419
精华内容 80,967
关键字:

定时器实现