今日简单编写熟悉一下UCOSIII系统的任务创建代码,理解一下OS系统:
并发现以及解决了 Printf 函数卡住 UCOSIII 系统问题解决
文章提供测试代码讲解、完整工程下载、测试效果图
目录
文件结构解释:
任务函数文件:
目前各个文件任务:
#include "main.h"
#include "ComTask.h"
#include "MessageTask.h"
#include "CalculateTask.h"
测试效果图:
完整工程下载:
遇到的问题:
Printf函数卡住UCOSIII系统:
总结了以下尝试:
解决方案:
解决方案效果截图:
文件结构解释:
我的文件结构可能与其余作者的不太一样,我不喜欢在main.c文件中放置太多东西,因此需要先解释一下工程里的一些函数,以及它们的位置在哪:
任务函数文件:
工程包含一个TASK组,里面含有各个任务的实际函数体:
#include "ComTask.h"包含串口相关任务操作
#include "MessageTask.h" 包含信号灯状态等 对外释放信号安排的 相关操作
#include "CalculateTask.h" 包含数据计算任务
目前各个文件任务:
#include "main.h"
创建开始任务初始化三个基本任务:
代码如下:
#include "main.h" int main(void) { OS_ERR err; CPU_SR_ALLOC(); Init_ALL(); OSInit(&err); //初始化UCOSIII OS_CRITICAL_ENTER();//进入临界区 //创建开始任务 OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块 (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 while(1); } //开始任务函数 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(); //进入临界区 //创建ComTask任务 OSTaskCreate((OS_TCB * )&COMTASKTaskTCB, (CPU_CHAR * )"com task", (OS_TASK_PTR )comTask, (void * )0, (OS_PRIO )COMTASK_TASK_PRIO, (CPU_STK * )&COMTASK_TASK_STK[0], (CPU_STK_SIZE)COMTASK_STK_SIZE/10, (CPU_STK_SIZE)COMTASK_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); //创建MessageTask任务 OSTaskCreate((OS_TCB * )&MessageTaskTaskTCB, (CPU_CHAR * )"Message task", (OS_TASK_PTR )MessageTask, (void * )0, (OS_PRIO )MessageTask_TASK_PRIO, (CPU_STK * )&MessageTask_TASK_STK[0], (CPU_STK_SIZE)MessageTask_STK_SIZE/10, (CPU_STK_SIZE)MessageTask_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); //创建CalculateTask任务 OSTaskCreate((OS_TCB * )&CalculateTaskTaskTCB, (CPU_CHAR * )"Calculate task", (OS_TASK_PTR )CalculateTask, (void * )0, (OS_PRIO )CalculateTask_TASK_PRIO, (CPU_STK * )&CalculateTask_TASK_STK[0], (CPU_STK_SIZE)CalculateTask_STK_SIZE/10, (CPU_STK_SIZE)CalculateTask_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_TaskSuspend((OS_TCB*)&StartTaskTCB,&err); //挂起开始任务 OS_CRITICAL_EXIT(); //进入临界区 }
宏定义依赖如下:
#ifndef __main_H #define __main_H #include "headfile.h" //任务优先级 #define START_TASK_PRIO 3 //任务堆栈大小 #define START_STK_SIZE 512 //任务控制块 OS_TCB StartTaskTCB; //任务堆栈 CPU_STK START_TASK_STK[START_STK_SIZE]; //任务函数 void start_task(void *p_arg); //任务优先级 #define COMTASK_TASK_PRIO 6 //任务堆栈大小 #define COMTASK_STK_SIZE 128 //任务控制块 OS_TCB COMTASKTaskTCB; //任务堆栈 CPU_STK COMTASK_TASK_STK[COMTASK_STK_SIZE]; //任务函数定义在#include "ComTask.h" //任务优先级 #define MessageTask_TASK_PRIO 7 //任务堆栈大小 #define MessageTask_STK_SIZE 128 //任务控制块 OS_TCB MessageTaskTaskTCB; //任务堆栈 CPU_STK MessageTask_TASK_STK[MessageTask_STK_SIZE]; //任务函数定义在#include "MessageTask.h" //任务优先级 #define CalculateTask_TASK_PRIO 8 //任务堆栈大小 #define CalculateTask_STK_SIZE 128 //任务控制块 OS_TCB CalculateTaskTaskTCB; //任务堆栈 __align(8) CPU_STK CalculateTask_TASK_STK[CalculateTask_STK_SIZE]; //任务函数定义在#include "CalculateTask.h" #endif
#include "ComTask.h"
串口打印自己运行次数,然后等待800ms:
代码如下:
#include "ComTask.h" //ComTask 打印自己运行次数 然后等待800ms void comTask(void * p_arg) { OS_ERR err; int i=0; p_arg = p_arg; while (DEF_TRUE) { i++; UsartPrintf(USART1, "comTask Print%d\r\n",i); //printf("comTask Print%d\r\n",i); //UsartPrintf(USART1, "%d\r\n",i); OSTimeDlyHMSM(0,0,0,800,OS_OPT_TIME_HMSM_STRICT,&err); //延时800ms } }
注意其中的打印函数有修改,修改后代码如下:
添加了临界段代码保护
//选择串口发送数据--自定义Printf void UsartPrintf (USART_TypeDef *USARTx, char *fmt,...) { // 等待TC标志位被设置,但加入延时以避免长时间阻塞 //int start_tick; // 获取当前系统时钟节拍 //OS_ERR *p_err; unsigned char UsartPrintfBuf[296]; //最大长度296 va_list ap; unsigned char *pStr = UsartPrintfBuf; CPU_SR_ALLOC(); OS_CRITICAL_ENTER();//进入临界区 va_start(ap, fmt); vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap); //格式化 va_end(ap); while(*pStr != 0) { USART_SendData(USARTx, *pStr++); while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET) { // start_tick++; // if(start_tick>5) break; } } OS_CRITICAL_EXIT(); //退出临界区 }
#include "MessageTask.h"
每205ms切换一次灯显示效果,然后打印任务运行次数(每205*4ms)
代码如下:
#include "MessageTask.h" /* 打印任务MessageTask运行次数 分别按顺序 200ms间隔延迟 单独点亮 红绿蓝 LED 最后全亮 同时打印当前灯颜色 */ void MessageTask (void * p_arg) { OS_ERR err; int i=0; p_arg = p_arg; while (DEF_TRUE) { i++; //UsartPrintf(USART1, "MessageTask Print%d\r\n",i); UsartPrintf(USART1, "M %d\r\n",i); //单独亮红灯 LED_RED_L;LED_GREEN_H;LED_Blue_H; OSTimeDlyHMSM(0,0,0,205,OS_OPT_TIME_HMSM_STRICT,&err); //延时205ms //单独亮绿灯 LED_GREEN_L;LED_RED_H;LED_Blue_H; OSTimeDlyHMSM(0,0,0,205,OS_OPT_TIME_HMSM_STRICT,&err); //延时205ms //单独亮蓝灯 LED_Blue_L;LED_RED_H;LED_GREEN_H; OSTimeDlyHMSM(0,0,0,205,OS_OPT_TIME_HMSM_STRICT,&err); //延时205ms //全部点亮 LED_RED_L;LED_GREEN_L;LED_Blue_L; OSTimeDlyHMSM(0,0,0,205,OS_OPT_TIME_HMSM_STRICT,&err); //延时205ms } }
#include "CalculateTask.h"
每100ms打印自己运行次数:
代码如下:
#include "CalculateTask.h" //CalculateTask 打印自己运行次数 然后等待100ms void CalculateTask(void *p_arg) { OS_ERR err; int i=0; p_arg = p_arg; while (DEF_TRUE) { i++; UsartPrintf(USART1, "CalculateTask Print %d\r\n",i); //UsartPrintf(USART1, "C:%d\r\n",i); OSTimeDlyHMSM(0,0,0,100,OS_OPT_TIME_HMSM_STRICT,&err); //延时100ms } }
测试效果图:
从打印情况直观地感受到各个任务运行情况:
ComTask与MessageTask运行周期都是最长的(800ms与820ms)
它们俩次任务之间,最快的CalculateTask运行了 8次
同时灯也有相应的闪变:(这里就不完全截图了)
完整工程下载:
https://download.csdn.net/download/qq_64257614/90139424
遇到的问题:
Printf函数卡住UCOSIII系统:
Printf函数卡住UCOSIII系统,导致每个任务只运行一次就卡死
检查发现是串口发送函数有个有阻塞的等待逻辑在里面:
起初以为是阻塞的等待逻辑的问题,后文发现,其实是临界区代码保护的问题......
后面我还尝试了printf函数,他也会卡住UCOSIII系统:
总结了以下尝试:
最原始的直接轮询等待方式:(只执行一次)
添加了简单超时计数机制:(OS系统正常运行(从LED行为分析),但打印不正常)
这里为了查看comtask运行是否正常,改了一句最简单的串口打印,发现打印有问题,但发送时间戳正确,是800ms一次的
改进尝试读取时钟节拍方式来设定超时:
打印与系统都没正常运行:(此时ComTask都没启动完整)
//选择串口发送数据--自定义Printf void UsartPrintf (USART_TypeDef *USARTx, char *fmt,...) { // 等待TC标志位被设置,但加入延时以避免长时间阻塞 int start_tick; // 获取当前系统时钟节拍 OS_ERR *p_err; unsigned char UsartPrintfBuf[296]; //最大长度296 va_list ap; unsigned char *pStr = UsartPrintfBuf; va_start(ap, fmt); vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap); //格式化 va_end(ap); while(*pStr != 0) { USART_SendData(USARTx, *pStr++); start_tick=OSTimeGet(p_err); //获取当前时钟节拍 do { //大于10节拍 if( OSTimeGet(p_err)-start_tick > 100000) { //超时处理 break; } } while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET); } }
解决方案:
添加进入退出临界段代码,防止printf函数被打断查验标志位的部分:
//选择串口发送数据--自定义Printf void UsartPrintf (USART_TypeDef *USARTx, char *fmt,...) { // 等待TC标志位被设置,但加入延时以避免长时间阻塞 //int start_tick; // 获取当前系统时钟节拍 //OS_ERR *p_err; unsigned char UsartPrintfBuf[296]; //最大长度296 va_list ap; unsigned char *pStr = UsartPrintfBuf; CPU_SR_ALLOC(); OS_CRITICAL_ENTER();//进入临界区 va_start(ap, fmt); vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap); //格式化 va_end(ap); while(*pStr != 0) { USART_SendData(USARTx, *pStr++); while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET) { // start_tick++; // if(start_tick>5) break; } } OS_CRITICAL_EXIT(); //退出临界区 }
解决方案效果截图:
发现ucos 系统运行正常了: