关于RTOS的全部真相。 第28条。 软件计时器

上一篇文章介绍了软件计时器的概念。 它们是内核对象,为任务提供了一种简单的方法来按时触发事件,或者通常是一种定期执行操作的方法。 先前的文章中讨论了Nucleus SE中与时间相关的功能(精度,中断处理等)的所有详细信息。




使用计时器


可以将程序计时器配置为触发一次,即启动,然后在指定的时间段后简单地结束循环。 或者可以将计时器配置为重新启动:完成计数后,计时器将自动重新启动。 重新启动后的运行时间可能与初始运行时间不同。 另外,计时器可以可选地配置为执行特殊的终止功能,该功能在计时器(或每次)完成工作周期时执行。

计时器设定


计时器数


与Nucleus SE的大多数方面一样,计时器设置由nuse_config.h中#define指令控制 。 主要参数是NUSE_TIMER_NUMBER ,它定义了在应用程序中配置的计时器。 默认情况下,此值为零(即,应用程序中未使用计时器),并且最多可采用16个值。不正确的值将导致编译错误,这将通过检入文件nuse_config_check.h生成 (此文件包含在nuse_config.c中并进行编译) ),这将触发#error指令。

选择一个非零值是主计时器激活器。 定义数据结构时使用此参数,其大小取决于其值。 此外,非零值会激活API设置。

激活完成功能


在Nucleus SE中,我试图找到机会使该功能成为可选功能,以节省内存。 一个很好的例子是对计时器完成功能的支持。 除了该功能对于每个计时器都是可选的事实之外,还可以使用nuse_config.h中的NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT参数为整个应用程序激活(或不激活)该机制。 将此参数设置为FALSE将阻止在ROM中定义两个数据结构,本文将对此进行详细描述。

API激活


Nucleus SE中的每个API函数(实用程序调用)在nuse_config.h中都有一个激活的#define指令。 对于计时器,这些符号包括:
NUSE_TIMER_CONTROL
NUSE_TIMER_GET_REMAINING
NUSE_TIMER_RESET
NUSE_TIMER_INFORMATION
NUSE_TIMER_COUNT

默认情况下,所有激活器均设置为FALSE ,因此所有服务调用均被禁用,从而阻止了包含实现它们的代码。 要在应用程序中设置计时器,您需要选择必要的API服务调用并将其设置为TRUE

以下是默认nuse_config.h文件中的代码片段。

#define NUSE_TIMER_NUMBER 0/*      0-16 */ /*    */ #define NUSE_TIMER_CONTROL FALSE #define NUSE_TIMER_GET_REMAINING FALSE #define NUSE_TIMER_RESET FALSE #define NUSE_TIMER_INFORMATION FALSE #define NUSE_TIMER_COUNT FALSE 

如果激活了与计时器有关的API函数,并且应用程序中没有配置的计时器( NUSE_Timer_Count()函数始终启用),则将发生编译错误。 如果您的代码使用尚未激活的API调用,则会发生布局错误,因为实现代码未包含在应用程序中。

定时服务电话


Nucleus RTOS支持八个与计时器相关的实用程序调用,这些调用提供以下功能:

  • 管理(启动/停止)计时器。 Nucleus SE在NUSE_Timer_Control()函数中实现。
  • 检索剩余的计时器时间。 在Nucleus SE中,在NUSE_Timer_Get_Remaining()中实现
  • 将计时器恢复到其原始状态(重置)。 Nucleus SE在NUSE_Timer_Reset()中实现
  • 提供有关特定计时器的信息。 Nucleus SE在NUSE_Timer_Information()中实现
  • 返回应用程序中已配置(当前)计时器的数量。 Nucleus SE在NUSE_Timer_Count()中实现
  • 向应用程序添加一个新计时器(创建)。 未实施Nucleus SE。
  • 从应用程序中删除计时器。 未实施Nucleus SE。
  • 返回指向应用程序中所有计时器的指针。 未实施Nucleus SE。

