STM32移植UCOSIII

本文最后更新于:2023年10月3日 下午

STM32移植UCOSIII

  早期嵌入式Q开发没有嵌入式操作系统的概念,直接操作裸机,在裸机上写程序,比如用51单片机基本就没有操作系统的概念。通常把程序分为两部分:前台系统和后台系统。传统简单的小系统通常是前后台系统,这样的程序包括一个死循环和若干个中断服务程序:应用程序是一个无限循环,循环中调用API函数完成所需的操作,这个大循环就叫做后台系统。中断服务程序用于处理系统的异步事件,也就是前台系统。前台是中断级,后台是任务级。

  在前后台系统下,所有的任务的优先级是平等的,且是以FIFO的方式排列。也就是说,后面的任务必须等待前面的任务都完成之后才可以执行。在前后台系统下,实时性非常差,而且随着任务数量的增多,实时性会愈差。就好像我们上课时突然内急(优先级很高),需要立刻去WC,但是却不得不等到下课(优先级不高)。这种情况下在程序上表现出来可能就是数据丢失,或者响应慢(比如步进电机过冲)等效果了。尽管也可以利用定时器等手段去手动分配时间片来增强实时性,但这样的方法容易造成程序的高度耦合,与程序开发的“高内聚,低耦合”相悖,这样的代码肯定是很难管理和维护的。

  而在RTOS实时操作系统下进行程序开发,每个任务都是独立运行的一个有限次或无限次循环,一般情况下不会互相影响(除非栈溢出等因素),因而很容易管理和维护。每个任务都可以设置优先级,RTOS的内核大多都是可抢占式的。高优先级的任务一但就绪就可以夺走低优先级任务的CPU使用权,实时性相比于前后台系统大幅度提高。而且RTOS会提供一些内核对象供用户使用,比如信号量,常用作任务同步和共享资源保护等。

  uC/OS-Ⅲ(Micro C OS Three微型的 C 语言编写的操作系统第3版)是一个可升级的,可固化的,基于优先级的实时内核,属于一种RTOS实时操作系统。它对任务的个数无限制。uC/OS-Ⅲ是一个第3代的系统内核,支持现代的实时内核所期待的大部分功能。例如资源管理,同步,任务间的通信等等。然而,uC/OS-Ⅲ提供的特色功能在其它的实时内核中是找不到的,比如说完备的运行时间测量性能,直接地发送信号或者消息到任务,任务可以同时等待多个内核对象等。

参考

STM32CubeIDE 复制工程使用CubeMX配置生成后src文件夹内容被删除(移除)_sudaroot的博客-CSDN博客

[野火]uCOS-III内核实现与应用开发实战指南—基于STM32

Weston Embedded Solutions · GitHub

2023年新版手把手教你学UCOS-III — 正点原子资料下载中心 1.0.0 文档 (openedv.com)

第6讲 UCOSIII移植_哔哩哔哩_bilibili

使用RTOS做单片机开发有什么优势 - 哔哩哔哩 (bilibili.com)

RTOS实时操作系统杂谈 - 知乎 (zhihu.com)

一、ucosⅢ源码下载

uc-OS3源码:weston-embedded/uC-OS3 at v3.08.01 (github.com)

uc-LIB源码:weston-embedded/uC-LIB at v1.39.01 (github.com)

uc-CPU源码:weston-embedded/uC-CPU at v1.32.01 (github.com)

二、ucosⅢ源码结构

ucosⅢ源码架构

三、工程添加4个分组【官方源码文件】

