本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发
向上代码兼容GD32F450ZGT6中使用
后续项目主要在下面该专栏中发布:
https://blog.csdn.net/qq_62316532/category_12608431.html?spm=1001.2014.3001.5482
感兴趣的点个关注收藏一下吧!
电机驱动开发可以跳转:
GD32F103RCT6/GD32F303RCT6-实战项目-无刷电机驱动(1)_gd32f103rct6例程-CSDN博客
BMS电源系统开发可以跳转:
暂未放链接
DCDC-双向BUCK-BOOST实战链接:
向上代码兼容GD32F303RCT6中使用
本项目配套开发板:
基于GD32F103RCT6国产GD32平台,以下教程编写基于该开发板
图片:
参考资料:
《UCOS-III开发指南_V1.5》
《µC/OS-III Documentation》
《ARM Cortex-M3 与 Cortex-M4 权威指南(第 3 版)》
《uCOS-III内核实现与应用开发实战指南—基于STM32》
介绍:
在上一节中我们完成了在只有一个UCOSIII官方源码以及GD32新建工程的情况下,对UCOSIII进行复刻,上一节以移植为主,所以我们并没有在主函数中加载任务函数,程序实际上只是个空架子,那么接下来这章,让我们来对UCOSIII的任务函数进行编写,先来点亮一个LED,有一点点小的成果吧!
外设:
既然要点亮一个LED,就需要我们对LED进行相关的初始化和操作了,我们在前面的裸机实验中已经做过这些了,就不需要进行重复操作,直接拿过来使用就行,详细的可以跳转到这个链接里面去:
GD32F103RCT6/GD32F303RCT6(3.2)GPIO外设使用-实验编程
不知道大家记不记得,我们在第一次编写我们的LED到后面几次实验过程中,就已经在对我们的外设库进行不断丰富了,那么我们也可以本次实验从我们的外设库中取文件:
后续操作系统会有很多任务以及使用到非常多的外设,所以为了方便阅读和整理,我们将所有的外设初始化统一放置在void BSP_Init (void)中
打开bsp.c
把BSP_LED加入库
头文件路径包含
在bsp.h中,加入led所需要的头文件,
添加初始化文件:
建立任务函数:
定义任务栈
本小结我们将会建立任务分别点亮板载上的几颗LED,如果只建立了一个任务,那么当该任务进入延时的时候,因为系统没有其他任务正处于就绪态,系统会自动进入优先级最低的空闲任务中;有人会问为什么会存在一个空闲任务?因为ucosiii在整个系统中,必须保证有一个任务在运行,空闲任务就是为此量身定制的,针对整个系统没有就绪任务的情景,而当用户编写的任务延时到期以后,因为空闲任务的任务是最低的,就会又从空闲任务切换回用户任务。
在uC/OS-III系统中,每个任务确实拥有独立的栈空间,保证了任务之间的隔离性和数据安全性。任务栈不仅保存任务的局部变量、函数参数等数据,还保存任务执行时的CPU寄存器状态,使得任务在被抢占或主动挂起后,能够准确地恢复到任务之前的运行状态并且在此基础上继续执行。栈的大小直接影响着任务能保存状态的复杂度和深度,如递归调用的深度、局部变量的数量等。
静态分配栈空间意味着栈的大小在编译时就已经固定,在设计任务时需要预估每个任务可能需要的最大栈空间。一旦任务的实际运行时栈需求超过了一开始预分配的大小,就会导致栈溢出的情况发生,这是嵌入式系统中常见的错误,会导致程序崩溃或等严重错误发生。
栈的大小设定过大会浪费宝贵的RAM资源,特别是在资源有限的嵌入式系统中,因此合理估算任务栈的大小是非常重要的。
定义任务控制块
任务控制块又被称为是任务的身份证,其C语言表示就是包含任务信息的结构体。
定义任务主体函数
任务本体实际上就是一个无限循环并且无返回值的C语言函数,任务必须是死循环,否则任务就会通过LR返回,如果 LR 指向 了非法的内存就会产生 HardFault_Handler , 而 uCOS 指向一个 任务退出函数 OS_TaskReturn(),它如果支持任务删除的话,则进行任务删除操作,否则就进入死循环中,这样子的任务是不安全的,所以避免这种情况,任务一般都是死循环并且无返回值的,只 执行一次的任务在执行完毕要记得及时删除。
任务里面的延时函数必须使用 uCOS 里面提供的阻塞延时函数,并不能使用我们裸机编程中的那种延时。这两种的延时的区别是 uCOS 里面的延时是阻塞延时,即调用 OSTimeDly()函数的时候,当前任务会被挂起,调度器会切换到其它就绪的任务,从而实现多任务。如果还是使用裸机编程中的那种延时,那么整个任务就成为了一个死循环,如果恰好该任务的优先级是最高的,那么系统永远都是在这个任务中运行,比它优先级更低的任务无法运行,根本无法实现多任务,因此任务中必须有能阻塞任务的函数,才能切换到其他任务中。
本次实验我们创建一个LED延时闪烁
static void LED_Task (void* parameter) { while (1) { LED1_ON; OSTimeDly (1000,OS_OPT_TIME_DLY,&err); /* 延时 1000 个 tick */ LED1_OFF; OSTimeDly (1000,OS_OPT_TIME_DLY,&err); } }
创建任务
一个任务的三要素是任务主题函数、任务栈、任务控制块,UCOSIII中通过任务创建函数OSTaskCreate(),将三者联系在一起,让任务能够在创建后被系统使用;
OSTaskCreate( (OS_TCB *)&AppTaskStartTCB, 任务控制块,由用户自己定义。 (CPU_CHAR *)"App Task Start", 任务名字,字符串形式,这里任务名字最好要与任务函数入口名字 一致,方便进行调试。 (OS_TASK_PTR ) AppTaskStart, 任务入口函数,即任务函数的名称,需要我们自己定义并且实现。 (void *) 0, 任务入口函数形参,不用的时候配置为 0 或者 NULL 即可,p_arg 是指向可选数据区域的指针,用于将参数传递给任务,因为任务一旦执行,那必须是在一 个死循环中,所以传参只在首次执行时有效。 (OS_PRIO ) APP_TASK_START_PRIO, 任务的优先级,由用户自己定义。 (CPU_STK *)&AppTaskStartStk[0], 指向堆栈基址的指针(即堆栈的起始地址)。 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10, 设置堆栈深度的限制位置。这个值表示任务的堆栈满溢之前剩余 的堆栈容量。例如,指定 stk_size 值的 10%表示将达到堆栈限制,当堆栈达到 90%满就表 示任务的堆栈已满。 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE, 任务堆栈大小,单位由用户决定,如果 CPU_STK 被设置为 CPU_INT08U,则单位为字节,而如果 CPU_STK 被设置为 CPU_INT16U,则单位为半字, 同理,如果 CPU_STK 被设置为 CPU_INT32U,单位为字。在 32 位的处理器下(GD32), 一个字等于 4 个字节,那么任务大小就为 APP_TASK_START_STK_SIZE * 4 字节。 (OS_MSG_QTY ) 5u, 设置可以发送到任务的最大消息数,按需设置即可。 (OS_TICK ) 0u, 在任务之间循环时的时间片的时间量(以滴答为单位)。指定 0 则使用默认值。 (void *) 0, 是指向用户提供的内存位置的指针,用作 TCB 扩展。例如,该用 户存储器可以保存浮点寄存器的内容在上下文切换期间,每个任务执行的时间,次数、任 务已经切换等。 (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err 用于保存返回的错误代码。 );
在uC/OS-III实时操作系统中,
OS_OPT
是一个枚举类型,用于定义各种操作系统的配置选项。OS_OPT_TASK_STK_CHK
和OS_OPT_TASK_STK_CLR
是这个枚举类型中的两个选项,它们与任务(Task)栈的操作相关。当在创建任务时使用这些选项,可以控制uC/OS-III如何初始化和管理任务的堆栈。下面是对这两个选项的具体解释:
OS_OPT_TASK_STK_CHK (任务栈检查):
- 这个选项启用了一个安全性特性,即在任务创建时,uC/OS-III会检查任务堆栈是否被正确地初始化了。它通过写入一个特定的模式到堆栈区域,然后检查这些模式是否仍然存在来验证堆栈没有被其他代码意外地使用。如果检测到堆栈被破坏,系统可以避免使用这个损坏的堆栈,从而预防潜在的运行时错误。这对于调试阶段特别有用,可以帮助开发者发现堆栈溢出等问题。
OS_OPT_TASK_STK_CLR (任务栈清零):
- 当启用这个选项时,uC/OS-III会在任务创建之初将整个任务堆栈清零。这不仅是一种安全措施,可以消除未初始化数据带来的不确定性,同时也便于调试,因为清零的堆栈使得在调试时更容易识别已使用的堆栈区域。清零堆栈对于安全关键型应用尤其重要,因为它减少了潜在的随机数据导致的错误。
因此,当你在创建任务时使用
(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR)
,意味着你要求uC/OS-III在任务初始化过程中既检查堆栈的有效性,也清零整个堆栈,以此增强系统的健壮性和可调试性。这是一种推荐的做法,尤其是在开发初期和调试阶段,尽管它可能会略微增加一些启动时间。
启动任务当任务创建好后,是处于任务就绪,在就绪态的任务可以参与操作系统的调度。任务
调度器只启动一次,之后就不会再次执行了,uCOS 中启动任务调度器的函数是 OSStart(),
并且启动任务调度器的时候就不会返回,从此任务都由 uCOS 管理,此时才是真正进入实
时操作系统中的第一步。OSStart(&err);
app.c
自此,将以上全部整合到一起以后就会得到我们本次实验的主体:
#include <includes.h>
/*
*************************************************************************
* 任务栈存放区域
*************************************************************************
*/
static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];
static CPU_STK AppTaskLed1Stk [ APP_TASK_LED1_STK_SIZE ];
static CPU_STK AppTaskLed2Stk [ APP_TASK_LED2_STK_SIZE ];
static CPU_STK AppTaskLed3Stk [ APP_TASK_LED3_STK_SIZE ];
/*
*************************************************************************
* 定义任务控制块存放区域
*************************************************************************
*/
static OS_TCB AppTaskStartTCB;
static OS_TCB AppTaskLed1TCB;
static OS_TCB AppTaskLed2TCB;
static OS_TCB AppTaskLed3TCB;
/*
*************************************************************************
* 函数原型存放区域
*************************************************************************
*/
static void AppTaskStart (void *p_arg);
static void AppTaskLed1 ( void * p_arg );
static void AppTaskLed2 ( void * p_arg );
static void AppTaskLed3 ( void * p_arg );
/*
*************************************************************************
* main 函数
*************************************************************************
*/
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
OS_ERR err;
OSInit(&err);
OSTaskCreate((OS_TCB *)&AppTaskStartTCB,
(CPU_CHAR *)"App Task Start",
(OS_TASK_PTR ) AppTaskStart,
(void *) 0,
(OS_PRIO ) APP_TASK_START_PRIO,
(CPU_STK *)&AppTaskStartStk[0],
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSStart(&err);
}
/*
*************************************************************************
* 空闲任务存放区域
*************************************************************************
*/
static void AppTaskStart (void *p_arg)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
OS_ERR err;
(void)p_arg;
BSP_Init(); /* Initialize BSP functions
*/
CPU_Init();
cpu_clk_freq = BSP_CPU_ClkFreq(); /* Determine SysTick reference
freq. */
cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; /* Determine
nbr SysTick increme nts */
OS_CPU_SysTickInit(cnts); /*Init uC/OS periodic time src(SysTick).*/
Mem_Init(); /* Initialize Memory Management Module
*/
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); /* Compute CPU capacity with no task
running */
#endif
CPU_IntDisMeasMaxCurReset();
OSTaskCreate((OS_TCB *)&AppTaskLed1TCB,/*Create the Led1 task */
(CPU_CHAR *)"App Task Led1",
(OS_TASK_PTR ) AppTaskLed1,
(void *) 0,
(OS_PRIO ) APP_TASK_LED1_PRIO,
(CPU_STK *)&AppTaskLed1Stk[0],
(CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSTaskCreate((OS_TCB *)&AppTaskLed2TCB, /*Create the Led2 task*/
(CPU_CHAR *)"App Task Led2",
(OS_TASK_PTR ) AppTaskLed2,
(void *) 0,
(OS_PRIO ) APP_TASK_LED2_PRIO,
(CPU_STK *)&AppTaskLed2Stk[0],
(CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSTaskCreate((OS_TCB *)&AppTaskLed3TCB, /*Create the Led3 task*/
(CPU_CHAR *)"App Task Led3",
(OS_TASK_PTR ) AppTaskLed3,
(void *) 0,
(OS_PRIO ) APP_TASK_LED3_PRIO,
(CPU_STK *)&AppTaskLed3Stk[0],
(CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE / 10,
(CPU_STK_SIZE) APP_TASK_LED3_STK_SIZE,
(OS_MSG_QTY ) 5u,
(OS_TICK ) 0u,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSTaskDel ( & AppTaskStartTCB, & err );
}
/*
********************************************************************
* LED1 TASK
********************************************************************
*/
static void AppTaskLed1 ( void * p_arg )
{
OS_ERR err;
(void)p_arg;
while (DEF_TRUE) { /* Task body, always written as an infinite
loop.*/
LED1_TOG;
OSTimeDly ( 1000, OS_OPT_TIME_DLY, & err );
}
}
/*
***********************************************************************
* LED2 TASK
************************************************************************
*/
static void AppTaskLed2 ( void * p_arg )
{
OS_ERR err;
(void)p_arg;
while (DEF_TRUE) { /* Task body, always written as an
infinite loop. */
LED2_TOG;
OSTimeDly ( 5000, OS_OPT_TIME_DLY, & err );
}
}
/*
**********************************************************************
* LED3 TASK
**********************************************************************
*/
static void AppTaskLed3 ( void * p_arg )
{
OS_ERR err;
(void)p_arg;
while (DEF_TRUE) { /* Task body, always written as an infinite
loop. */
LED3_TOG;
OSTimeDly ( 10000, OS_OPT_TIME_DLY, & err );
}
}
while (DEF_TRUE)的作用
在uC/OS-III或类似的嵌入式操作系统环境中,
while (DEF_TRUE)
这样的结构通常用于创建一个无限循环的任务执行体。
DEF_TRUE 的含义:
DEF_TRUE
是一个宏定义,代表逻辑真(通常就是1
),它是用来明确表示条件始终为真的一个标志。在C语言中,非零值被视为真,所以用DEF_TRUE
作为循环条件确保循环永远不会自然终止,除非有外部干预(比如中断、任务挂起、删除任务等)。无限循环的作用:
- 在实时操作系统(RTOS)的任务设计中,大多数任务都是以无限循环的形式编写,这是因为每个任务需要持续执行其指定的功能,直到被操作系统终止或挂起。这样的设计允许任务周期性地执行某些操作,比如不断检查传感器读数、处理数据、响应事件或与其他任务通信(如通过消息队列)。
循环体内的操作:
- 这个循环确保这个过程重复进行,构成了任务的主要逻辑。延迟函数调用让出了CPU控制权,允许其他优先级相同或更高的任务有机会执行,体现了RTOS的多任务调度特点。
资源管理和效率:
- 使用无限循环而不是让任务自然结束,可以有效管理任务资源。一旦任务执行完毕不再进入循环,如果没有适当的管理机制,操作系统可能不得不频繁创建和销毁任务,这在资源有限的嵌入式系统中是不切实际且低效的。
其中,我们需要在app_cfg.h中定义三个任务所需要的变量:
包括优先级和栈大小,三个任务的优先级一样则共享默认的时间片
编译下载
下载,发现实验现象与实际一致,完成本次设计!
群号:621154399
有问题欢迎大家加入我们一起交流,这个群是开源性技术交流群。