每个服务调用的实现将在下面详细讨论。

定时服务


计时器可以执行的基本操作是控制(启动和停止)以及读取当前值。 Nucleus RTOS和Nucleus SE为这些操作提供了两个基本的API实用程序调用。

计时器控制


通过对Nucleus RTOS API的实用程序调用来控制计时器,您可以激活和停用计时器(启动和停止)。 Nucleus SE提供了类似的功能。

Nucleus RTOS中的计时器控制挑战
服务电话原型:

状态NU_Control_Timer(NU_TIMER *计时器,OPTION启用);

参数:
timer-指向用户提供的计时器控制块的指针;
enable是必需的函数;它可以采用值NU_ENABLE_TIMERNU_DISABLE_TIMER

返回值:
NU_SUCCESS-调用成功完成;
NU_INAVLID_TIMER-无效的计时器指针;
NU_INAVLID_ENABLE-无效的函数。

Nucleus SE中的计时器控制挑战
该API调用支持Nucleus RTOS API的全部功能。

服务电话原型:
STATUS NUSE_Timer_Control(NUSE_TIMER计时器,OPTION启用);

参数:
timer-使用的计时器的索引(ID);
enable是必需的功能;它可以采用值NUSE_ENABLE_TIMERNUSE_DISABLE_TIMER

返回值:
NUSE_SUCCESS-呼叫已成功完成;
NUSE_INCALID_TIMER-无效的计时器索引;
NUSE_INVALID_ENABLE是无效的函数。

在Nucleus SE中实施计时器管理
API函数代码NUSE_Timer_Control() (检查参数后)非常简单:

 NUSE_CS_Enter(); if (enable == NUSE_ENABLE_TIMER) { NUSE_Timer_Status[timer] = TRUE; if (NUSE_Timer_Expirations_Counter[timer] == 0) { NUSE_Timer_Value[timer] = NUSE_Timer_Initial_Time[timer]; } else { NUSE_Timer_Value[timer] = NUSE_Timer_Reschedule_Time[timer]; } } else /* enable == NUSE_DISABLE_TIMER */ { NUSE_Timer_Status[timer] = FALSE; } NUSE_CS_Exit(); 

如果指定了NUSE_DISABLE_TIMER函数,则计时器状态( NUSE_Timer_Status []参数)设置为FALSE ,这将导致中断处理程序忽略计时器。

当您选择NUSE_ENABLE_TIMER函数时只要自上次复位以来从未停止过计时器计时器计数器( NUSE_Timer_Value [] )将设置为NUSE_Timer_initial_Time [] 。 否则,将为其分配值NUSE_Timer_Reschedule_Time [] 。 然后将计时器状态(参数NUSE_Timer_Status [] )设置为TRUE ,这将使计时器由中断处理程序处理。

计时器读取


要获取剩余的计时器时间,Nucleus RTOS API服务调用将返回小数,直到其到期为止。 Nucleus SE提供了类似的功能。

致电以获取Nucleus RTOS中的剩余时间

服务电话原型:
状态NU_Get_Remaining_Time(NU_TIMER *计时器,UNSIGNED *剩余时间);

参数:
timer-指向用户提供的计时器控制块的指针;
missing_time-指向剩余时间值的存储的指针,剩余时间值是UNSIGNED类型的变量。

返回值
NU_SUCCESS-调用成功完成;
NU_INVALID_TIMER-无效的计时器指针。

致电以获取Nucleus SE的剩余时间
该API调用支持Nucleus RTOS API的全部功能。

服务电话原型:
状态NUSE_Timer_Get_Remaining(NUSE_TIMER计时器,U16 *剩余时间);

参数:
timer-使用的计时器的索引(ID);
missing_time-指向剩余时间值的存储的指针,剩余时间值是U16类型的变量。