Keil工程分组 添加文件 备注
(19个文件)
uC_OS3/OS ./uC_OS3/uC-OS3/Cfg/Template/os_app_hooks.c 包含了8 个空的能被uC/OS-III 调用的hook0 函数。
./uC_OS3/uC-OS3/Ports/ARM-Cortex-M/ARMv7-M/ARM/os_cpu_a.asm 包含了汇编语言定义的函数。至少需包含OSCtxSW(),OSIntCtxSW(),OSStartHighRdy()的定义
./uC_OS3/uC-OS3/Ports/ARM-Cortex-M/ARMv7-M/os_cpu_c.c 包含了C 代码编写的hook 函数以及初始化任务创建时堆栈框架的相关代码
./uC_OS3/uC-OS3/Source/os_cfg_app.c 申明变量和数组(基于OS_CFG_APP.H 中的变量类型)
./uC_OS3/uC-OS3/Source/os_core.c 包含uC/OS-III 的核心函数如OSInit(),OSSchedule(),OSIntExit()等等
./uC_OS3/uC-OS3/Source/os_dbg.c 申明了关于内核调试器或uC/Probe 的常量
./uC_OS3/uC-OS3/Source/os_flag.c 包含了时间管理相关的代码
./uC_OS3/uC-OS3/Source/os_mem.c 包含了uC/OS-III 内存管理相关代码
./uC_OS3/uC-OS3/Source/os_msg.c 包含了消息处理相关代码
./uC_OS3/uC-OS3/Source/os_mutex.c 包含了与互斥信号量相关的代码
./uC_OS3/uC-OS3/Source/os_prio.c 包含了管理任务优先级相关的代码。这个文件可以被改为同等功能的汇编语言写的文件(如果CPU 支持位设置,位清除指令,计数清零指令)以提高性能。
./uC_OS3/uC-OS3/Source/os_q.c 包含了管理消息队列相关的代码
./uC_OS3/uC-OS3/Source/os_sem.c 包含了管理与资源、同步相关的信号量代码
./uC_OS3/uC-OS3/Source/os_stat.c 包含了与统计任务相关的代码
./uC_OS3/uC-OS3/Source/os_task.c 包含了与管理任务相关的代码如OSTaskCreate(),OSTaskDel(),OSTaskChangePrio()等函数
./uC_OS3/uC-OS3/Source/os_tick.c 包含了与任务延时,任务停止相关的代码
./uC_OS3/uC-OS3/Source/os_time.c 包含了任务定时相关的代码
./uC_OS3/uC-OS3/Source/os_tmr.c 包含了管理软件定时器相关的代码
./uC_OS3/uC-OS3/Source/os_var.c 包含了uC/OS-III 的全局变量,这些变量都是用于管理uC/OS-III 的,不应该被用户的代码访问
(2个文件)
uC_OS3/BSP ./uC_OS3/uC-CPU/BSP/Template/bsp_cpu.c
./uC_OS3/uC-OS3/Template/bsp_os_dt.c
(3个文件)
uC_OS3/CPU ./uC_OS3/uC-CPU/ARM-Cortex-M/ARMv7-M/cpu_c.c 特定CPU 架构的C语言代码。一般来说,如果函数能用C 编写就用C 编写,除非用汇编编写能够产生更高的性
./uC_OS3/uC-CPU/ARM-Cortex-M/ARMv7-M/ARM/cpu_a.asm 包含了汇编语言编写的函数用于关中断,开中断,计数清零(如果CPU 支持这条汇编指令的话),其它CPU 相关的只能用汇编编写的函数。这个文件也包含开启caches,设置MPUs 和MMU 等等。
./uC_OS3/uC-CPU/cpu_core.c 适应于所有架构 CPU 的代码。包含了测量 CPU 关中断时间相关函数的代码。
(4个文件)
uC_OS3/LIB ./uC_OS3/uC-LIB/lib_ascii.c 包含将一些标准库函数(如 tolower()、toupper()、isalpha()、isdigit() 等)分别替换为 uC/LIB 对应函数 ASCII_ToLower()、ASCII_ToUpper()、ASCII_IsAlpha() 和ASCII_IsDig() 的源代码。
./uC_OS3/uC-LIB/lib_math.c 包含将一些标准库函数(如 rand()、srand()等)分别替换为 uC/LIB 对应函数 Math_Rand()、Math_SetSeed()的源代码
./uC_OS3/uC-LIB/lib_mem.c 包含将一些标准库函数,(如 memclr()、memset()、memcpy()、memcmp() 等)分别替换为 uC/LIB 对应函数Mem_Clr()、Mem_Set()、Mem_Copy ()、Mem_Cmp() 的源代码
./uC_OS3/uC-LIB/lib_str.c 包含将一些标准库函数,(如strlen()、strcpy()、strcat()、strcmp()等)分别替换为 uC/LIB 对应函数Str_Len()、Str_Copy()、Str_Cat()、Str_Cmp()的源代码

