STM32F1与STM32CubeIDE编程实例-ThreadX中的系统时钟及应用程序定时器

ThreadX中的系统时钟及应用程序定时器

文章目录

1、定时器介绍

对异步外部事件的快速响应是实时、嵌入式应用程序最重要的功能。 但是,这些应用程序中的许多应用程序还必须以预定的时间间隔执行某些活动。

ThreadX 应用程序计时器能够以特定的时间间隔执行应用程序 C 函数。 还可以将应用程序计时器设置为仅过期一次。 这种类型的计时器称为一次性计时器(One-Shot Timer),而重复间隔计时器称为周期计时器(Periodic Timer)。 每个应用程序计时器都是公共资源。

时间间隔由周期性定时器中断测量。 每个定时器中断称为一个定时器节拍。 计时器滴答之间的实际时间由应用程序指定,但 10 毫秒是许多实现的标准。(注意,周期性定时器设置通常位于 tx_Initialize_low_level 汇编文件中)

底层硬件必须能够生成周期性中断,以使应用程序定时器起作用。 在某些情况下,处理器具有内置的周期性中断功能。 如果没有,必须有一个可以产生周期性中断的外围设备。

即使没有周期性中断源,ThreadX 仍然可以运行。 但是,所有与计时器相关的处理都将被禁用。 这包括时间片、暂停超时和计时器服务。

计时器到期间隔是根据计时器节拍指定的。 计时器计数从指定的到期值开始,并在每个计时器节拍减一。 因为可以在定时器中断(或定时器节拍)之前启用应用程序定时器,所以定时器可以提前一个节拍到期。

如果计时器节拍速率为 10 毫秒,则应用程序计时器可能会提前 10 毫秒到期。 对于 10 ms 定时器,这种不准确性比 1 秒定时器更显着。 当然,增加定时器中断频率会减小这个误差范围。

应用程序计时器按照它们激活的顺序执行。 例如,如果创建三个具有相同到期值的计时器然后激活它们,则它们对应的到期函数保证按照激活计时器的顺序执行。

默认情况下,应用程序计时器在以零优先级运行的隐藏系统线程中执行,该优先级高于任何应用程序线程。 因此,应该将计时器到期函数内部的处理保持在最低限度。 重要的是要避免挂起在应用程序计时器到期功能内进行的任何服务调用。

尽可能避免使用在每个计时器滴答时都到期的计时器也很重要。这可能会导致应用程序的开销过大。

除了应用程序计时器之外,ThreadX 还提供了一个连续递增的 32 位节拍计数器。 此节拍计数器或内部系统时钟在每次定时器中断时加一。 应用程序可以分别调用 tx_time_get 和 tx_time_set 来读取或设置这个 32 位计数器。 内部系统时钟的使用完全由应用程序决定。

2、内部系统时钟服务

ThreadX 在应用程序初始化期间将内部系统时钟设置为零,并且每个计时器节拍都会将时钟加一。 内部系统时钟仅供开发人员使用; ThreadX 不会将它用于任何目的,包括实现应用程序计时器。 应用程序可以对内部系统时钟执行两种操作:读取当前时钟值,或设置其值。

tx_time_get 服务从内部系统时钟中检索当前时间,其原型如下:

ULONG tx_time_get(VOID);

调用此服务后,将返回内部系统时钟的副本。 此服务可用于测量经过的时间并执行其他与时间相关的计算。

tx_time_set 服务将内部系统时钟的当前时间设置为某个指定值。其原型如下:

VOID tx_time_set(ULONG new_time);

调用此服务后,内部系统时钟的当前时间设置为new_time,并在下一个节拍时,它将增加1。

简单示例如下:

1
2
3
ULONG current_time;
current_time = tx\_time\_get();

1
2
tx\_time\_set(0x1234);

3、应用程序定时器控制块

应用程序定时器控制块由TX_TIMER数据结构描述,它包含有用的信息,例如 ID、应用程序计时器名称、剩余计时器滴答数、重新初始化值、指向超时函数的指针、超时函数的参数以及各种指针。 与其他控制块一样,ThreadX 禁止应用程序显式修改应用程序计时器控制块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/\* Define the timer structure utilized by the application. \*/

