STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-线程管理

文章目录

线程管理

CMSIS-RTOS v2 (CMSIS-RTOS2) 为基于 Arm® Cortex® 处理器的设备提供通用 RTOS 接口。 它为需要 RTOS 功能的软件组件提供标准化 API,从而为用户和软件行业带来巨大的好处。

本文将详细介绍,在CMSIS-RTOS V2中如何使用线程。

1、CMSIS-RTOS V2中的线程管理概念

在CMSIS-RTOS V2中,线程管理功能组允许在系统中定义、创建和控制线程功能(注意:不能从中断服务程序调用线程管理函数)。

在CMSIS-RTOS V2中,对线程的状态定义如下:

  • RUNNING:当前正在运行的线程处于RUNNING状态。 一次只能有一个线程处于此状态。
  • READY:准备运行的线程处于 READY 状态。 一旦 RUNNING 线程终止或被 BLOCKED,具有最高优先级的下一个 READY 线程将成为 RUNNING 线程。
  • BLOCKED:被延迟、等待事件发生或挂起的线程处于 BLOCKED 状态。
  • TERMINATED:调用 osThreadTerminate 时,线程在资源尚未释放的情况下被 TERMINATED(适用于可连接线程)。
  • INACTIVE:未创建或已终止并释放所有资源的线程处于 INACTIVE 状态。

线程不同状态之间的切换逻辑如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbUQV6Kg-1651805259050)(images/02-1.png)]

CMSIS-RTOS 假定线程按线程状态和状态转换图所示进行调度。 线程状态变化如下:

  • 使用函数 osThreadNew 创建一个线程。 这会将线程置于 READYRUNNING 状态(取决于线程优先级)。
  • CMSIS-RTOS 是抢占式的。 具有最高优先级的活动线程将成为 RUNNING 线程,前提是它不等待任何事件。 线程的初始优先级由 osThreadAttr_t 定义,但可以在执行期间使用函数 osThreadSetPriority 更改。
  • RUNNING 线程在延迟、等待事件或挂起时转入 BLOCKED 状态。
  • 可以使用函数 osThreadTerminate 随时终止活动线程。 线程也可以通过从线程函数返回来终止。 终止的线程处于**INACTIVE **状态,通常不消耗任何动态内存资源。

线程状态在代码中定义如下:

1
2
3
4
5
6
7
8
9
10
enum  	osThreadState\_t {
osThreadInactive = 0,
osThreadReady = 1,
osThreadRunning = 2,
osThreadBlocked = 3,
osThreadTerminated = 4,
osThreadError = -1,
osThreadReserved = 0x7FFFFFFF
}

线程属性在代码中定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/// Attributes structure for thread.
typedef struct {
const char \*name; ///< name of the thread
uint32\_t attr_bits; ///< attribute bits
void \*cb_mem; ///< memory for control block
uint32\_t cb_size; ///< size of provided memory for control block
void \*stack_mem; ///< memory for stack
uint32\_t stack_size; ///< size of stack
osPriority\_t priority; ///< initial thread priority (default: osPriorityNormal)
TZ_ModuleId_t tz_module; ///< TrustZone module identifier
uint32\_t reserved; ///< reserved (must be 0)
} osThreadAttr\_t;

线程执行函数定义如下:

1
2
void(\* osThreadFunc\_t)(void \*argument)

osPriority_t 值指定线程的优先级。 默认线程优先级应该是 osPriorityNormal。 如果活动线程准备好,其优先级高于当前正在运行的线程,则立即发生线程切换。 系统继续执行具有较高优先级的线程。

为了防止优先级倒置,兼容 CMSIS-RTOS 的操作系统可以选择实现优先级继承方法。 当高优先级线程正在等待由具有较低优先级的线程控制的资源或事件时,就会发生优先级反转。 从而导致高优先级线程可能被另一个具有较低优先级的线程永久阻塞。 为了解决这个问题,控制资源的低优先级线程应该被视为具有更高优先级,直到它释放资源。