四、添加头文件路径

包含头文件文件夹路径
.._OS3-CPU
.._OS3-CPU-Cortex-M-M
.._OS3-CPU
.._OS3-LIB
.._OS3-LIB
.._OS3-OS3
.._OS3-OS3-Cortex-M-M
.._OS3-OS3

五、修改System文件【正点原子自定义文件】

直接拷贝正点原子项目中的文件,添加进项目分组即可

头文件 源文件 备注
uart.h uart.c 调试用串口函数定义(相应修改为cubemx代码结构,可删除)
sys.h sys.c 系统配置定义(相应修改为cubemx代码结构,去除配置系统时钟函数)
delay.h delay.c 延时函数定义(相应修改为cubemx代码结构)

六、替换中断服务函数

startup_stm32h743xx.s文件中的PendSV_Handler替换成OS_CPU_PendSVHandler

七、修改配置文件

使用正点原子配置文件(内容替换 或 按如下配置)

文件 修改内容 说明
./uC_OS3/uC-CPU/Cfg/Template/cpu_cfg.h #define CPU_CFG_NAME_EN DEF_ENABLED #define CPU_CFG_TS_32_EN DEF_ENABLED 开启Configure CPU count leading zeros bits 开启Configure CPU count trailing zeros bits 开启#define CPU_CFG_ENDIAN_TYPE CPU_ENDIAN_TYPE_LITTLE 开启#define CPU_CFG_NVIC_PRIO_BITS 4u 启用CPU 主机名 启用 32 位 CPU 时间戳功能 定义前导零位计数函数。 定义尾零位计数函数 重写 cpu.h 中定义的默认 CPU 端位类型为小端 确定可编程中断优先级[7:4]
/uC_OS3/uC-LIB/Cfg/Template/lib_cfg.h #define LIB_MEM_CFG_HEAP_SIZE 0u 禁用堆分配功能
./uC_OS3/uC-OS3/Cfg/Template/os_cfg_app.h
./uC_OS3/uC-OS3/Cfg/Template/os_cfg.h #define OS_CFG_APP_HOOKS_EN 0u #define OS_CFG_TS_EN 1u #define OS_CFG_PRIO_MAX 32u #define OS_CFG_TASK_REG_TBL_SIZE 0u #define OS_CFG_TASK_STK_REDZONE_EN 1u 禁用应用程序特定的钩子 启用时间标记 定义任务优先级的最大数量32 任务专用寄存器数量0 启用堆栈红区,以便在调试时检测堆栈溢出

八、添加自定义功能代码

(1)STM32CubeMX配置说明(更详细的配置自行上网查询)

  • 复制工程前后或使用图形化配置前记得先把文件夹下的.mxproject文件删除即可
  • 外部有源晶振BYPASS Clock Source
  • 外部无源晶振Crystal/Ceramic Resonator
  • 功率调节器电压级别Power Regulator Voltage Scale限制最高工作频率
  • IO引脚为黄色表示未激活,需要进行相应配置
GPIO默认电平设置 说明(一般在检测输入信号时设置)
No pull--up and no pull-down 不使用内部电阻(用于输出模式)
pull-up 上拉电阻,初始化为高电平
pull-down 下拉电阻,初始化为低电平