typedef struct TX\_TIMER\_STRUCT
{

/\* Define the timer ID used for error checking. \*/
ULONG tx_timer_id;

/\* Define the timer's name. \*/
CHAR \*tx_timer_name;

/\* Define the actual contents of the timer. This is the block that
is used in the actual timer expiration processing. \*/
TX_TIMER_INTERNAL tx_timer_internal;

/\* Define the pointers for the created list. \*/
struct TX\_TIMER\_STRUCT
\*tx_timer_created_next,
\*tx_timer_created_previous;

/\* Define optional extension to timer control block. \*/
TX_TIMER_EXTENSION

#ifdef TX\_TIMER\_ENABLE\_PERFORMANCE\_INFO

/\* Define the number of timer activations. \*/
ULONG tx_timer_performance_activate_count;

/\* Define the number of timer reactivations. \*/
ULONG tx_timer_performance_reactivate_count;

/\* Define the number of timer deactivations. \*/
ULONG tx_timer_performance_deactivate_count;

/\* Define the number of timer expirations. \*/
ULONG tx_timer_performance_expiration_count;

/\* Define the total number of timer expiration adjustments. \*/
ULONG tx_timer_performance__expiration_adjust_count;
#endif

} TX_TIMER;

应用程序计时器控制块可以位于内存中的任何位置,但最常见的是通过在任何函数范围之外定义控制块来使控制块成为全局结构。

4、应用程序定时器服务

4.1 创建定时器

应用程序定时器使用 TX_TIMER 数据类型声明,并使用 tx_timer_create 服务定义。 定义应用程序计时器时,必须指定其控制块、应用程序计时器的名称、计时器到期时要调用的到期函数、传递给到期函数的输入值、计时器到期前的初始计时器滴答数 ,第一个后所有计时器到期的计时器滴答数,以及确定计时器何时激活的选项。 初始计时器滴答数的有效值范围为 1 到 0xFFFFFFFF(含)。其原型如下:

UINT tx_timer_create(TX_TIMER *timer_ptr,
CHAR *name_ptr,
VOID (*expiration_function)(ULONG),
ULONG expiration_input, ULONG initial_ticks,
ULONG reschedule_ticks, UINT auto_activate);

其中,

  • timer_ptr:指向定时器控制块的指针
  • name_ptr:指向定时器名称的指针
  • expiration_function:回调函数
  • expiration_input:回调函数参数
  • initial_ticks:初始节拍数
  • reschedule_ticks:到期的计时器节拍数(即,周期节拍数)。当设置为0时,表示创建一次性定时器
  • auto_activate:自动激活标志

服务如果成功调用,则返回TX_SUCCESS。

简单示例如下:

1
2
3
4
5
6
7
8
9
10
11
TX_TIMER my_timer;
UINT status;

void my\_timer\_function (ULONG){
//
}

status = tx\_timer\_create(&my_timer,"my\_timer\_name",
my_timer_function, 0x1234, 100, 25,
TX_AUTO_ACTIVATE);

在示例中创建一个名称为my_timer_function应用程序计时器,定时在 100 个计时器节拍后执行,然后在每 25 个计时器节拍后周期性执行。 此计时器指定为立即启动。

如果status等于 TX_SUCCESS,my_timer_function 将在 100 个计时器滴答之后被调用,然后每 25 个计时器滴答被调用。 请注意,每次调用时都会将值 0x1234 传递给 my_timer_function。

4.2 激活与暂停定时器

当使用 TX_NO_ACTIVATE 选项创建应用程序计时器时,它会保持非活动状态,直到调用 tx_timer_activate 服务。 类似地,如果使用 tx_timer_deactive 服务停用应用程序计时器,它会保持不活动状态,直到调用 tx_timer_activate 服务。 如果两个或多个应用程序定时器同时到期,则相应的到期函数按照它们被激活的顺序执行。其原型分别如下:

UINT tx_timer_activate(TX_TIMER *timer_ptr);

UINT tx_timer_deactivate(TX_TIMER *timer_ptr);

在修改应用程序计时器的计时特性之前,必须首先停用该计时器。 这是 tx_timer_deactivate 服务的唯一目的。

