今日学习嵌入式实时操作系统RTOS:UC/OS-III实时操作系统
本文只是个人学习笔记备忘用,附图、描述等 部分都是对网上资料的整合......
文章主要研究如何将UC/OS-III 移植到 STM32 F407VET6上,提供测试工程下载
(2024.5.21 文章未写完,测试有问题,以后再说)
(2024.5.22 系统移植失败,测试有问题,以后再说)
附言:网络上的资料真是参差不齐,整整浪费我一天时间......
目录
UCOS基础知识:
任务的五种状态:
任务五种状态转换图:
UCOS-III的三大列表:
UCOS-III系统配置文件说明:
UC/OS-III移植(STM32F4):
添加 UC/OS-III 源码部分:
修改system_stm32f4xx.s启动文件代码:
修改/确定 系统时钟(SysTick)内核:
修改CONFIG/app_cfg.h:
修改CONFIG/includes.h :
尝试编写简单的 UC/OS-III 任务例程:
先引用头文件:
定义任务栈大小/优先级:
定义任务控制块TCB:
定义任务栈:
定义任务主体函数:
创建任务TASK1-2-3:
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
OS_CRITICAL_EXIT();
启动UCOS III 系统函数:
主函数调用情况展示:
测试效果展示:
整体测试工程下载:
网上学习资料网址贴出:
UCOS基础知识:
任务的五种状态:
任务五种状态转换图:
1、被创建的任务,初始状态均为就绪态
2、被删除的任务,会转为休眠态
3、仅就绪态和中断态可转变成运行态
4、其他状态的任务想运行,必须先转变成就绪
UCOS-III的三大列表:
UCOS-川主要有三大类列表用来跟踪任务状态:
就绪列表 准备运行的任务将放在就绪列表:OSRdyList[x],其中x代表任务优先级数值
Tick列表 正在等待延时超时或挂起的对象超时的任务,将放在OSTickList
挂起列表 当任务等待信号量、事件时,任务将放置在挂起列表PendList
UCOS-III系统配置文件说明:
以下就是我们接下来需要移植的 部分文件,他们的作用大致各自如下:
UC/OS-III移植(STM32F4):
本次尝试移植UC/OS-III 于立创梁山派天空星开发板上,芯片型号是STM32F407VET6
其中UC/OS-III 的源码可以在整体工程下载中的压缩包内找到
注意:这里的源码是被我阉割过的,削除了官方文件中不必要的文件与目录
其次就是网络上那些所谓提供UC/OS源码或者教程的,如果没有移植成功的工程供下载的案例,基本都是垃圾!浪费时间!不是缺少文件,就是解释不详细,缺步漏步!
本人也是在艰难的学习中掉进太多移植源码方面的坑里了.........
添加 UC/OS-III 源码部分:
在工程中新建几个分组:
uC-OS3/CPU
uC-OS3/LIB
uC-OS3/PORT
uC-OS3/SOURCE
uC-OS3/CONFIG
点击uC-OS3/CPU–>Add Files
UC-OSIII/CPU添加以下文件,如果只查找到一个,请将文件类型(I)选为 ALL files(''.'')
点击uC-OS3/CPU–>Add Files
UCOSIII\bsp添加以下文件,如果只查找到一个,请将文件类型(I)选为 ALL files(''.'')
点击uC-OS3/LIB–>Add Files
UCOSIII\uC-LIB添加以下所有文件,如果只查找到一个,请将文件类型(I)选为 ALL files(''.'')
点击uC-OS3/PORT–>Add Files
UCOSIII\uCOS-III\Ports添加以下所有文件,如果只查找到一个,请将文件类型(I)选为 ALL files(''.'')
点击uC-OS3/SOURCE–>Add Files
UCOSIII\uCOS-III\Source添加以下所有文件,如果只查找到一个,请将文件类型(I)选为 ALL files(''.'')
uC-OS3/CONFIG添加文件:
UCOSIII\config添加以下所有文件,如果只查找到一个,请将文件类型(I)选为 ALL files(''.'')
最后别忘记在魔棒中添加各个文件路径:
补:补充添加一条bsp的路径,之前忘记添加了,导致报错......
添加结束,编译看看有无报错缺漏:
这里也是完美无报错的典范了哈哈哈......
修改system_stm32f4xx.s启动文件代码:
打开工程自带的 system_stm32f4xx.s启动文件(这是启动文件,不是UC/OS源码!)
我们需要对其进行一些修改:
1
第80行框出代码修改:
DCD OS_CPU_PendSVHandler ; PendSV Handler DCD OS_CPU_SysTickHandler ; SysTick Handler
第220行框出代码修改:
OS_CPU_PendSVHandler\ PROC EXPORT OS_CPU_PendSVHandler [WEAK] B . ENDP OS_CPU_SysTickHandler\ PROC EXPORT OS_CPU_SysTickHandler [WEAK] B . ENDP
最后编译检查无问题:
修改/确定 系统时钟(SysTick)内核:
这里我还特意出去学习了一下系统内核时钟的初始化等知识:并附文:
STM32F407VET6 学习笔记3:内核定时器SystemTick(SysTick)初始化中断-CSDN博客
/**
* This function will initial stm32 board.
*/
void board_init(void)
{
/* NVIC Configuration */
#define NVIC_VTOR_MASK 0x3FFFFF80
#ifdef VECT_TAB_RAM
/* Set the Vector Table base location at 0x10000000 */
SCB->VTOR = (0x10000000 & NVIC_VTOR_MASK);
#else /* VECT_TAB_FLASH */
/* Set the Vector Table base location at 0x08000000 */
SCB->VTOR = (0x08000000 & NVIC_VTOR_MASK);
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); //Systick 时钟源频率168M
// 计算SysTick重装载值(SystemCoreClock为168MHz,希望SysTick中断频率为1ms(1000 Hz))
//SysTick_LOAD = (SystemCoreClock / TickRate) - 1
uint32_t reload = SystemCoreClock / 1000 - 1; // 1ms中断频率
SysTick->LOAD = reload;
// 清除SysTick当前值并启动SysTick,同时使能中断
SysTick->VAL = 0; // 清空当前值
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
/* 设置NVIC优先级分组 */
// 4位抢占优先级和0位子优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* 设置SysTick中断的优先级 */
// 设置SysTick的抢占优先级为3(最低),没有子优先级(因为分组4没有子优先级位)
NVIC_SetPriority(SysTick_IRQn, 3); // 注意:优先级值根据分组设置可能有所不同
}
// 声明SysTick中断处理函数
void SysTick_Handler(void)
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
修改CONFIG/app_cfg.h:
#define APP_CFG_SERIAL_EN DEF_ENABLED ------>
#define APP_CFG_SERIAL_EN DEF_DISABLED
改后如下:
#define APP_TRACE BSP_Ser_Printf --------> #define APP_TRACE (void)
修改CONFIG/includes.h :
尝试编写简单的 UC/OS-III 任务例程:
先引用头文件:
/*****************UC/OS头文件***************/
#include "os.h"
#include "os_cpu_bsp.h"
#include <app_cfg.h>
#include <cpu_core.h>
#include <os_app_hooks.h>
#include <cpu.h>
/* USER CODE END Includes */
定义任务栈大小/优先级:
//任务栈大小定义 #define START_STK_SIZE 128 #define TASK1_STK_SIZE 128 #define TASK2_STK_SIZE 128 #define TASK3_STK_SIZE 128
//任务优先级定义 #define APP_TASK_START_PRIO 4 #define APP_TASK_1_PRIO 5 #define APP_TASK_2_PRIO 6 #define APP_TASK_3_PRIO 7
定义任务控制块TCB:
//创建任务控制块 static OS_TCB Start_Task_TCB; static OS_TCB Task1_TCB; static OS_TCB Task2_TCB; static OS_TCB Task3_TCB;
定义任务栈:
//任务堆栈 CPU_STK START_TASK_STK[START_STK_SIZE]; CPU_STK TASK1_TASK_STK[TASK1_STK_SIZE]; CPU_STK TASK2_TASK_STK[TASK2_STK_SIZE]; CPU_STK TASK3_TASK_STK[TASK3_STK_SIZE];
定义任务主体函数:
这个放置在主函数下面吧就:
任务里面一定要有阻塞延时,如果这个任务的优先级最高且没有阻塞延时,那么操作系统就只会执行这一个任务,其他的任务就得不到执行。
注意:这里的 OSTimeDly ( 2000, OS_OPT_TIME_DLY, & err ); 对应的延时 不仅与 内传的参数 有关 还与 内核定时器SystemTick 有关:
创建任务TASK1-2-3:
各个TASK的任务如下描述:
//TASK_1 报告自己执行次数(2s),并在执行8次后删除 TASK_2
//TASK_2 串口报告自己执行的次数 (4s)
//这里的 TASK_3 会每隔 17S 恢复TASK_2 (全新的开始)
/*
创建一个全新的任务实例,而不是简单地“恢复”一个已经被删除的任务。
这意味着新的任务实例将拥有自己独立的堆栈(stack)和其他资源,而原来的任务实例(如果已经被删除)的资源将被释放
*/
//TASK_1 报告自己执行次数(2s),并在执行8次后删除 TASK_2
void TASK_1(void)
{
OS_ERR err; // 定义一个“错误” 变量用来存放一些错误的类型
int TASK1_num=0; //记录任务TASK_1执行次数
//在某些嵌入式系统中,进入和退出关键区域可能需要禁用中断,以防止在关键代码执行过程中被中断打断。
CPU_SR_ALLOC(); //为保存和恢复CPU的状态寄存器(Status Register)或中断状态做准备工作。
OS_CRITICAL_ENTER();
/* 此处添加不希望被打断的硬件初始化代码等......*/
OS_CRITICAL_EXIT();
while(1)
{
TASK1_num++; //任务TASK_1执行次数加1
if(TASK1_num%8==0)
{
OSTaskDel((OS_TCB*)&Task2_TCB,&err); //任务1每执行8次后(即16s时) 删除掉任务2
printf("TASK_1 has Deleted the TASK_2 !\r\n");//打印报告 任务1 删除了 任务2
}
UsartPrintf(USART1,"TASK_1 has Carred out %d times! \r\n",TASK1_num); //打印测试字符串(并报告TASK_1执行次数)
OSTimeDly ( 2000, OS_OPT_TIME_DLY, & err ); //使当前任务延迟指定的时间(2s): (让当前任务放弃CPU一段时间,CPU让给其余任务)
}
}
//TASK_2 串口报告自己执行的次数 (4s)
void TASK_2(void)
{
OS_ERR err; // 定义一个“错误” 变量用来存放一些错误的类型
int TASK2_num=0; //记录任务TASK_2执行次数
while(1)
{
UsartPrintf(USART1,"TASK_2 has Carred out %d times! \r\n",TASK2_num); //打印测试字符串(并报告TASK_2执行次数)
OSTimeDly ( 4000, OS_OPT_TIME_DLY, & err );//使当前任务延迟指定的时间(4S): (让当前任务放弃CPU一段时间,CPU让给其余任务)
}
}
//这里的 TASK_3 会每隔 17S 恢复TASK_2 (全新的开始)
/*
创建一个全新的任务实例,而不是简单地“恢复”一个已经被删除的任务。
这意味着新的任务实例将拥有自己独立的堆栈(stack)和其他资源,而原来的任务实例(如果已经被删除)的资源将被释放
*/
void TASK_3(void)
{
OS_ERR err; // 定义一个“错误” 变量用来存放一些错误的类型
int TASK3_num=0; //记录任务TASK_3执行次数
CPU_SR_ALLOC();
OS_CRITICAL_ENTER(); //进入临界区
//重新创建TASK2任务
OSTaskCreate((OS_TCB * )&Task2_TCB,
(CPU_CHAR * )"TASK_2",
(OS_TASK_PTR )TASK_2,
(void * )0,
(OS_PRIO )TASK2_PRIO,
(CPU_STK * )&TASK2_TASK_STK[0],
(CPU_STK_SIZE)TASK2_STK_SIZE/10,
(CPU_STK_SIZE)TASK2_STK_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);
OS_CRITICAL_EXIT(); //退出临界区
while(1)
{
UsartPrintf(USART1,"TASK_3 has Resume the TASK_2 %d times! \r\n",TASK3_num); //打印测试字符串(并报告TASK_3执行次数)
OSTimeDly ( 17000, OS_OPT_TIME_DLY, & err );//使当前任务延迟指定的时间(17S): (让当前任务放弃CPU一段时间,CPU让给其余任务)
}
}
//开始任务任务函数
void start_task(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
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 //当使用时间片轮转的时候
//使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
OS_CRITICAL_ENTER(); //进入临界区
//创建TASK1任务
OSTaskCreate((OS_TCB * )&Task1_TCB, //任务控制块
(CPU_CHAR * )"TASK_1", //任务名字
(OS_TASK_PTR )TASK_1, //任务函数
(void * )0, //传递给任务函数的参数
(OS_PRIO )TASK1_PRIO,
(CPU_STK * )&TASK1_TASK_STK[0],
(CPU_STK_SIZE)TASK1_STK_SIZE/10,
(CPU_STK_SIZE)TASK1_STK_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任务
OSTaskCreate((OS_TCB * )&Task2_TCB,
(CPU_CHAR * )"TASK_2",
(OS_TASK_PTR )TASK_2,
(void * )0,
(OS_PRIO )TASK2_PRIO,
(CPU_STK * )&TASK2_TASK_STK[0],
(CPU_STK_SIZE)TASK2_STK_SIZE/10,
(CPU_STK_SIZE)TASK2_STK_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);
//创建TASK3任务
OSTaskCreate((OS_TCB * )&Task3_TCB,
(CPU_CHAR * )"TASK_3",
(OS_TASK_PTR )TASK_3,
(void * )0,
(OS_PRIO )TASK3_PRIO,
(CPU_STK * )&TASK3_TASK_STK[0],
(CPU_STK_SIZE)TASK3_STK_SIZE/10,
(CPU_STK_SIZE)TASK3_STK_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);
OS_CRITICAL_EXIT(); //退出临界区
OSTaskDel((OS_TCB*)0,&err); //删除start_task任务自身
}
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
OS_CRITICAL_EXIT();
//在某些嵌入式系统中,进入和退出关键区域可能需要禁用中断,以防止在关键代码执行过程中被中断打断。
CPU_SR_ALLOC(); //为保存和恢复CPU的状态寄存器(Status Register)或中断状态做准备工作。
/*
OS_CRITICAL_ENTER();
这个函数通常用于进入一个关键区域。在进入关键区域之前,
它可能会禁用中断(如果之前通过CPU_SR_ALLOC();已经做了相关准备),
或者通过其他机制(如锁)来确保没有其他线程或中断可以访问当前线程正在使用的共享资源。
通过禁用中断或获得锁,OS_CRITICAL_ENTER();
确保了代码的关键部分在执行时不会被其他任务或中断打断,
从而保证了数据的一致性和操作的原子性。
*/
OS_CRITICAL_ENTER();
/*
OS_CRITICAL_EXIT();
这个函数用于退出之前由OS_CRITICAL_ENTER();进入的关键区域。
在退出关键区域时,它可能会重新启用之前被禁用的中断,或者释放之前获得的锁。
这样做允许其他任务或中断再次访问之前被保护的共享资源
*/
OS_CRITICAL_EXIT();
启动UCOS III 系统函数:
//启动UCOS III 系统 !
void UCOS_III_init(void)
{
OS_ERR err;
CPU_SR_ALLOC();
OSInit(&err); //初始化UCOSIII
OS_CRITICAL_ENTER(); //进入临界区
//创建开始任务
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_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY )0, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
(OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度,
(void * )0, //用户补充的存储区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
(OS_ERR * )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSIII
}
主函数调用情况展示:
网上学习资料网址贴出:
第3讲 UCOS基础知识_哔哩哔哩_bilibili
uCosII移植STM32F407教程_stm32f407 ucos-CSDN博客
基于stm32cubemx移植uC/OS-III操作系统_cubemx ucos-CSDN博客