(2)LED灯功能代码

1.文件led.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"

extern uint8_t key;
extern uint8_t LED2_sta;

#define LED2(n) (n ? HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET)) // 设置LED状态
#define LED2_Toggle (HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1)) // LED1输出电平翻转

#endif

2.文件led.c

1
2
3
4
5
#include "led.h"

uint8_t key;
uint8_t LED2_sta = 0;

(3)Key按键功能代码

1.文件key.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef _KEY_H
#define _KEY_H
#include "./SYSTEM/sys/sys.h"

#define KEY1 HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) // KEY1按键PE3
#define KEY2 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5) // KEY2按键PC5

#define KEY1_PRES 1 // KEY1按下后返回值
#define KEY2_PRES 2 // KEY2按下后返回值

void KEY_Init(void); // 按键IO初始化函数
uint8_t KEY_Scan(uint8_t mode); // 按键扫描函数
#endif

2.文件key.c

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
#include "key.h"
#include "./SYSTEM/delay/delay.h"

// 按键处理函数
// 返回按键值
// mode:0,不支持连续按;1,支持连续按;
// 0,没有任何按键按下
// 1,WKUP按下 WK_UP
// 注意此函数有响应优先级,KEY1>KEY2

uint8_t KEY_Scan(uint8_t mode)
{
static uint8_t key_up = 1; // 按键松开标志
if (mode == 1)
key_up = 1; // 支持连按
if (key_up && (KEY1 == 0 || KEY2 == 0))
{
delay_ms(10);
key_up = 0;
if (KEY1 == 0)
return KEY1_PRES;
else if (KEY2 == 0)
return KEY2_PRES;
}
else if (KEY1 == 1 && KEY2 == 1)
key_up = 1;
return 0; // 无按键按下
}

(4)uC_OS3用户代码

1.文件uC_OS3.h

1
2
3
4
5
6
7
#ifndef __UC_OS3_H__
#define __UC_OS3_H__

void uc_os3(void);

#endif

2.文件uC_OS3.c

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include "uC_OS3.h"
#include "./SYSTEM/sys/sys.h"
#include "usart.h"
#include "led.h"
#include "key.h"

/*uC/OS-III*********************************************************************************************/
#include "os.h"
#include "cpu.h"
/******************************************************************************************************/
/*uC/OS-III配置*/