osPriority_t 在代码中定义如下:

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
enum  	osPriority\_t {
osPriorityNone = 0,
osPriorityIdle = 1,
osPriorityLow = 8,
osPriorityLow1 = 8+1,
osPriorityLow2 = 8+2,
osPriorityLow3 = 8+3,
osPriorityLow4 = 8+4,
osPriorityLow5 = 8+5,
osPriorityLow6 = 8+6,
osPriorityLow7 = 8+7,
osPriorityBelowNormal = 16,
osPriorityBelowNormal1 = 16+1,
osPriorityBelowNormal2 = 16+2,
osPriorityBelowNormal3 = 16+3,
osPriorityBelowNormal4 = 16+4,
osPriorityBelowNormal5 = 16+5,
osPriorityBelowNormal6 = 16+6,
osPriorityBelowNormal7 = 16+7,
osPriorityNormal = 24,
osPriorityNormal1 = 24+1,
osPriorityNormal2 = 24+2,
osPriorityNormal3 = 24+3,
osPriorityNormal4 = 24+4,
osPriorityNormal5 = 24+5,
osPriorityNormal6 = 24+6,
osPriorityNormal7 = 24+7,
osPriorityAboveNormal = 32,
osPriorityAboveNormal1 = 32+1,
osPriorityAboveNormal2 = 32+2,
osPriorityAboveNormal3 = 32+3,
osPriorityAboveNormal4 = 32+4,
osPriorityAboveNormal5 = 32+5,
osPriorityAboveNormal6 = 32+6,
osPriorityAboveNormal7 = 32+7,
osPriorityHigh = 40,
osPriorityHigh1 = 40+1,
osPriorityHigh2 = 40+2,
osPriorityHigh3 = 40+3,
osPriorityHigh4 = 40+4,
osPriorityHigh5 = 40+5,
osPriorityHigh6 = 40+6,
osPriorityHigh7 = 40+7,
osPriorityRealtime = 48,
osPriorityRealtime1 = 48+1,
osPriorityRealtime2 = 48+2,
osPriorityRealtime3 = 48+3,
osPriorityRealtime4 = 48+4,
osPriorityRealtime5 = 48+5,
osPriorityRealtime6 = 48+6,
osPriorityRealtime7 = 48+7,
osPriorityISR = 56,
osPriorityError = -1,
osPriorityReserved = 0x7FFFFFFF
}

1.2、线程管理API

CMSIS-RTOS V2对线程管理提供了如下API:

函数 描述
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr) 创建一个线程并将其添加到活动线程
const char * osThreadGetName(osThreadId_t thread_id) 查询线程名称
osThreadId_t osThreadGetId(void) 返回当前运行线程的线程 ID
osThreadState_t osThreadGetState(osThreadId_t thread_id) 返回当前线程状态
osStatus_t osThreadSetPriority(osThreadId_t thread_id,osPriority_t priority) 改变线程优先级
osPriority_t osThreadGetPriority (osThreadId_t thread_id) 返回当前线程优先级
osStatus_t osThreadYield (void) 将控制权传递给处于就绪状态的下一个线程
osStatus_t osThreadSuspend (osThreadId_t thread_id) 暂停线程的执行
osStatus_t osThreadResume (osThreadId_t thread_id) 恢复线程的执行
osStatus_t osThreadDetach (osThreadId_t thread_id) 分离一个线程(线程终止时可以回收线程存储)
osStatus_t osThreadJoin (osThreadId_t thread_id) 等待指定线程终止
__NO_RETURN void osThreadExit (void) 终止当前正在运行的线程的执行
osStatus_t osThreadTerminate (osThreadId_t thread_id) 终止线程的执行
uint32_t osThreadGetStackSize (osThreadId_t thread_id) 获取线程的堆栈大小
uint32_t osThreadGetStackSpace (osThreadId_t thread_id) 根据执行期间的堆栈水印记录获取线程的可用堆栈空间
uint32_t osThreadGetCount (void) 获取活动线程数量
uint32_t osThreadEnumerate (osThreadId_t *thread_array, uint32_t array_items) 枚举活动线程