返回值:
NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_TIMER-无效的计时器索引;
NUSE_INVALID_POINTER-指向剩余时间的空指针( NULL )。

在Nucleus SE中实现计时器读取
API函数代码变体NUSE_Timer_Get_Remaining() (检查参数后)非常简单。 获得值NUSE_Timer_Value [] ,然后在关键部分返回。

辅助计时器服务


Nucleus RTOS有四个API调用,这些API提供与计时器相关的辅助功能:重置计时器,获取计时器信息,获取应用程序中计时器的数量以及获取指向应用程序中所有计时器的指针。 前三个功能在Nucleus SE中实现。

计时器重置


此API调用将计时器重置为其原始的未使用状态。 通话结束后,可以启动或关闭计时器。 仅在禁用计时器后才能使用它(使用NUSE_Timer_Control() )。 下次激活计时器时,将使用NUSE_Timer_Initial_Time []参数对其进行初始化。 Nucleus RTOS允许您提供新的初始状态和重新安排时间,以及在重置计时器时指定完成功能。 在Nucleus SE中,这些值是在设置过程中设置的,因为它们存储在ROM中,所以无法更改。

调用以重置Nucleus RTOS中的计时器

服务电话原型:
状态NU_Reset_Timer(NU_TIMER *计时器,VOID(* expiration_routine)(UNSIGNED),UNSIGNED initial_time,UNSIGNED reschedule_time,OPTION enable);

参数:
timer-指向可重置计时器的指针;
expiration_routine-指示循环结束时将要执行的功能;
initial_time-直到循环结束的计时器滴答声的初始数量;
reschedule_time-直到第二个及后续周期完成的计时器滴答数;
enable-重置后计时器的必需状态,可以采用NU_ENABLE_TIMERNU_DISABLE_TIMER值。

返回值:
NU_SUCCESS-调用成功完成;
NU_INVALID_TIMER-指向计时器控制单元的无效指针;
NU_INVALID_FUNCTION-指向完成函数的空指针( NULL );
NU_INVALID_ENABLE-指定的状态不正确;
NU_NOT_DISABLED-计时器已在运行(应在调用此函数之前将其停止)。

调用以重置Nucleus SE中的计时器
该API服务调用支持Nucleus RTOS API核心功能的简化版本。

服务电话原型:
STATUS NUSE_Timer_Reset(NUSE_TIMER计时器,OPTION启用);

参数:
timer-重置计时器的索引(ID);
enable-重置后的必需状态,可以采用值NUSE_ENABLE_TIMERNUSE_DISABLE_TIMER

返回值:
NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_TIMER-无效的计时器索引;
NUSE_INVALID_ENABLE-指定的状态不正确;
NUSE_NOT_DISABLED-计时器已在运行(应在调用此函数之前将其停止)。

在Nucleus SE中实现计时器重置
API函数代码NUSE_Timer_Reset()的版本 (在检查了参数和当前状态之后)非常简单:

 NUSE_CS_Enter(); NUSE_Init_Timer(timer); if (enable == NUSE_ENABLE_TIMER) { NUSE_Timer_Status[timer] = TRUE; } /*  enable == NUSE_DISABLE_TIMER    FALSE */ NUSE_CS_Exit(); 

调用NUSE_Init_Timer()会初始化时间值并清除完成计数器。 然后,如有必要,检查所需状态的值以及计时器是否打开。

计时器信息


通过此服务呼叫,您可以获得一组计时器信息。 Nucleus SE的实现与Nucleus RTOS的不同之处在于,由于不支持对象命名,因此它返回的信息较少。

在Nucleus RTOS中调用计时器信息

服务电话原型:
状态NU_Timer_Information(NU_TIMER *计时器,CHAR *名称,OPTION *启用,UNSIGNED *到期,UNSIGNED * ID,UNSIGNED * initial_time,UNSIGNED * reschedule_time);