/* START_TASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIO 5
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
OS_TCB start_task_tcb;
void start_task(void *p_arg);

/* TASK1 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIO 4
#define TASK1_STACK_SIZE 256
CPU_STK SetLed_task1_stack[TASK1_STACK_SIZE];
OS_TCB task1_tcb;
void SetLed(void *p_arg);

/* TASK2 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 256
CPU_STK ScanKey_task2_stack[TASK2_STACK_SIZE];
OS_TCB task2_tcb;
void ScanKey(void *p_arg);

void uc_os3(void)
{
OS_ERR err;

// 初始化uC/OS-III
OSInit(&err);
OSTaskCreate((OS_TCB *)&start_task_tcb,
(CPU_CHAR *)"start_task",
(OS_TASK_PTR)start_task,
(void *)0,
(OS_PRIO)START_TASK_PRIO,
(CPU_STK *)&start_task_stack[0],
(CPU_STK_SIZE)START_TASK_STACK_SIZE / 10,
(CPU_STK_SIZE)START_TASK_STACK_SIZE,
(OS_MSG_QTY)0,
(OS_TICK)0,
(void *)0,
(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
if (err != OS_ERR_NONE)
{
// The task didn't get created. Look up the value of the error code ...
// ... in OS.H for the meaning of the error
}
OSStart(&err); // 开始任务调度
}

void start_task(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
CPU_Init();

#if OS_CFG_STAT_TASK_EN > 0u // 开启统计任务
OSStatTaskCPUUsageInit(&err);
#endif

#ifdef CPU_CFG_INT_DIS_MEAS_EN // 如果使能了测量中断关闭时间
CPU_IntDisMeasMaxCurReset();
#endif

#if OS_CFG_SCHED_ROUND_ROBIN_EN // 当使用时间片轮转的时候
OSSchedRoundRobinCfg(DEF_ENABLED, 1, &err); // 使能时间片轮转调度功能,设置默认的时间片长度
#endif

CPU_CRITICAL_ENTER(); /* 进入临界区 */
/* 创建task1 */
// task1_stack = mymalloc(SRAMIN, TASK1_STACK_SIZE * sizeof(CPU_STK));
OSTaskCreate((OS_TCB *)&task1_tcb,
(CPU_CHAR *)"SetLed",
(OS_TASK_PTR)SetLed,
(void *)0,
(OS_PRIO)TASK1_PRIO,
(CPU_STK *)SetLed_task1_stack,
(CPU_STK_SIZE)TASK1_STACK_SIZE / 10,
(CPU_STK_SIZE)TASK1_STACK_SIZE,
(OS_MSG_QTY)0,
(OS_TICK)0,
(void *)0,
(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);

/* 创建task2 */
// task2_stack = mymalloc(SRAMIN, TASK2_STACK_SIZE * sizeof(CPU_STK));
OSTaskCreate((OS_TCB *)&task2_tcb,
(CPU_CHAR *)"ScanKey",
(OS_TASK_PTR)ScanKey,
(void *)0,
(OS_PRIO)TASK2_PRIO,
(CPU_STK *)ScanKey_task2_stack,
(CPU_STK_SIZE)TASK2_STACK_SIZE / 10,
(CPU_STK_SIZE)TASK2_STACK_SIZE,
(OS_MSG_QTY)0,
(OS_TICK)0,
(void *)0,
(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);

CPU_CRITICAL_EXIT(); /* 退出临界区 */
OSTaskDel((OS_TCB *)0, &err);
}

/* 实现LED0每500ms反转一次 */
void SetLed(void *p_arg)
{
OS_ERR err;
while (1)
{
printf("task1正在运行!!!\r\n");
LED2_sta = !LED2_sta;

LED2(LED2_sta);
OSTimeDly(500, OS_OPT_TIME_DLY, &err);
}
}

/* 检测按键输入,挂起和恢复任务 */
void ScanKey(void *p_arg)
{
OS_ERR err;
while (1)
{
// printf("task3正在运行!!!\r\n");
key = KEY_Scan(0);
if (key == KEY1_PRES)
{
printf("挂起task1\r\n");
OSTaskSuspend(&task1_tcb, &err);
}
else if (key == KEY2_PRES)
{
printf("恢复task1\r\n");
OSTaskResume(&task1_tcb, &err);
}

OSTimeDly(10, OS_OPT_TIME_DLY, &err);
}
}

(5)main函数内容

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
/**
* @brief The application entry point.
* @retval int
*/
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_DMA_Init();
MX_QUADSPI_Init();
MX_DCMI_Init();
// MX_SDMMC1_SD_Init();
MX_SPI2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

/* Infinite loop */
uc_os3();

九、关于实时操作系统的两个误区:

(1)误区一:一定能提高系统响应速度?

  不一定。因为实时操作系统本身引入了执行开销,所以对于小型应用来说,有RTOS的性能也许不如无操作系统的情况。实时操作系统的优势最能体现在中大型系统中,当任务间存在复杂的耦合和依赖关系,并且应用程序经常要长时间等待外部资源时。

(2)误区二:一定可以保证系统实时性?

  不一定。相对来说,使用实时系统可以改善系统的实时性。但是实时操作系统只是作为工具存在的,如果需要提供实时性保障,还需要使用实时系统理论对任务的可调度性和响应时间进行分析,才可以得到科学、系统的响应性保障。