1.3、线程创建

CMSIS-RTOS V2有多种方式创建线程。下面分别详细介绍。

1)简单方式创建线程

使用全局内存池中线程属性和内存的所有默认值中创建一个线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
__NO_RETURN void thread1 (void \*argument) {
// ...
for (;;) {}
}

int main (void) {
osKernelInitialize();
;
osThreadNew(thread1, NULL, NULL); // 使用默认设置创建线程
;
osKernelStart();
}

2)创建具有堆栈非默认堆栈大小的线程

与简单线程类似,所有属性都是默认的。 堆栈是从全局内存池中动态分配的。

osThreadAttr_t::stack_size 用于将堆栈大小以字节为单位传递给 osThreadNew。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__NO_RETURN void thread1 (void \*argument) {
// ...
for (;;) {}
}

const osThreadAttr\_t thread1_attr = {
.stack_size = 1024 // 创建大小为 1024 字节的线程堆栈
};

int main (void) {
;
osThreadNew(thread1, NULL, &thread1_attr); // 使用自定义大小的堆栈内存创建线程
;
}

3)使用静态分配的堆栈创建线程

与简单线程类似,所有属性都是默认的。 堆栈是使用 uint64_t 数组 thread1_stk_1 静态分配的。 这分配了 64*8 字节(=512 字节),对齐为 8 字节(对于 Cortex-M 堆栈内存是必需的)。

osThreadAttr_t::stack_mem 持有指向栈最低地址的指针。

osThreadAttr_t::stack_size 用于将堆栈大小以字节为单位传递给 osThreadNew。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__NO_RETURN void thread1 (void \*argument) {
// ...
for (;;) {}
}

static uint64\_t thread1_stk_1[64];

const osThreadAttr\_t thread1_attr = {
.stack_mem = &thread1_stk_1[0],
.stack_size = sizeof(thread1_stk_1)
};

int main (void) {
;
osThreadNew(thread1, NULL, &thread1_attr); // 使用静态分配的堆栈内存创建线程
;
}

4)具有静态分配的任务控制块的线程

通常,此方法与静态分配的堆栈一起选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "cmsis\_os2.h"

//include rtx\_os.h for types of RTX objects
#include "rtx\_os.h"
__NO_RETURN void thread1 (void \*argument) {
// ...
for (;;) {}
}

static osRtxThread\_t thread1_tcb;

const osThreadAttr\_t thread1_attr = {
.cb_mem = &thread1_tcb,
.cb_size = sizeof(thread1_tcb),
};

int main (void) {
;
osThreadNew(thread1, NULL, &thread1_attr); // Create thread with custom tcb memory
;
}

5)创建具有不同优先级的线程

RTX 的默认优先级是 osPriorityNormal。 通常,如果运行具有更高或更低优先级的任务,请使用 osThreadAttr_t 控制结构设置所需的任何初始优先级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__NO_RETURN void thread1 (void \*argument) {
// ...
for (;;) {}
}

const osThreadAttr\_t thread1_attr = {
.priority = osPriorityHigh // 将初始线程优先级设置为高
};

int main (void) {
;
osThreadNew(thread1, NULL, &thread1_attr);
;
}

6)Joinable 线程

在下面示例中,主线程创建了四个具有 osThreadJoinable 属性的线程。 这些将做一些工作并在完成后使用 osThreadExit 调用返回。osThreadJoin用于同步线程终止。

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
__NO_RETURN void worker (void \*argument) {
; // work a lot on data[]
osDelay(1000U);
osThreadExit();
}