参数:
计时器 -指向计时器的指针,有关该计时器的信息;
name-指向定时器名称的8个字符区域的指针;
enable-指向采用计时器激活器当前状态的变量的指针: NU_ENABLE_TIMERNU_DISABLE_TIMER ;
expirations-指向变量的指针,该变量获取自上次复位以来计时器周期完成次数的计数器;
id-指向一个变量的指针,该变量采用传递给计时器周期结束函数的参数的值;
initial_time-指向变量的指针,该变量带有一个值,该值将在重置后将计时器初始化为该值;
reschedule_time-指向一个变量的指针,该变量采用一个值,计时器在完成后将初始化为该值。

返回值:
NU_SUCCESS-调用成功完成;
NU_INVALID_TIMER-无效的计时器指针。

在Nucleus SE中调用计时器信息
该API调用支持Nucleus RTOS API的核心功能。

服务电话原型:
状态NUSE_Timer_Information(NUSE_TIMER计时器,OPTION *启用,U8 *到期,U8 * id,U16 * initial_time,U16 * reschedule_time);

参数:
timer-计时器的索引,有关该计时器的信息请求;
enable-指向一个变量的指针,该变量的值为TRUEFALSE ,取决于计时器是否被激活;
到期时间 -指向类型为U8的变量的指针,该变量采用自上次复位以来定时器完成的次数的值;
id-指向U8类型变量的指针,该变量采用传递给计时器完成功能的参数的值(如果禁用了完成功能,则将返回空值);
initial_time-指向U16类型变量的指针,该变量具有一个值,该值将在重置后初始化计时器;
reschedule_time-指向类型为U16的变量的指针,该变量采用值,定时器将在完成后初始化该值。

返回值:
NUSE_SUCCESS-呼叫已成功完成;
NUSE_INVALID_TIMER-无效的计时器索引;
NUSE_INVALID_POINTER-一个或多个指针参数不正确。

在Nucleus SE中实现计时器信息
实现此API调用非常简单:

 NUSE_CS_Enter(); if (NUSE_Timer_Status[timer]) { *enable = NUSE_ENABLE_TIMER; } else { *enable = NUSE_DISABLE_TIMER; } *expirations = NUSE_Timer_Expirations_Counter[timer]; #if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT *id = NUSE_Timer_Expiration_Routine_Parameter[timer]; #endif *initial_time = NUSE_Timer_Initial_Time[timer]; *reschedule_time = NUSE_Timer_Reschedule_Time[timer]; NUSE_CS_Exit(); 

该函数返回计时器的状态。 仅当在应用程序中激活了它们的支持时,才返回终止函数的参数值。

获取计时器数量


该实用程序调用返回在应用程序中配置的计时器数。 在Nucleus RTOS中,该值可能会随时间变化,并且返回值将显示当前计时器数。 在Nucleus SE中,返回值是在组装阶段设置的,无法更改。

在Nucleus RTOS中调用计时器计数器

服务电话原型:
UNSIGNED NU_Established_Timers(VOID);

参数:无

返回值:在系统中创建的计时器数。

在Nucleus SE中调用计时器
该API调用支持Nucleus RTOS API的核心功能。

服务电话原型:
U8 NUSE_Timer_Count(无效);

参数:无

返回值:
应用程序中已配置计时器的数量

计时器计数器实现


此API调用的实现非常简单: 返回 #define NUSE_TIMER_NUMBER符号的值。

资料结构


计时器使用五个或七个数据结构(位于RAM或ROM中)(与其他Nucleus SE对象一样)是一组表,其大小和数量与已配置的计时器和所选参数的数量相对应。

我强烈建议应用程序代码不要直接访问这些数据结构,而是通过提供的API函数引用它们。 这将避免与Nucleus SE的未来版本不兼容以及不必要的副作用,并简化将应用程序移植到Nucleus RTOS的过程。 以下是对结构的详细概述,以简化对服务调用和调试代码的理解。

RAM数据