简单示例如下:

1
2
3
4
5
6
7
TX_TIMER my_timer;
UINT status;

...

status = tx\_timer\_activate(&my_timer);

1
2
3
4
5
6
7
```c
TX_TIMER my_timer;
UINT status;

...

status = tx\_timer\_deactivate(&my_timer);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#### 4.3 修改定时器


创建应用程序计时器时,必须指定计时器到期前的初始计时器节拍数,以及周期性执行的拍数。 调用 `tx_timer_change` 服务可以更改这些值。 必须在调用此服务之前停用应用程序计时器,并在此服务之后调用 `tx_timer_activate` 以重新启动计时器。其原型如下:



>
> UINT tx\_timer\_change(TX\_TIMER \*timer\_ptr,
> ULONG initial\_ticks,
> ULONG reschedule\_ticks);
>
>
>


简单示例如下:



TX_TIMER my_timer;
UINT status;

status = tx_timer_change(&my_timer,50, 50);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

在示例中,将定时器的初始化节拍数更改为50,周期性执行节拍数为50。如果status的值为TX\_SUCCESS,则表示更改成功。


#### 4.4 定时器信息检索


可以通过三种服务检索定时器的重要信息:


* **tx\_timer\_pool\_info\_get**:从应用程序计时器控制块中检索信息子集。此信息为特定时刻的**快照(Snapshot)**,
* 其他两个服务提供基于收集的运行时性能数据的摘要信息:
+ **tx\_timer\_pool\_performance\_info\_get service**:查询指定定时器信息
+ **tx\_timer\_pool\_performance\_system\_info\_get**:查询所有定时器信息


它们的原型如下:



>
> UINT tx\_timer\_info\_get(TX\_TIMER \*timer\_ptr, CHAR \*\*name,
> UINT \*active, ULONG \*remaining\_ticks,
> ULONG \*reschedule\_ticks,
> TX\_TIMER \*\*next\_timer);
>
>
>



>
> UINT tx\_timer\_performance\_info\_get(TX\_TIMER \*timer\_ptr,
> ULONG \*activates,
> ULONG \*reactivates, ULONG \*deactivates,
> ULONG \*expirations,
> ULONG \*expiration\_adjusts);
>
>
>



>
> UINT tx\_timer\_performance\_system\_info\_get
> ULONG \*activates, ULONG \*reactivates,
> ULONG \*deactivates, ULONG \*expirations,
> ULONG \*expiration\_adjusts);
>
>
>


简单示例如下:



TX_TIMER my_timer;
CHAR *my_timer_name;
UINT active;
ULONG remaining_ticks;
ULONG reschedule_ticks;
TX_TIMER *next_timer;
UINT status;

status = tx_timer_info_get(&my_timer, &my_timer_name,
&active,&remaining_ticks,
&reschedule_ticks,
&next_timer);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

当status的值为TX\_SUCCESS时,表示检索成功。


`注意`:在定时器回调函数中,不能调用`tx_thread_sleep`之类函数,否则将导致程序出现无法预料情况。可以在定时器回调中创建新线程。


### 5、完整示例


示例将创建两个定时器,一个周期性定时器用于计数,一个一次性定时器,在指定时间到达后输出信息。


#### 5.1 应用程序


**1)基本定义**



/*
* app.h
*
* Created on: May 3, 2022
* Author: jenson
*/

#ifndef __APP_H__
#define __APP_H__

#include “tx_api.h”

void app_start(void);

uint16_t create_task_stack(void** statck,uint32_t size);

#endif /* __APP_H__ */

1
2
3
4
5

**2)定义实现**



/*
* app.c
*
* Created on: May 3, 2022
* Author: jenson
*/
#include <stdio.h>
#include “main.h”
#include “app.h”
#include “tx_api.h”
#include “tx_thread.h”
#include <stdbool.h>
#include “tasks/counter_task.h”

// 10k应用程序内存
#define APP_MEMORY_SIZE 1024 * 10

// 字节内存句柄
static TX_BYTE_POOL __s_app_statc_pool;

static uint8_t __app_memory_area[APP_MEMORY_SIZE];

// 创建字节内存池
uint16_t create_app_byte_pool() {
UINT status;
// 创建字节内存池
status = tx_byte_pool_create(&__s_app_statc_pool, “app_byte_pool”,
__app_memory_area, APP_MEMORY_SIZE);
return status;
}

// 创建字节栈
uint16_t create_task_stack(void **statck, uint32_t size) {
UINT status;
// 分配字节内存
status = tx_byte_allocate(&__s_app_statc_pool, statck, size, TX_NO_WAIT);
return status;
}

void tx_application_define(void *first_unused_memory) {

uint16\_t status = create\_app\_byte\_pool();
if (status != TX_SUCCESS) {
    printf("create app memory byte pool failed:code = %d\r\n", status);
    while (true)
        ;
}

// 创建定时器
create\_counter\_task();
create\_one\_shot\_timer();

}

void app_start(void) {
// 启用内核调度
tx_kernel_enter();
}

1
2
3
4
5
6
7
8

#### 5.2 定时器实现


**1)基本定义**



/*
* counter_task.h
*
* Created on: 2022年5月20日
* Author: jenson
*/

#ifndef __COUNTER_TASK_H__
#define __COUNTER_TASK_H__

void create_counter_timer(void);
void create_one_shot_timer(void);

#endif /* __COUNTER_TASK_H__ */

1
2
3
4
5

**2)定义实现**



/*
* counter_task.c
*
* Created on: 2022年5月20日
* Author: jenson
*/

#include “counter_task.h”

#include “tx_api.h”
#include “tx_thread.h”
#include <stdio.h>
#include “app.h”
#include <stdbool.h>

TX_TIMER counter_timer;
TX_TIMER one_shot_timer;

ULONG counter = 0;
void on_counter_callback(ULONG params){
counter++;
printf(“counter = %d\r\n”,counter);
if(counter == 3){
printf(“activate one shot timer\r\n”);
tx_timer_activate(&one_shot_timer);
}
}

void on_one_shot_timer_callback(ULONG params){
printf(“one shot timer called\r\n”);
}

void create_counter_timer(void){
UINT status;
status = tx_timer_create(&counter_timer,”counter_timer”,on_counter_callback,0x00,1000*3,500,TX_AUTO_ACTIVATE);
if(status != TX_SUCCESS){
printf(“cannot create counter timer.\r\n”);
while(true);
}

}

void create_one_shot_timer(void){
UINT status;
status = tx_timer_create(&one_shot_timer,”one_shot_timer”,on_one_shot_timer_callback,0x00,1000*5,0,TX_NO_ACTIVATE);
if(status != TX_SUCCESS){
printf(“cannot create counter timer.\r\n”);
while(true);
}
}

1
2
3
4
5

#### 5.3 主程序



/* Private includes ———————————————————-*/
/* USER CODE BEGIN Includes */
#include “app.h”
#include <stdio.h>
/* USER CODE END Includes */

int main(void) {
/* USER CODE BEGIN 1 */

/\* USER CODE END 1 \*/

/\* MCU Configuration--------------------------------------------------------\*/

/\* Reset of all peripherals, Initializes the Flash interface and the Systick. \*/
HAL\_Init();

/\* USER CODE BEGIN Init \*/

/\* USER CODE END Init \*/

/\* Configure the system clock \*/
SystemClock\_Config();

/\* USER CODE BEGIN SysInit \*/

/\* USER CODE END SysInit \*/

/\* Initialize all configured peripherals \*/
MX\_GPIO\_Init();
MX\_USART1\_UART\_Init();
/\* USER CODE BEGIN 2 \*/
printf("\*\*\*\*STM32CubeIDE:ThreadX Demo\*\*\*\*\r\n");
app\_start();
/\* USER CODE END 2 \*/

/\* Infinite loop \*/
/\* USER CODE BEGIN WHILE \*/
while (1) {
    /\* USER CODE END WHILE \*/

    /\* USER CODE BEGIN 3 \*/
}
/\* USER CODE END 3 \*/

}


运行结果:


![在这里插入图片描述](https://img-blog.csdnimg.cn/dffde201cb9a4632950c430e7e6a7bd7.png#pic_center)





文章来源: https://iotsmart.blog.csdn.net/article/details/125183283