__NO_RETURN void thread1 (void \*argument) {
osThreadAttr\_t worker_attr;
osThreadId\_t worker_ids[4];
uint8\_t data[4][10];
memset(&worker_attr, 0, sizeof(worker_attr));
worker_attr.attr_bits = osThreadJoinable;

worker_ids[0] = osThreadNew(worker, &data[0][0], &worker_attr);
worker_ids[1] = osThreadNew(worker, &data[1][0], &worker_attr);
worker_ids[2] = osThreadNew(worker, &data[2][0], &worker_attr);
worker_ids[3] = osThreadNew(worker, &data[3][0], &worker_attr);

osThreadJoin(worker_ids[0]);
osThreadJoin(worker_ids[1]);
osThreadJoin(worker_ids[2]);
osThreadJoin(worker_ids[3]);

osThreadExit();
}

2、STM32CubeIDE中配置线程

第一步: 新建工程

新建工程,请参考前面文章:

串口配置,请参考前面文章:

第二步: 配置时钟

配置时钟,请参考前面文章:STM32F1与STM32CubeIDE快速入门-GPIO概述与点亮LED

由于使用了RTOS,因此,需要配置一个定时器作为RTOS的时基,配置如下:

在这里插入图片描述

第三步:配置FreeRTOS

在这里插入图片描述

在这里插入图片描述

该选项为可选项。

FreeRTOS堆栈配置:

在这里插入图片描述

堆栈大小配置需要根据MCU内存大小配置。

第四步:添加线程

在这里插入图片描述

在这里插入图片描述

可根据需求设置TaskNamePriority等等。

在这里插入图片描述

线程最终使用堆栈大小及整个FreeRTOS的堆栈使用大小如下:

在这里插入图片描述

第五步:保存配置生成代码

3、线程功能代码实现

main.c文件的main函数中,RTOS启动调用如下:

1
2
3
4
5
6
7
/\* Init scheduler \*/
osKernelInitialize(); /\* Call init function for freertos objects (in freertos.c) \*/
MX\_FREERTOS\_Init();

/\* Start scheduler \*/
osKernelStart();

freertos.c文件中,生成定义的线程代码如下:

1)线程osThreadAttr_t和osThreadId_t定义及配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/\* USER CODE END Variables \*/
/\* Definitions for defaultTask \*/
osThreadId\_t defaultTaskHandle;
const osThreadAttr\_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for led1 \*/
osThreadId\_t led1Handle;
const osThreadAttr\_t led1_attributes = {
.name = "led1",
.stack_size = 128 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for led2 \*/
osThreadId\_t led2Handle;
const osThreadAttr\_t led2_attributes = {
.name = "led2",
.stack_size = 128 \* 4,
.priority = (osPriority\_t) osPriorityLow,
};

2)线程执行函数:

1
2
3
4
void StartDefaultTask(void \*argument);
void led1Task(void \*argument);
void led2Task(void \*argument);

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
/\* USER CODE END Header\_StartDefaultTask \*/
void StartDefaultTask(void \*argument)
{
/\* USER CODE BEGIN StartDefaultTask \*/
/\* Infinite loop \*/
for(;;)
{
osDelay(1);
}
/\* USER CODE END StartDefaultTask \*/
}

/\* USER CODE BEGIN Header\_led1Task \*/
/\*\*
\* @brief Function implementing the led1 thread.
\* @param argument: Not used
\* @retval None
\*/
/\* USER CODE END Header\_led1Task \*/
void led1Task(void \*argument)
{
/\* USER CODE BEGIN led1Task \*/
/\* Infinite loop \*/
for(;;)
{
osDelay(1);
}
/\* USER CODE END led1Task \*/
}

/\* USER CODE BEGIN Header\_led2Task \*/
/\*\*
\* @brief Function implementing the led2 thread.
\* @param argument: Not used
\* @retval None
\*/
/\* USER CODE END Header\_led2Task \*/
void led2Task(void \*argument)
{
/\* USER CODE BEGIN led2Task \*/
/\* Infinite loop \*/
for(;;)
{
osDelay(1);
}
/\* USER CODE END led2Task \*/
}

3)线程启动:

1
2
3
4
5
6
7
8
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