该数据具有以下结构:
NUSE_Timer_Status []U8类型的数组,每个配置的计时器都有一个条目,并存储计时器的状态(运行或停止: TRUEFALSE )。
NUSE_Timer_Value []U16类型的数组,每个配置的计时器都有一个条目,并存储计时器计数器的当前值。
NUSE_Timer_Expirations_Counter [] -类型为U8的数组,其中包含一个计数器,该计数器记录了自上次复位以来定时器到达周期末尾的情况。

Nucleus SE启动时,所有这些数据结构都由NUSE_Init_Timer()函数初始化。 以下文章之一将完整描述Nucleus SE的启动过程。

以下是nuse_init.c文件中这些数据结构的定义:
RAM U8 Timer_Status [NUSE_TIMER_NUMBER];
RAM U16 NUSE_Timer_Value [NUSE_TIMER_NUMBER];
RAM U8 NUSE_Timer_Expirations_Counter [NUSE_TIMER_NUMBER];

ROM数据


该数据的结构:
NUSE_Timer_Initial_Time []U16类型的数组,每个配置的计时器都有一个条目,并存储每个计时器的值。
NUSE_Timer_Reschedule_Time []-U16类型的数组,每个配置的计时器都有一个条目,并存储在完成后将在其中设置计时器的值。 零值表示计时器为“一次性”,并且不应自动重启。
NUSE_Timer_Expiration_Routine_Address []-ADDR类型的数组,包含计时器到期过程的地址。 仅当激活对计时器到期过程的支持时,此数组才存在。
NUSE_Timer_Expiration_Routine_Parameter []-U8类型的数组,其中包含传递给计时器完成功能的参数的值。 仅当激活了对完成功能的支持时,此数组才存在。

这些数据结构在nuse_config.c文件中声明和初始化(静态),因此:

 ROM U16 NUSE_Timer_Initial_Time[NUSE_TIMER_NUMBER] = { /*   ------ */ }; ROM U16 NUSE_Timer_Reschedule_Time[NUSE_TIMER_NUMBER] = { /*      ------ */ }; #if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT || NUSE_INCLUDE_EVERYTHING /*    */ ROM ADDR NUSE_Timer_Expiration_Routine_Address[NUSE_TIMER_NUMBER] = { /*     ------ */ /*   NULL */ }; ROM U8 NUSE_Timer_Expiration_Routine_Parameter[NUSE_TIMER_NUMBER] = { /*     ------ */ }; #endif 

计时器的内存量


像所有其他Nucleus SE对象一样,计时器所需的数据量是可以预测的。

应用程序中所有计时器的RAM中的数据量(以字节为单位)可以计算如下:
NUSE_TIMER_NUMBER * 4

如果禁用了对完成功能的支持,则可以计算应用程序中所有计时器的ROM中的数据量(以字节为单位),如下所示:
NUSE_TIMER_NUMBER * 4

否则等于:
NUSE_TIMER_NUMBER *(sizeof(ADDR)+ 5)

未实现的API调用


Nucleus SE并未实现RTOS中可以找到的三个API调用。

计时器创建


此API调用创建一个计时器。 Nucleus SE不需要它,因为计时器是静态创建的。

服务电话原型:
状态NU_Create_Timer(NU_TIMER *计时器,CHAR *名称,VOID(* expiration_routine)(UNSIGNED),UNSIGNED ID,UNSIGNED initial_time,UNSIGNED reschedule_time,OPTION enable);

参数:
timer-指向用户提供的计时器控制块的指针; 它将用于控制其他API调用中的计时器;
name-指向计时器的7个字符的名称的指针,以0结尾;
expiration_routine-指示计时器结束后应执行的功能;
id-传递给终止函数的UNSIGNED类型的数据元素:此参数可用于标识具有相同终止函数的计时器;
initial_time-表示计时器结束之前计时器滴答的初始数量;
reschedule_time-指示直到第二个及后续周期完成为止的计时器滴答数; 如果此参数等于零,则计时器仅停止一次;
enable-此参数可以采用值NU_ENABLE_TIMERNU_DISABLE_TIMER ; NU_ENABLE_TIMER在创建计时器后激活它; NU_DISABLE_TIMER禁用计时器; 使用NU_DISABLE_TIMER参数创建的计时器必须通过调用NU_Control_Timer来激活。

