6 Software Timer Management
6.1 Chapter Introduction and Scope
Software timers are used to schedule the execution of a function at a set time in the future, or periodically with a fixed frequency. The function executed by the software timer is called the software timer's callback function.
Software timers are implemented by, and are under the control of, the FreeRTOS kernel. They do not require hardware support, and are not related to hardware timers or hardware counters.
Note that, in line with the FreeRTOS philosophy of using innovative design to ensure maximum efficiency, software timers do not use any processing time unless a software timer callback function is actually executing.
Software timer functionality is optional. To include software timer functionality:
Build the FreeRTOS source file FreeRTOS/Source/timers.c as part of your project.
Set
configUSE_TIMERS
to 1 in FreeRTOSConfig.h.
6.1.1 Scope
This chapter covers:
The characteristics of a software timer compared to the characteristics of a task.
The RTOS daemon task.
The timer command queue.
The difference between a one shot software timer and a periodic software timer.
How to create, start, reset and change the period of a software timer.
6.2 Software Timer Callback Functions
Software timer callback functions are implemented as C functions. The only thing special about them is their prototype, which must return void, and take a handle to a software timer as its only parameter. The callback function prototype is demonstrated by Listing 75.
Listing 75. The software timer callback function prototype
Software timer callback functions execute from start to finish, and exit in the normal way. They should be kept short, and must not enter the Blocked state.
Note: As will be seen, software timer callback functions execute in the context of a task that is created automatically when the FreeRTOS scheduler is started. Therefore, it is essential that software timer callback functions never call FreeRTOS API functions that will result in the calling task entering the Blocked state. It is ok to call functions such as xQueueReceive()
, but only if the function's xTicksToWait
parameter (which specifies the function's block time) is set to 0. It is not ok to call functions such as vTaskDelay()
, as calling vTaskDelay()
will always place the calling task into the Blocked state.
6.3 Attributes and States of a Software Timer
6.3.1 Period of a Software Timer
A software timer's 'period' is the time between the software timer being started, and the software timer's callback function executing.
6.3.2 One-shot and Auto-reload Timers
There are two types of software timer:
One-shot timers
Once started, a one-shot timer will execute its callback function once only. A one-shot timer can be restarted manually, but will not restart itself.
Auto-reload timers
Once started, an auto-reload timer will re-start itself each time it expires, resulting in periodic execution of its callback function.
Figure 38 shows the difference in behavior between a one-shot timer and an auto-reload timer. The dashed vertical lines mark the times at which a tick interrupt occurs.
Referring to Figure 38:
Timer 1
Timer 1 is a one-shot timer that has a period of 6 ticks. It is started at time t1, so its callback function executes 6 ticks later, at time t7. As timer 1 is a one-shot timer, its callback function does not execute again.
Timer 2
Timer 2 is an auto-reload timer that has a period of 5 ticks. It is started at time t1, so its callback function executes every 5 ticks after time t1. In Figure 38 this is at times t6, t11 and t16.
6.3.3 Software Timer States
A software timer can be in one of the following two states:
Dormant
A Dormant software timer exists, and can be referenced by its handle, but is not running, so its callback functions will not execute.
Running
A Running software timer will execute its callback function after a time equal to its period has elapsed since the software timer entered the Running state, or since the software timer was last reset.
Figure 39 and Figure 40 show the possible transitions between the Dormant and Running states for an auto-reload timer and a one-shot timer respectively. The key difference between the two diagrams is the state entered after the timer has expired; the auto-reload timer executes its callback function then re-enters the Running state, the one-shot timer executes its callback function then enters the Dormant state.
The xTimerDelete()
API function deletes a timer. A timer can be deleted at any time.
6.4 The Context of a Software Timer
6.4.1 The RTOS Daemon (Timer Service) Task
All software timer callback functions execute in the context of the same RTOS daemon (or 'timer service') task10.
(10): The task used to be called the 'timer service task', because originally it was only used to execute software timer callback functions. Now the same task is used for other purposes too, so it is known by the more generic name of the 'RTOS daemon task'.
The daemon task is a standard FreeRTOS task that is created automatically when the scheduler is started. Its priority and stack size are set by the configTIMER_TASK_PRIORITY
and configTIMER_TASK_STACK_DEPTH
compile time configuration constants respectively. Both constants are defined within FreeRTOSConfig.h.
Software timer callback functions must not call FreeRTOS API functions that will result in the calling task entering the Blocked state, as to do so will result in the daemon task entering the Blocked state.
6.4.2 The Timer Command Queue
Software timer API functions send commands from the calling task to the daemon task on a queue called the 'timer command queue'. This is shown in Figure 41. Examples of commands include 'start a timer', 'stop a timer' and 'reset a timer'.
The timer command queue is a standard FreeRTOS queue that is created automatically when the scheduler is started. The length of the timer command queue is set by the configTIMER_QUEUE_LENGTH
compile time configuration constant in FreeRTOSConfig.h.
6.4.3 Daemon Task Scheduling
The daemon task is scheduled like any other FreeRTOS task; it will only process commands, or execute timer callback functions, when it is the highest priority task that is able to run. Figure 42 and Figure 43 demonstrate how the configTIMER_TASK_PRIORITY
setting affects the execution pattern.
Figure 42 shows the execution pattern when the priority of the daemon task is below the priority of a task that calls the xTimerStart()
API function.
Referring to Figure 42, in which the priority of Task 1 is higher than the priority of the daemon task, and the priority of the daemon task is higher than the priority of the Idle task:
At time t1
Task 1 is in the Running state, and the daemon task is in the Blocked state.
The daemon task will leave the Blocked state if a command is sent to the timer command queue, in which case it will process the command, or if a software timer expires, in which case it will execute the software timer's callback function.
At time t2
Task 1 calls
xTimerStart()
.xTimerStart()
sends a command to the timer command queue, causing the daemon task to leave the Blocked state. The priority of Task 1 is higher than the priority of the daemon task, so the daemon task does not pre-empt Task 1.Task 1 is still in the Running state, and the daemon task has left the Blocked state and entered the Ready state.
At time t3
Task 1 completes executing the
xTimerStart()
API function. Task 1 executedxTimerStart()
from the start of the function to the end of the function, without leaving the Running state.At time t4
Task 1 calls an API function that results in it entering the Blocked state. The daemon task is now the highest priority task in the Ready state, so the scheduler selects the daemon task as the task to enter the Running state. The daemon task then starts to process the command sent to the timer command queue by Task 1.
Note: The time at which the software timer being started will expire is calculated from the time the 'start a timer' command was sent to the timer command queue—it is not calculated from the time the daemon task received the 'start a timer' command from the timer command queue.
At time t5
The daemon task has completed processing the command sent to it by Task 1, and attempts to receive more data from the timer command queue. The timer command queue is empty, so the daemon task re-enters the Blocked state. The daemon task will leave the Blocked state again if a command is sent to the timer command queue, or if a software timer expires.
The Idle task is now the highest priority task in the Ready state, so the scheduler selects the Idle task as the task to enter the Running state.
Figure 43 shows a similar scenario to that shown by Figure 42, but this time the priority of the daemon task is above the priority of the task that calls xTimerStart()
.
Referring to Figure 43, in which the priority of the daemon task is higher than the priority of Task 1, and the priority of the Task 1 is higher than the priority of the Idle task:
At time t1
As before, Task 1 is in the Running state, and the daemon task is in the Blocked state.
At time t2
Task 1 calls
xTimerStart()
.xTimerStart()
sends a command to the timer command queue, causing the daemon task to leave the Blocked state. The priority of the daemon task is higher than the priority of Task 1, so the scheduler selects the daemon task as the task to enter the Running state.Task 1 was pre-empted by the daemon task before it had completed executing the
xTimerStart()
function, and is now in the Ready state.The daemon task starts to process the command sent to the timer command queue by Task 1.
At time t3
The daemon task has completed processing the command sent to it by Task 1, and attempts to receive more data from the timer command queue. The timer command queue is empty, so the daemon task re-enters the Blocked state.
Task 1 is now the highest priority task in the Ready state, so the scheduler selects Task 1 as the task to enter the Running state.
At time t4
Task 1 was pre-empted by the daemon task before it had completed executing the
xTimerStart()
function, and only exits (returns from)xTimerStart()
after it has re-entered the Running state.At time t5
Task 1 calls an API function that results in it entering the Blocked state. The Idle task is now the highest priority task in the Ready state, so the scheduler selects the Idle task as the task to enter the Running state.
In the scenario shown by Figure 42, time passed between Task 1 sending a command to the timer command queue, and the daemon task receiving and processing the command. In the scenario shown by Figure 43, the daemon task had received and processed the command sent to it by Task 1 before Task 1 returned from the function that sent the command.
Commands sent to the timer command queue contain a time stamp. The time stamp is used to account for any time that passes between a command being sent by an application task, and the same command being processed by the daemon task. For example, if a 'start a timer' command is sent to start a timer that has a period of 10 ticks, the time stamp is used to ensure the timer being started expires 10 ticks after the command was sent, not 10 ticks after the command was processed by the daemon task.
6.5 Creating and Starting a Software Timer
6.5.1 The xTimerCreate() API Function
FreeRTOS V9.0.0 also includes the xTimerCreateStatic()
function, which allocates the memory required to create a timer statically at compile time: A software timer must be explicitly created before it can be used.
Software timers are referenced by variables of type TimerHandle_t
. xTimerCreate()
is used to create a software timer and returns a TimerHandle_t
to reference the software timer it creates. Software timers are created in the Dormant state.
Software timers can be created before the scheduler is running, or from a task after the scheduler has been started.
Section 0 describes the data types and naming conventions used.
Listing 76. The xTimerCreate() API function prototype
xTimerCreate() parameters and return value
pcTimerName
A descriptive name for the timer. This is not used by FreeRTOS in any way. It is included purely as a debugging aid. Identifying a timer by a human readable name is much simpler than attempting to identify it by its handle.
xTimerPeriodInTicks
The timer's period specified in ticks. The
pdMS_TO_TICKS()
macro can be used to convert a time specified in milliseconds into a time specified in ticks. Cannot be 0.uxAutoReload
Set
uxAutoReload
topdTRUE
to create an auto-reload timer. SetuxAutoReload
topdFALSE
to create a one-shot timer.pvTimerID
Each software timer has an ID value. The ID is a void pointer, and can be used by the application writer for any purpose. The ID is particularly useful when the same callback function is used by more than one software timer, as it can be used to provide timer specific storage. Use of a timer's ID is demonstrated in an example within this chapter.
pvTimerID
sets an initial value for the ID of the task being created.pxCallbackFunction
Software timer callback functions are simply C functions that conform to the prototype shown in Listing 75. The
pxCallbackFunction
parameter is a pointer to the function (in effect, just the function name) to use as the callback function for the software timer being created.Return value
If NULL is returned, then the software timer cannot be created because there is insufficient heap memory available for FreeRTOS to allocate the necessary data structure.
A non-NULL value being returned indicates that the software timer has been created successfully. The returned value is the handle of the created timer.
Chapter 3 provides more information on heap memory management.
6.5.2 The xTimerStart() API Function
xTimerStart()
is used to start a software timer that is in the Dormant state, or reset (re-start) a software timer that is in the Running state. xTimerStop()
is used to stop a software timer that is in the Running state. Stopping a software timer is the same as transitioning the timer into the Dormant state.
xTimerStart()
can be called before the scheduler is started, but when this is done, the software timer will not actually start until the time at which the scheduler starts.
Note: Never call xTimerStart()
from an interrupt service routine. The interrupt-safe version xTimerStartFromISR()
should be used in its place.
Listing 77. The xTimerStart() API function prototype
xTimerStart() parameters and return value
xTimer
The handle of the software timer being started or reset. The handle will have been returned from the call to
xTimerCreate()
used to create the software timer.xTicksToWait
xTimerStart()
uses the timer command queue to send the 'start a timer' command to the daemon task.xTicksToWait
specifies the maximum amount of time the calling task should remain in the Blocked state to wait for space to become available on the timer command queue, should the queue already be full.xTimerStart()
will return immediately ifxTicksToWait
is zero and the timer command queue is already full.The block time is specified in tick periods, so the absolute time it represents is dependent on the tick frequency. The macro
pdMS_TO_TICKS()
can be used to convert a time specified in milliseconds into a time specified in ticks.If
INCLUDE_vTaskSuspend
is set to 1 inFreeRTOSConfig.h
then settingxTicksToWait
toportMAX_DELAY
will result in the calling task remaining in the Blocked state indefinitely (without a timeout) to wait for space to become available in the timer command queue.If
xTimerStart()
is called before the scheduler has been started then the value ofxTicksToWait
is ignored, andxTimerStart()
behaves as ifxTicksToWait
had been set to zero.Return value
There are two possible return values:
pdPASS
pdPASS
will be returned only if the 'start a timer' command was successfully sent to the timer command queue.If the priority of the daemon task is above the priority of the task that called
xTimerStart()
, then the scheduler will ensure the start command is processed beforexTimerStart()
returns. This is because the daemon task will pre-empt the task that calledxTimerStart()
as soon as there is data in the timer command queue.If a block time was specified (
xTicksToWait
was not zero), then it is possible the calling task was placed into the Blocked state to wait for space to become available in the timer command queue before the function returned, but data was successfully written to the timer command queue before the block time expired.pdFALSE
pdFALSE
will be returned if the 'start a timer' command could not be written to the timer command queue because the queue was already full.If a block time was specified (
xTicksToWait
was not zero) then the calling task will have been placed into the Blocked state to wait for the daemon task to make room in the timer command queue, but the specified block time expired before that happened.
6.5.3 Example 13. Creating one-shot and auto-reload timers
This example creates and starts a one-shot timer and an auto-reload timer—as shown in Listing 78.
Listing 78. Creating and starting the timers used in Example 13
The timers' callback functions just print a message each time they are called. The implementation of the one-shot timer callback function is shown in Listing 79. The implementation of the auto-reload timer callback function is shown in Listing 80.
Listing 79. The callback function used by the one-shot timer in Example 13
Listing 80. The callback function used by the auto-reload timer in Example 13
Executing this example produces the output shown in Figure 44. Figure 44 shows the auto-reload timer's callback function executing with a fixed period of 500 ticks (mainAUTO_RELOAD_TIMER_PERIOD
is set to 500 in Listing 78), and the one-shot timer's callback function executing only once, when the tick count is 3333 (mainONE_SHOT_TIMER_PERIOD
is set to 3333 in Listing 78).
6.6 The Timer ID
Each software timer has an ID, which is a tag value that can be used by the application writer for any purpose. The ID is stored in a void pointer (void *
), so can store an integer value directly, point to any other object, or be used as a function pointer.
An initial value is assigned to the ID when the software timer is created—after which the ID can be updated using the vTimerSetTimerID()
API function, and queried using the pvTimerGetTimerID()
API function.
Unlike other software timer API functions, vTimerSetTimerID()
and pvTimerGetTimerID()
access the software timer directly—they do not send a command to the timer command queue.
6.6.1 The vTimerSetTimerID() API Function
Listing 81. The vTimerSetTimerID() API function prototype
vTimerSetTimerID() parameters
xTimer
The handle of the software timer being updated with a new ID value. The handle will have been returned from the call to
xTimerCreate()
used to create the software timer.pvNewID
The value to which the software timer's ID will be set.
6.6.2 The pvTimerGetTimerID() API Function
Listing 82. The pvTimerGetTimerID() API function prototype
pvTimerGetTimerID() parameters and return value
xTimer
The handle of the software timer being queried. The handle will have been returned from the call to
xTimerCreate()
used to create the software timer.Return value
The ID of the software timer being queried.
6.6.3 Example 14. Using the callback function parameter and the software timer ID
The same callback function can be assigned to more than one software timer. When that is done, the callback function parameter is used to determine which software timer expired.
Example 13 used two separate callback functions; one callback function was used by the one-shot timer, and the other callback function was used by the auto-reload timer. Example 14 creates similar functionality to that created by Example 13, but assigns a single callback function to both software timers.
The main()
function used by Example 14 is almost identical to the main()
function used in Example 13. The only difference is where the software timers are created. This difference is shown in Listing 83, where prvTimerCallback()
is used as the callback function for both timers.
Listing 83. Creating the timers used in Example 14
prvTimerCallback()
will execute when either timer expires. The implementation of prvTimerCallback()
uses the function's parameter to determine if it was called because the one-shot timer expired, or because the auto-reload timer expired.
prvTimerCallback()
also demonstrates how to use the software timer ID as timer specific storage; each software timer keeps a count of the number of times it has expired in its own ID, and the auto-reload timer uses the count to stop itself the fifth time it executes.
The implementation of prvTimerCallback()
is shown in Listing 82.
Listing 84. The timer callback function used in Example 14
The output produced by Example 14 is shown in Figure 45. It can be seen that the auto-reload timer only executes five times.
6.7 Changing the Period of a Timer
Every official FreeRTOS port is provided with one or more example projects. Most example projects are self-checking, and an LED is used to give visual feedback of the project's status; if the self-checks have always passed then the LED is toggled slowly, if a self-check has ever failed then the LED is toggled quickly.
Some example projects perform the self-checks in a task, and use the vTaskDelay()
function to control the rate at which the LED toggles. Other example projects perform the self-checks in a software timer callback function, and use the timer's period to control the rate at which the LED toggles.
6.7.1 The xTimerChangePeriod() API Function
The period of a software timer is changed using the xTimerChangePeriod()
function.
If xTimerChangePeriod()
is used to change the period of a timer that is already running, then the timer will use the new period value to recalculate its expiry time. The recalculated expiry time is relative to when xTimerChangePeriod()
was called, not relative to when the timer was originally started.
If xTimerChangePeriod()
is used to change the period of a timer that is in the Dormant state (a timer that is not running), then the timer will calculate an expiry time, and transition to the Running state (the timer will start running).
Note: Never call xTimerChangePeriod()
from an interrupt service routine. The interrupt-safe version xTimerChangePeriodFromISR()
should be used in its place.
Listing 85. The xTimerChangePeriod() API function prototype
xTimerChangePeriod() parameters and return value
xTimer
The handle of the software timer being updated with a new period value. The handle will have been returned from the call to
xTimerCreate()
used to create the software timer.xTimerPeriodInTicks
The new period for the software timer, specified in ticks. The
pdMS_TO_TICKS()
macro can be used to convert a time specified in milliseconds into a time specified in ticks.xTicksToWait
xTimerChangePeriod()
uses the timer command queue to send the 'change period' command to the daemon task. xTicksToWait specifies the maximum amount of time the calling task should remain in the Blocked state to wait for space to become available on the timer command queue, should the queue already be full.xTimerChangePeriod()
will return immediately if xTicksToWait is zero and the timer command queue is already full.The macro
pdMS_TO_TICKS()
can be used to convert a time specified in milliseconds into a time specified in ticks.If
INCLUDE_vTaskSuspend
is set to 1 in FreeRTOSConfig.h, then settingxTicksToWait
toportMAX_DELAY
will result in the calling task remaining in the Blocked state indefinitely (without a timeout) to wait for space to become available in the timer command queue.If
xTimerChangePeriod()
is called before the scheduler has been started, then the value ofxTicksToWait
is ignored, andxTimerChangePeriod()
behaves as ifxTicksToWait
had been set to zero.Returned value
There are two possible return values:
pdPASS
pdPASS
will be returned only if data was successfully sent to the timer command queue.If a block time was specified (
xTicksToWait
was not zero), then it is possible the calling task was placed into the Blocked state to wait for space to become available in the timer command queue before the function returned, but data was successfully written to the timer command queue before the block time expired.pdFALSE
pdFALSE
will be returned if the 'change period' command could not be written to the timer command queue because the queue was already full.If a block time was specified (
xTicksToWait
was not zero) then the calling task will have been placed into the Blocked state to wait for the daemon task to make room in the queue, but the specified block time expired before that happened.
Listing 86 shows how the FreeRTOS examples that include self-checking functionality in a software timer callback function use xTimerChangePeriod()
to increase the rate at which an LED toggles if a self-check fails. The software timer that performs the self-checks is referred to as the 'check timer'.
Listing 86. Using xTimerChangePeriod()
6.8 Resetting a Software Timer
Resetting a software timer means to re-start the timer; the timer's expiry time is recalculated to be relative to when the timer was reset, rather than when the timer was originally started. This is demonstrated by Figure 46, which shows a timer that has a period of 6 being started, then reset twice, before eventually expiring and executing its callback function.
Referring to Figure 46:
Timer 1 is started at time t1. It has a period of 6, so the time at which it will execute its callback function is originally calculated to be t7, which is 6 ticks after it was started.
Timer 1 is reset before time t7 is reached, so before it had expired and executed its callback function. Timer 1 is reset at time t5, so the time at which it will execute its callback function is re-calculated to be t11, which is 6 ticks after it was reset.
Timer 1 is reset again before time t11, so again before it had expired and executed its callback function. Timer 1 is reset at time t9, so the time at which it will execute its callback function is re-calculated to be t15, which is 6 ticks after it was last reset.
Timer 1 is not reset again, so it expires at time t15, and its callback function is executed accordingly.
6.8.1 The xTimerReset() API Function
A timer is reset using the xTimerReset()
API function.
xTimerReset()
can also be used to start a timer that is in the Dormant state.
Note: Never call xTimerReset()
from an interrupt service routine. The interrupt-safe version xTimerResetFromISR()
should be used in its place.
Listing 87. The xTimerReset() API function prototype
xTimerReset() parameters and return value
xTimer
The handle of the software timer being reset or started. The handle will have been returned from the call to
xTimerCreate()
used to create the software timer.xTicksToWait
xTimerChangePeriod()
uses the timer command queue to send the 'reset' command to the daemon task.xTicksToWait
specifies the maximum amount of time the calling task should remain in the Blocked state to wait for space to become available on the timer command queue, should the queue already be full.xTimerReset()
will return immediately ifxTicksToWait
is zero and the timer command queue is already full.If
INCLUDE_vTaskSuspend
is set to 1 inFreeRTOSConfig.h
then settingxTicksToWait
toportMAX_DELAY
will result in the calling task remaining in the Blocked state indefinitely (without a timeout) to wait for space to become available in the timer command queue.Returned value
There are two possible return values:
pdPASS
pdPASS
will be returned only if data was successfully sent to the timer command queue.If a block time was specified (
xTicksToWait
was not zero), then it is possible the calling task was placed into the Blocked state to wait for space to become available in the timer command queue before the function returned, but data was successfully written to the timer command queue before the block time expired.pdFALSE
pdFALSE
will be returned if the 'reset' command could not be written to the timer command queue because the queue was already full.If a block time was specified (
xTicksToWait
was not zero) then the calling task will have been placed into the Blocked state to wait for the daemon task to make room in the queue, but the specified block time expired before that happened.
6.8.2 Example 15. Resetting a software timer
This example simulates the behavior of the backlight on a cell phone. The backlight:
Turns on when a key is pressed.
Remains on provided further keys are pressed within a certain time period.
Automatically turns off if no key presses are made within a certain time period.
A one-shot software timer is used to implement this behavior:
The [simulated] backlight is turned on when a key is pressed, and turned off in the software timer's callback function.
The software timer is reset each time a key is pressed.
The time period during which a key must be pressed to prevent the backlight being turned off is therefore equal to the period of the software timer; if the software timer is not reset by a key press before the timer expires, then the timer's callback function executes, and the backlight is turned off.
The xSimulatedBacklightOn
variable holds the backlight state. xSimulatedBacklightOn
is set to pdTRUE
to indicate the backlight is on, and pdFALSE
to indicate the backlight is off.
The software timer callback function is shown in Listing 88.
Listing 88. The callback function for the one-shot timer used in Example 15
Example 15 creates a task to poll the keyboard11. The task is shown in Listing 89, but for the reasons described in the next paragraph, Listing 89 is not intended to be representative of an optimal design.
(11): Printing to the Windows console, and reading keys from the Windows console, both result in the execution of Windows system calls. Windows system calls, including use of the Windows console, disks, or TCP/IP stack, can adversely affect the behavior of the FreeRTOS Windows port, and should normally be avoided.
Using FreeRTOS allows your application to be event driven. Event driven designs use processing time very efficiently, because processing time is only used if an event has occurred, and processing time is not wasted polling for events that have not occurred. Example 15 could not be made event driven because it is not practical to process keyboard interrupts when using the FreeRTOS Windows port, so the much less efficient polling technique had to be used instead. If Listing 89 was an interrupt service routine, then xTimerResetFromISR()
would be used in place of xTimerReset()
.
Listing 89. The task used to reset the software timer in Example 15
The output produced when Example 15 is executed is shown in Figure 47. With reference to Figure 47:
The first key press occurred when the tick count was 812. At that time the backlight was turned on, and the one-shot timer was started.
Further key presses occurred when the tick count was 1813, 3114, 4015 and 5016. All of these key presses resulted in the timer being reset before the timer had expired.
The timer expired when the tick count was 10016. At that time the backlight was turned off.
It can be seen in Figure 47 that the timer had a period of 5000 ticks; the backlight was turned off exactly 5000 ticks after a key was last pressed, so 5000 ticks after the timer was last reset.
Last updated