/\* creation of led1 \*/
led1Handle = osThreadNew(led1Task, NULL, &led1_attributes);

/\* creation of led2 \*/
led2Handle = osThreadNew(led2Task, NULL, &led2_attributes);

STM32CubeIDE帮助我们生成了整个与线程管理相关的脚手加代码,接下来,只需要在线程执行函数中实现需要的功能即可。

比如,定义的线程执行函数中,实现的功能如下:

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
/\* USER CODE END Header\_led1Task \*/
void led1Task(void \*argument) {
/\* USER CODE BEGIN led1Task \*/
/\* Infinite loop \*/

for (;;) {
HAL\_GPIO\_TogglePin(LED1_GPIO_Port, LED1_Pin);
osDelay(1000 / portTICK_PERIOD_MS);
}
/\* USER CODE END led1Task \*/
}

/\* USER CODE BEGIN Header\_led2Task \*/
/\*\*
\* @brief Function implementing the led2 thread.
\* @param argument: Not used
\* @retval None
\*/
/\* USER CODE END Header\_led2Task \*/
void led2Task(void \*argument) {
/\* USER CODE BEGIN led2Task \*/
/\* Infinite loop \*/
for (;;) {
HAL\_GPIO\_TogglePin(LED2_GPIO_Port, LED2_Pin);
osDelay(500 / portTICK_PERIOD_MS);
}
/\* USER CODE END led2Task \*/
}

led1Taskled2Task函数中,分别对LED进行点亮和关闭操作,两个LED点亮和关闭的时间不同。关于GPIO的设置,请参考:

最后编译生成固件并下载到开发板中。

4、STM32F1与STM32CubeIDE快速入门系列文章

序号 内容
1 STM32F1与STM32CubeIDE快速入门-开发环境搭建
2 STM32F1与STM32CubeIDE快速入门-STM32F1微控制器概述
3 STM32F1与STM32CubeIDE快速入门-GPIO概述与点亮LED
4 STM32F1与STM32CubeIDE快速入门-按键与LED控制
5 STM32F1与STM32CubeIDE快速入门-中断、NVIC与EXTI概述
6 STM32F1与STM32CubeIDE快速入门-外部中断配置与功能实现
7 STM32F1与STM32CubeIDE快速入门-USART/UART串口通信
8 STM32F1与STM32CubeIDE快速入门-定时器(Timer)概述
9 STM32F1与STM32CubeIDE快速入门-定时器定时模式
10 STM32F1与STM32CubeIDE快速入门-定时器计数模式
11 STM32F1与STM32CubeIDE快速入门-定时器PWM模式
12 STM32F1与STM32CubeIDE快速入门-定时器编码(Encoder)模式
13 STM32F1与STM32CubeIDE快速入门-定时器输入捕获模式(Input Capture Mode)实现频率计数
14 STM32F1与STM32CubeIDE快速入门-DMA概述
15 STM32F1与STM32CubeIDE快速入门-USART通过DMA进行数据接收与发送
16 STM32F1与STM32CubeIDE快速入门-ADC概述
17 STM32F1与STM32CubeIDE快速入门-ADC轮询方式实现PWM调光器
18 STM32F1与STM32CubeIDE快速入门-ADC中断方式实现PWM调光器
19 STM32F1与STM32CubeIDE快速入门-ADC通过DMA方式与PWM实现调光器
20 STM32F1与STM32CubeIDE快速入门-DAC概述
21 STM32F1与STM32CubeIDE快速入门-SPI概述
22 STM32F1与STM32CubeIDE快速入门-M25P16串行闪存驱动
23 STM32F1与STM32CubeIDE快速入门-I2C概述
24 STM32F1与STM32CubeIDE快速入门-I2C驱动LCD1602显示屏(基于PCF8574)
25 STM32F1与STM32CubeIDE快速入门-独立看门狗(IWDG)
26 STM32F1与STM32CubeIDE快速入门-OLED-SSD1306-I2C驱动
27 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2配置(基于FreeRTOS)

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