返回值:
NU_SUCCESS-调用成功完成;
NU_INVALID_TIMER-指向计时器控制单元( NULL )的空指针,或者该控制单元已在使用中;
NU_INVALID_FUNCTION-指向完成程序的空指针( NULL );
NU_INVALID_ENABLE-无效的启用参数;
NU_INVALID_OPERATION - initial_time参数为零。

删除计时器


此API调用将删除以前创建的计时器。 Nucleus SE不需要它,因为计时器是静态创建的,无法删除。

服务电话原型:
状态NU_Delete_Timer(NU_TIMER *计时器);

参数:
timer-指向计时器控制块的指针。

返回值:
NU_SUCCESS-调用成功完成;
NU_INVALID_TIMER-无效的计时器指针;
NU_NOT_DISABLED-未禁用指定的计时器。

计时器指针


该API调用形成了指向系统中所有计时器的指针的顺序列表。 Nucleus SE不需要它,因为计时器是由简单的索引而不是指针确定的。

服务电话原型:
UNSIGNED NU_Timer_Pointers(NU_TIMER **指针列表,UNSIGNED maximum_pointers);

参数:
pointer_list-指针数组NU_TIMER的指针; 它将充满指向系统中配置的计时器的指针;
maximum_pointers-数组中的最大指针数。

返回值:
放置在数组中的NU_TIMER指针的数量。

兼容Nucleus RTOS


与所有其他Nucleus SE对象一样,我的目标是最大程度地提高与Nucleus RTOS的应用程序代码兼容性。 定时器也不例外,从用户的角度来看,它们的实现方式与Nucleus RTOS中的实现方式相同。由于存在一定的不兼容性,因此我认为这是可以接受的,因为这样一来,就所需的内存量而言,代码将变得更加易于理解和效率更高。否则,Nucleus RTOS API调用几乎可以直接移植到Nucleus SE。

对象标识符


在Nucleus RTOS中,所有对象均由数据结构(具有特定数据类型的控制块)描述。指向该控制单元的指针是计时器标识符。我决定在Nucleus SE中,需要一种不同的方法来有效地使用内存:所有内核对象都由RAM和/或ROM中的一组表描述。这些表的大小由每种类型的已配置对象的数量确定。特定对象的标识符是此表中的索引。所以我定义了NUSE_TIMER等于U8,此类型的变量(不是指针)用作计时器的标识符。如果将代码从Nucleus SE移植到Nucleus RTOS,反之亦然,则这种轻微的不兼容性很容易处理。通常,除了移动和存储之外,不对对象标识符执行任何操作。
Nucleus RTOS还支持命名计时器。这些名称仅用于调试。我从Nucleus SE中排除了它们以节省内存。

计时器大小


在Nucleus RTOS中,使用32位计数器实现计时器。我决定在Nucleus SE中将此值减小为16位。这导致了内存效率和运行时间的显着改善。如果应用程序需要更长的运行时间,则可以修改Nucleus SE。

完成功能


Nucleus SE以类似于Nucleus RTOS的方式实现终止功能,只有它们可以完全关闭(这可以节省内存),并且它们也是静态确定的。重置计时器时无法更改结束功能。

未实现的API调用


Nucleus RTOS支持八个计时器服务调用。其中,三个在Nucleus SE中未实现。这些调用的详细说明以及做出此决定的原因,可以在本文前面的“未实现的API调用”部分中找到。

下面的文章将检查中断。

Source: https://habr.com/ru/post/zh-CN455072/


All Articles