(学习日记)2024.04.14:UCOSIII第四十二节:任务信号量

2024-04-12 15:52

本文主要是介绍(学习日记)2024.04.14:UCOSIII第四十二节:任务信号量,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

2024.04.14:UCOSIII第四十二节:任务信号量

  • 五十六、UCOSIII:任务信号量
    • 1、任务信号量的基本概念
    • 2、任务信号量的函数接口讲解
      • 1. 任务信号量释放函数OSTaskSemPost()
      • 2. 获取任务信号量函数OSTaskSemPend()
    • 3、任务信号量实验
      • 1、任务信号量代替二值信号量实验
      • 2. 任务信号量代替二值信号量实验现象
      • 3. 任务信号量代替计数信号量实验
      • 4. 任务信号量代替计数信号量实验

五十六、UCOSIII:任务信号量

1、任务信号量的基本概念

μC/OS提供任务信号量这个功能,每个任务都有一个32位(用户可以自定义位宽,我们使用32位的CPU,此处就是32位)的信号量值SemCtr。
这个信号量值是在任务控制块中包含的,是任务独有的一个信号量通知值,在大多数情况下,任务信号量可以替代内核对象的二值信号量、计数信号量等。

注:本章主要讲解任务信号量,而非内核对象信号量,如非特别说明,本章中的信号量都指的是内核对象信号量。前面所讲的信号量是单独的内核对象, 是独立于任务存在的;本章要讲述的任务信号量是任务特有的属性,紧紧依赖于一个特定任务。

相对于前面使用μC/OS内核通信的资源,必须创建二进制信号量、计数信号量等情况,使用任务信号量显然更灵活。
因为使用任务信号量比通过内核对象信号量通信方式解除阻塞的任务的速度要快,并且更加节省RAM内存空间,任务信号量的使用无需单独创建信号量。

通过对任务信号量的合理使用,可以在一定场合下替代μC/OS的信号量,用户只需向任务内部的信号量发送一个信号而不用通过外部的信号量进行发送, 这样子处理就会很方便并且更加高效。
当然,凡事都有利弊,不然的话μC/OS还要内核的IPC通信机制干嘛,任务信号量虽然处理更快,RAM开销更小, 但也有限制:只能有一个任务接收任务信号量,因为必须指定接收信号量的任务,才能正确发送信号量;而内核对象的信号量则没有这个限制, 用户在释放信号量,可以采用广播的方式,让所有等待信号量的任务都获取到信号量。

在实际任务间的通信中,一个或多个任务发送一个信号量给另一个任务是非常常见的,而一个任务给多个任务发送信号量的情况相对比较少。 这种情况就很适合采用任务信号量进行传递信号,如果任务信号量可以满足设计需求,那么尽量不要使用普通信号量,这样子设计的系统会更加高效。

任务信号量的运作机制与普通信号量一样,没什么差别。

2、任务信号量的函数接口讲解

1. 任务信号量释放函数OSTaskSemPost()

函数 OSTaskSemPost()用来释放任务信号量,虽然只有拥有任务信号量的任务才可以等待该任务信号量, 但是其他所有的任务或者中断都可以向该任务释放信号量,其源码具体如下:

OS_SEM_CTR  OSTaskSemPost (OS_TCB  *p_tcb,          (1)     //目标任务OS_OPT   opt,       (2)     //选项OS_ERR  *p_err)     (3)     //返回错误类型
{OS_SEM_CTR  ctr;CPU_TS      ts;#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测if (p_err == (OS_ERR *)0)           //如果 p_err 为空{OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数return ((OS_SEM_CTR)0);         //返回0(有错误),停止执行}
#endif#if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测功能switch (opt)                            //根据选项分类处理{case OS_OPT_POST_NONE:              //如果选项在预期之内case OS_OPT_POST_NO_SCHED:break;                         //跳出default:                            //如果选项超出预期*p_err =  OS_ERR_OPT_INVALID;   //错误类型为“选项非法”return ((OS_SEM_CTR)0u);       //返回0(有错误),停止执行}
#endifts = OS_TS_GET();                                      //获取时间戳#if OS_CFG_ISR_POST_DEFERRED_EN > 0u//如果启用了中断延迟发布if (OSIntNestingCtr > (OS_NESTING_CTR)0)  //如果该函数是在中断中被调用{OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_TASK_SIGNAL,//将该信号量发布到中断消息队列(void      *)p_tcb,(void      *)0,(OS_MSG_SIZE)0,(OS_FLAGS   )0,(OS_OPT     )0,(CPU_TS     )ts,(OS_ERR    *)p_err);    (4)return ((OS_SEM_CTR)0);                           //返回0(尚未发布)}
#endifctr = OS_TaskSemPost(p_tcb,                  //将信号量按照普通方式处理opt,ts,p_err);             (5)return (ctr);                                 //返回信号的当前计数值
}
  • (1):目标任务控制块指针,指向要释放任务信号量的任务。
  • (2):释放任务信号量的选项。
  • (3):用于返回保存错误代码。
  • (4):如果启用了中断延迟发布, 并且该函数在中断中被调用,那就将信号量发布到中断消息队列,由中断消息队列发布任务信号量。
  • (5):调用OS_TaskSemPost ()函数将信号量发布到任务中, 其源码具体如下
OS_SEM_CTR  OS_TaskSemPost (OS_TCB  *p_tcb,         (1)     //目标任务OS_OPT   opt,           (2)     //选项CPU_TS   ts,            (3)     //时间戳OS_ERR  *p_err)         (4)     //返回错误类型
{OS_SEM_CTR  ctr;CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器// SR(临界段关中断只需保存SR),开中断时将该值还原。OS_CRITICAL_ENTER();                               //进入临界段if (p_tcb == (OS_TCB *)0)             (5)//如果 p_tcb 为空{p_tcb = OSTCBCurPtr;                   //将任务信号量发给自己(任务)}p_tcb->TS = ts;                            //记录信号量被发布的时间戳*p_err     = OS_ERR_NONE;                           //错误类型为“无错误”switch (p_tcb->TaskState)              (6)//跟吴目标任务的任务状态分类处理{case OS_TASK_STATE_RDY:                        //如果目标任务没有等待状态case OS_TASK_STATE_DLY:case OS_TASK_STATE_SUSPENDED:case OS_TASK_STATE_DLY_SUSPENDED:               (7)switch (sizeof(OS_SEM_CTR)){                                           //判断是否将导致该信case 1u:                                     //号量计数值溢出,如if (p_tcb->SemCtr == DEF_INT_08U_MAX_VAL)   //果溢出,则开中断,{OS_CRITICAL_EXIT();                     //返回错误类型为“计*p_err = OS_ERR_SEM_OVF;                //数值溢出”,返回0return ((OS_SEM_CTR)0);                 //(有错误),不继续}                                           //执行。break;case 2u:if (p_tcb->SemCtr == DEF_INT_16U_MAX_VAL){OS_CRITICAL_EXIT();*p_err = OS_ERR_SEM_OVF;return ((OS_SEM_CTR)0);}break;case 4u:if (p_tcb->SemCtr == DEF_INT_32U_MAX_VAL){OS_CRITICAL_EXIT();*p_err = OS_ERR_SEM_OVF;return ((OS_SEM_CTR)0);}break;default:break;}p_tcb->SemCtr++;                (8)//信号量计数值不溢出则加1ctr = p_tcb->SemCtr;            (9)//获取信号量的当前计数值OS_CRITICAL_EXIT();                           //退出临界段break;                                        //跳出case OS_TASK_STATE_PEND:                           //如果任务有等待状态case OS_TASK_STATE_PEND_TIMEOUT:case OS_TASK_STATE_PEND_SUSPENDED:case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:(10)if (p_tcb->PendOn == OS_TASK_PEND_ON_TASK_SEM)      //如果正等待任务信号量{OS_Post((OS_PEND_OBJ *)0,                //发布信号量给目标任务(OS_TCB      *)p_tcb,(void        *)0,(OS_MSG_SIZE  )0u,(CPU_TS       )ts);             (11)ctr = p_tcb->SemCtr;                   //获取信号量的当前计数值OS_CRITICAL_EXIT_NO_SCHED();           //退出临界段(无调度)if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0)   //如果选择了调度任务{OSSched();                    (12)//调度任务}}else//如果没等待任务信号量{switch (sizeof(OS_SEM_CTR))       (13)//判断是否将导致{case 1u:                                  //该信号量计数值if (p_tcb->SemCtr == DEF_INT_08U_MAX_VAL)   //如果溢出,{OS_CRITICAL_EXIT();                  //则开中断,返回*p_err = OS_ERR_SEM_OVF;             //错误类型为“计return ((OS_SEM_CTR)0);             //数值溢出”,返}         //回0(有错误),break;    //不继续执行。case 2u:if (p_tcb->SemCtr == DEF_INT_16U_MAX_VAL){OS_CRITICAL_EXIT();*p_err = OS_ERR_SEM_OVF;return ((OS_SEM_CTR)0);}break;case 4u:if (p_tcb->SemCtr == DEF_INT_32U_MAX_VAL){OS_CRITICAL_EXIT();*p_err = OS_ERR_SEM_OVF;return ((OS_SEM_CTR)0);}break;default:break;}p_tcb->SemCtr++;                       //信号量计数值不溢出则加1ctr = p_tcb->SemCtr;                  //获取信号量的当前计数值OS_CRITICAL_EXIT();                   //退出临界段}break;                                    //跳出default:                          (14)//如果任务状态超出预期OS_CRITICAL_EXIT();                      //退出临界段*p_err = OS_ERR_STATE_INVALID;            //错误类型为“状态非法”ctr   = (OS_SEM_CTR)0;                 //清零 ctrbreak;                                   //跳出}return (ctr);  //返回信号量的当前计数值
}
  • (1):目标任务。
  • (2):释放任务信号量选项
  • (3):时间戳。
  • (4):保存返回的错误类型代码。
  • (5):如果目标任务为空,则表示将任务信号量释放给自己,那么p_tcb就指向当前任务。
  • (6):根据目标任务的任务状态分类处理。
  • (7):如果目标任务没有等待状态,判断一下是否即将导致该信号量计数值溢出, 如果溢出,则开中断,返回错误类型为“计数值溢出”的错误代码,退出不再继续执行。
  • (8):如果信号量还没溢出,信号量计数值加1。
  • (9):获取信号量的当前计数值,跳出switch语句。
  • (10):如果任务有等待状态,并且如果正等待任务信号量。
  • (11):调用OS_Post()函数发布信号量给目标任务,该函数在前面章节有讲解。
  • (12):如果选择了调度任务,就进行一次任务调度。
  • (13):如果不是等待任务信号量,判断一下是否即将导致该信号量计数值溢出, 如果溢出,则开中断,返回错误类型为“计数值溢出”的错误代码,退出不再继续执行,如果信号量还没溢出,信号量计数值加1。
  • (14):如果任务状态超出预期,返回错误类型为“状态非法”的错误代码。

在释放任务信号量的时候,系统首先判断目标任务的状态, 只有处于等待状态并且等待的是任务信号量那就调用OS_Post()函数让等待的任务就绪(如果内核对象信号量的话,还会让任务脱离等待列表), 所以任务信号量的操作是非常高效的;如果没有处于等待状态或者等待的不是任务信号量,那就直接将任务控制块的元素SemCtr 加 1。 最后返回任务信号量计数值。

其实,不管是否启用了中断延迟发布,最终都是调用 OS_TaskSemPost()函数进行释放任务信号量。只是启用了中断延迟发布的释放过程会比较曲折, 中间会有许多插曲,这是中断管理范畴的内容,留到后面再作介绍。在 OS_TaskSemPost()函数中,又会调用OS_Post()函数释放内核对象。 OS_Post()函数是一个底层的释放(发布)函数,它不仅仅用来释放(发布)任务信号量,还可以释放信号量、互斥信号量、消息队列、 事件标志组或任务消息队列。注意:在这里,OS_Post()函数将任务信号量直接释放给目标任务。

释放任务互斥量函数的使用实例具体如下:

OSTaskSemPost((OS_TCB  *)&AppTaskPendTCB,          //目标任务(OS_OPT   )OS_OPT_POST_NONE,        //没选项要求(OS_ERR  *)&err);                  //返回错误类型

2. 获取任务信号量函数OSTaskSemPend()

与 OSTaskSemPost()任务信号量释放函数相对应,OSTaskSemPend()函数用于获取一个任务信号量, 参数中没有指定某个任务去获取信号量,实际上就是当前运行任务获取它自己拥有的任务信号量, OSTaskSemPend()源码具体如下:

OS_SEM_CTR  OSTaskSemPend (OS_TICK   timeout,       (1)     //等待超时时间OS_OPT    opt,              (2)     //选项CPU_TS   *p_ts,             (3)     //返回时间戳OS_ERR   *p_err)            (4)     //返回错误类型
{OS_SEM_CTR    ctr;CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器// SR(临界段关中断只需保存SR),开中断时将该值还原。#ifdef OS_SAFETY_CRITICAL//如果启用了安全检测if (p_err == (OS_ERR *)0)            //如果错误类型实参为空{OS_SAFETY_CRITICAL_EXCEPTION();  //执行安全检测异常函数return ((OS_SEM_CTR)0);          //返回0(有错误),停止执行}
#endif#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用了中断中非法调用检测if (OSIntNestingCtr > (OS_NESTING_CTR)0)    //如果该函数在中断中被调用{*p_err = OS_ERR_PEND_ISR;                //返回错误类型为“在中断中等待”return ((OS_SEM_CTR)0);                 //返回0(有错误),停止执行}
#endif#if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测switch (opt)                            //根据选项分类处理{case OS_OPT_PEND_BLOCKING:          //如果选项在预期内case OS_OPT_PEND_NON_BLOCKING:break;                         //直接跳出default:                            //如果选项超出预期*p_err = OS_ERR_OPT_INVALID;    //错误类型为“选项非法”return ((OS_SEM_CTR)0);        //返回0(有错误),停止执行}
#endifif (p_ts != (CPU_TS *)0)        //如果 p_ts 非空{*p_ts  = (CPU_TS  )0;        //清零(初始化)p_ts}CPU_CRITICAL_ENTER();                        //关中断if (OSTCBCurPtr->SemCtr > (OS_SEM_CTR)0)     //如果任务信号量当前可用{OSTCBCurPtr->SemCtr--;         (5)//信号量计数器减1ctr    = OSTCBCurPtr->SemCtr;    (6)//获取信号量的当前计数值if (p_ts != (CPU_TS *)0)                 //如果 p_ts 非空{*p_ts  = OSTCBCurPtr->TS;       (7)//返回信号量被发布的时间戳}
#if OS_CFG_TASK_PROFILE_EN > 0u     (8)OSTCBCurPtr->SemPendTime = OS_TS_GET() - OSTCBCurPtr->TS;     //更新任务等待if (OSTCBCurPtr->SemPendTimeMax < OSTCBCurPtr->SemPendTime)   //任务信号量的{OSTCBCurPtr->SemPendTimeMax = OSTCBCurPtr->SemPendTime;   //最长时间记录。}//如果启用任务统计的宏,计算任务信号量从被提交到获取所用时间及最大时间
#endifCPU_CRITICAL_EXIT();                     //开中断*p_err = OS_ERR_NONE;                     //错误类型为“无错误”return (ctr);                            //返回信号量的当前计数值}/* 如果任务信号量当前不可用 */                      (9)if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务{CPU_CRITICAL_EXIT();                              //开中断*p_err = OS_ERR_PEND_WOULD_BLOCK;        //错误类型为“缺乏阻塞”return ((OS_SEM_CTR)0);                  //返回0(有错误),停止执行}else(10)//如果选择了阻塞任务{if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)    //如果调度器被锁{CPU_CRITICAL_EXIT();                          //开中断*p_err = OS_ERR_SCHED_LOCKED;//错误类型为“调度器被锁”return ((OS_SEM_CTR)0);              //返回0(有错误),停止执行}}/* 如果调度器未被锁 */OS_CRITICAL_ENTER_CPU_EXIT();                      //锁调度器,重开中断OS_Pend((OS_PEND_DATA *)0,                        //阻塞任务,等待信号量。(OS_PEND_OBJ  *)0,                            //不需插入等待列表。(OS_STATE      )OS_TASK_PEND_ON_TASK_SEM,(OS_TICK       )timeout);               (11)OS_CRITICAL_EXIT_NO_SCHED();                          //开调度器(无调度)OSSched();                              (12)//调度任务/* 任务获得信号量后得以继续运行 */CPU_CRITICAL_ENTER();                   (13)//关中断switch (OSTCBCurPtr->PendStatus)             //根据任务的等待状态分类处理{case OS_STATUS_PEND_OK:              (14)//如果任务成功获得信号量if (p_ts != (CPU_TS *)0)              //返回信号量被发布的时间戳{*p_ts                    =  OSTCBCurPtr->TS;
#if OS_CFG_TASK_PROFILE_EN > 0u//更新最长等待时间记录OSTCBCurPtr->SemPendTime = OS_TS_GET() - OSTCBCurPtr->TS;if (OSTCBCurPtr->SemPendTimeMax < OSTCBCurPtr->SemPendTime){OSTCBCurPtr->SemPendTimeMax = OSTCBCurPtr->SemPendTime;}
#endif}*p_err = OS_ERR_NONE;                         //错误类型为“无错误”break;                                       //跳出case OS_STATUS_PEND_ABORT:             (15)//如果等待被中止if (p_ts != (CPU_TS *)0)                     //返回被终止时的时间戳{*p_ts  =  OSTCBCurPtr->TS;}*p_err = OS_ERR_PEND_ABORT;              //错误类型为“等待被中止”break;                                  //跳出case OS_STATUS_PEND_TIMEOUT:         (16)//如果等待超时if (p_ts != (CPU_TS *)0)                     //返回时间戳为0{*p_ts  = (CPU_TS  )0;}*p_err = OS_ERR_TIMEOUT;                      //错误类型为“等待超时”break;                                       //跳出default:                               (17)//如果等待状态超出预期*p_err = OS_ERR_STATUS_INVALID;               //错误类型为“状态非法”break;                                       //跳出}ctr = OSTCBCurPtr->SemCtr;                    //获取信号量的当前计数值CPU_CRITICAL_EXIT();                           //开中断
return (ctr);   (18)//返回信号量的当前计数值
}
  • (1):等待超时时间。
  • (2):等待的选项。
  • (3):保存返回的时间戳。
  • (4):保存返回错误的类型。
  • (5):如果任务信号量当前可用,那就信号量计数值SemCtr减一。
  • (6):获取信号量的当前计数值保存在ctr变量中,用于返回。
  • (7):返回信号量被发布的时间戳。
  • (8):如果启用任务统计的宏,计算任务信号量从被释放到获取所用时间及最大时间。
  • (9):如果任务信号量当前不可用,并且如果用户选择了不阻塞任务,那么就返回错误类型为“缺乏阻塞”错误代码。
  • (10):如果选择了阻塞任务,判断一下调度器是否被锁,如果被锁,则返回错误类型为“调度器被锁”的错误代码。
  • (11):如果调度器未被锁,锁调度器, 重开中断,调用OS_Pend()函数将当前任务进入阻塞状态以等待任务信号量,该函数在前面的章节已经讲解过, 此处就不再重复赘述。
  • (12):进行一次任务调度。
  • (13):当程序能执行到这里, 就说明大体上有两种情况,要么是任务获取到任务信号量了;要么任务还没获取到任务信号量(任务没获取到任务信号量的情况有很多种), 无论是哪种情况,都先把中断关掉再说,再根据当前运行任务的等待状态分类处理。
  • (14):如果任务成功获得任务信号量,返回信号量被发布的时间戳,然后跳出switch语句。
  • (15):如果任务在等待中被中止, 返回被终止时的时间戳,返回错误类型为“等待被中止”的错误代码,跳出switch语句。
  • (16):如果任务等待超时,返回错误类型为“等待超时”的错误代码,跳出switch语句。
  • (17):如果等待状态超出预期,返回错误类型为“状态非法”的错误代码。
  • (18):获取并返回任务信号量的当前计数值。

在调用该函数的时候,系统先判断任务信号量是否可用,即检查任务信号量的计数值是否大于 0,如果大于0,即表示可用, 这个时候获取信号量,即将计数值减 1 后直接返回。
如果信号量不可用,且当调度器没有被锁住时, 用户希望在任务信号量不可用的时候进行阻塞任务以等待任务信号量可用,那么系统就会调用OS_Pend()函数将任务脱离就绪列表, 如果用户有指定超时时间,系统还要将该任务插入节拍列表。
注意:此处系统并没有将任务插入等待列表。然后切换任务, 处于就绪列表中最高优先级的任务通过任务调度获得 CPU使用权,等到出现任务信号量被释放、任务等待任务信号量被强制停止、 等待超时等情况,任务会从阻塞中恢复,等待任务信号量的任务重新获得 CPU 使用权,返回相关错误代码和任务信号量计数值, 用户可以根据返回的错误知道任务退出等待状态的情况。

获取任务信号量函数的使用实例具体如下:

OSTaskSemPend ((OS_TICK   )0,                     //无期限等待(OS_OPT    )OS_OPT_PEND_BLOCKING,  //如果信号量不可用就等待(CPU_TS   *)&ts,                   //获取信号量被发布的时间戳(OS_ERR   *)&err);                 //返回错误类型

3、任务信号量实验

1、任务信号量代替二值信号量实验

任务通知代替消息队列是在ΜC/OS中创建了两个任务,其中一个任务是用于接收任务信号量,另一个任务发送任务信号量。
两个任务独立运行,发送任务信号量的任务是通过检测按键的按下情况发送,等待任务在任务信号量中没有可用的信号量之前就一直等待, 获取到信号量以后就继续执行,这样子是为了代替二值信号量,任务同步成功则继续执行, 然后在串口调试助手里将运行信息打印出来,具体如下:

#include <includes.h>static  OS_TCB   AppTaskStartTCB;      //任务控制块static  OS_TCB   AppTaskPostTCB;
static  OS_TCB   AppTaskPendTCB;static  CPU_STK  AppTaskStartStk[APP_TASK_START_STK_SIZE];       //任务栈static  CPU_STK  AppTaskPostStk [ APP_TASK_POST_STK_SIZE ];
static  CPU_STK  AppTaskPendStk [ APP_TASK_PEND_STK_SIZE ];static  void  AppTaskStart  (void *p_arg);               //任务函数声明static  void  AppTaskPost   ( void * p_arg );
static  void  AppTaskPend   ( void * p_arg );int  main (void)
{OS_ERR  err;OSInit(&err);       //初始化μC/OS-III/* 创建起始任务 */OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,//任务控制块地址(CPU_CHAR   *)"App Task Start",//任务名称(OS_TASK_PTR ) AppTaskStart,//任务函数(void       *) 0,//传递给任务函数(形参p_arg)的实参(OS_PRIO     ) APP_TASK_START_PRIO,//任务的优先级(CPU_STK    *)&AppTaskStartStk[0],//任务栈的基地址(CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,//任务栈空间剩下1/10时限制其增长(CPU_STK_SIZE) APP_TASK_START_STK_SIZE,//任务栈空间(单位:sizeof(CPU_STK))(OS_MSG_QTY  ) 5u,//任务可接收的最大消息数(OS_TICK     ) 0u,//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)(void       *) 0,//任务扩展(0表不扩展)(OS_OPT      )(OS_OPT_TASK_STK_CHK |  OS_OPT_TASK_STK_CLR),//任务选项(OS_ERR     *)&err);//返回错误类型OSStart(&err);//启动多任务管理(交由μC/OS-III控制)}static  void  AppTaskStart (void *p_arg)
{CPU_INT32U  cpu_clk_freq;CPU_INT32U  cnts;OS_ERR      err;(void)p_arg;//板级初始化BSP_Init();//初始化 CPU 组件(时间戳、关中断时间测量和主机名)CPU_Init();//获取 CPU 内核时钟频率(SysTick 工作时钟)cpu_clk_freq = BSP_CPU_ClkFreq();//根据用户设定的时钟节拍频率计算 SysTick 定时器的计数值cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;//调用 SysTick 初始化函数,设置定时器计数值和启动定时器OS_CPU_SysTickInit(cnts);Mem_Init();//初始化内存管理组件(堆内存池和内存池表)#if OS_CFG_STAT_TASK_EN > 0u
//如果启用(默认启用)了统计任务OSStatTaskCPUUsageInit(&err);
#endifCPU_IntDisMeasMaxCurReset();//复位(清零)当前最大关中断时间/* 创建 AppTaskPost 任务 */OSTaskCreate((OS_TCB     *)&AppTaskPostTCB,//任务控制块地址(CPU_CHAR   *)"App Task Post",//任务名称(OS_TASK_PTR ) AppTaskPost,//任务函数(void       *) 0,//传递给任务函数(形参p_arg)的实参(OS_PRIO     ) APP_TASK_POST_PRIO,//任务的优先级(CPU_STK    *)&AppTaskPostStk[0],//任务栈的基地址(CPU_STK_SIZE) APP_TASK_POST_STK_SIZE / 10,//任务栈空间剩下1/10时限制其增长(CPU_STK_SIZE) APP_TASK_POST_STK_SIZE,//任务栈空间(单位:sizeof(CPU_STK))(OS_MSG_QTY  ) 5u,//任务可接收的最大消息数(OS_TICK     ) 0u,//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)(void       *) 0,//任务扩展(0表不扩展)(OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务选项(OS_ERR     *)&err);//返回错误类型/* 创建 AppTaskPend 任务 */OSTaskCreate((OS_TCB     *)&AppTaskPendTCB,//任务控制块地址(CPU_CHAR   *)"App Task Pend",//任务名称(OS_TASK_PTR ) AppTaskPend,//任务函数(void       *) 0,//传递给任务函数(形参p_arg)的实参(OS_PRIO     ) APP_TASK_PEND_PRIO,//任务的优先级(CPU_STK    *)&AppTaskPendStk[0],//任务栈的基地址(CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE / 10,//任务栈空间剩下1/10时限制其增长(CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE,//任务栈空间(单位:sizeof(CPU_STK))(OS_MSG_QTY  ) 5u,//任务可接收的最大消息数(OS_TICK     ) 0u,//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)(void       *) 0,//任务扩展(0表不扩展)(OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务选项(OS_ERR     *)&err);//返回错误类型OSTaskDel ( & AppTaskStartTCB, & err );//删除起始任务本身,该任务不再运行}static  void  AppTaskPost ( void * p_arg )
{OS_ERR      err;uint8_t ucKey1Press = 0;     //记忆按键KEY1状态(void)p_arg;while (DEF_TRUE)//任务体{if ( Key_Scan ( macKEY1_GPIO_PORT, macKEY1_GPIO_PIN, 1, & ucKey1Press ) )//如果KEY1被按下{printf("发送任务信号量\n");/* 发布任务信号量 */OSTaskSemPost((OS_TCB  *)&AppTaskPendTCB,//目标任务(OS_OPT   )OS_OPT_POST_NONE,//没选项要求(OS_ERR  *)&err);//返回错误类型}OSTimeDlyHMSM ( 0, 0, 0, 20, OS_OPT_TIME_DLY, & err );//每20ms扫描一次}}static  void  AppTaskPend ( void * p_arg )
{OS_ERR         err;CPU_TS         ts;CPU_INT32U     cpu_clk_freq;CPU_SR_ALLOC();(void)p_arg;cpu_clk_freq = BSP_CPU_ClkFreq();//获取CPU时钟,时间戳是以该时钟计数while (DEF_TRUE)                                    //任务体{/* 阻塞任务,直到KEY1被按下 */OSTaskSemPend ((OS_TICK   )0,                     //无期限等待(OS_OPT    )OS_OPT_PEND_BLOCKING,//如果信号量不可用就等待(CPU_TS   *)&ts,//获取信号量被发布的时间戳(OS_ERR   *)&err);                 //返回错误类型ts = OS_TS_GET() - ts;//计算信号量从发布到接收的时间差macLED1_TOGGLE ();                     //切换LED1的亮灭状态OS_CRITICAL_ENTER();//进入临界段,避免串口打印被打断printf ( "任务信号量从被发送到被接收的时间差是%dus\n\n",ts / ( cpu_clk_freq / 1000000 ) );OS_CRITICAL_EXIT();                               //退出临界段}}

2. 任务信号量代替二值信号量实验现象

在调试助手中可以看到串口的打印信息,它里面输出了信息表明任务正在运行中, 我们按下开发板的按键,串口打印任务运行的信息,表明两个任务同步成功,具体见图
在这里插入图片描述

3. 任务信号量代替计数信号量实验

任务通知代替计数信号量是基于计数信号量实验修改而来,模拟停车场工作运行,并且在μC/OS中创建了两个任务:
一个是获取信号量任务,一个是发送信号量任务,两个任务独立运行,获取任务信号量的任务是通过按下KEY1按键获取, 模拟停车场停车操作,其等待时间是0;发送任务信号量的任务则是通过检测KEY2按键按下进行信号量的发送(发送到获取任务), 模拟停车场取车操作,并且在串口调试助手输出相应信息,实验源码具体如下:

#include <includes.h>static  OS_TCB   AppTaskStartTCB;      //任务控制块
static  OS_TCB   AppTaskPostTCB;
static  OS_TCB   AppTaskPendTCB;
static  CPU_STK  AppTaskStartStk[APP_TASK_START_STK_SIZE];       //任务栈
static  CPU_STK  AppTaskPostStk [ APP_TASK_POST_STK_SIZE ];
static  CPU_STK  AppTaskPendStk [ APP_TASK_PEND_STK_SIZE ];
static  void  AppTaskStart  ( void *p_arg);               //任务函数声明
static  void  AppTaskPost   ( void * p_arg );
static  void  AppTaskPend   ( void * p_arg );int  main (void)
{OS_ERR  err;OSInit(&err);//初始化μC/OS-III/* 创建起始任务 */OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,//任务控制块地址(CPU_CHAR   *)"App Task Start",//任务名称(OS_TASK_PTR ) AppTaskStart,//任务函数(void       *) 0,//传递给任务函数(形参p_arg)的实参(OS_PRIO     ) APP_TASK_START_PRIO,//任务的优先级(CPU_STK    *)&AppTaskStartStk[0],//任务栈的基地址(CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,//任务栈空间剩下1/10时限制其增长(CPU_STK_SIZE) APP_TASK_START_STK_SIZE,//任务栈空间(单位:sizeof(CPU_STK))(OS_MSG_QTY  ) 5u,//任务可接收的最大消息数(OS_TICK     ) 0u,//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)(void       *) 0,//任务扩展(0表不扩展)(OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务选项(OS_ERR     *)&err);//返回错误类型OSStart(&err);//启动多任务管理(交由μC/OS-III控制)}static  void  AppTaskStart (void *p_arg)
{CPU_INT32U  cpu_clk_freq;CPU_INT32U  cnts;OS_ERR      err;(void)p_arg;BSP_Init();//板级初始化CPU_Init();//初始化 CPU组件(时间戳、关中断时间测量和主机名)cpu_clk_freq = BSP_CPU_ClkFreq();//获取 CPU内核时钟频率(SysTick 工作时钟)cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;//根据用户设定的时钟节拍频率计算 SysTick定时器的计数值//调用 SysTick初始化函数,设置定时器计数值和启动定时器OS_CPU_SysTickInit(cnts);Mem_Init();//初始化内存管理组件(堆内存池和内存池表)#if OS_CFG_STAT_TASK_EN > 0u
//如果启用(默认启用)了统计任务OSStatTaskCPUUsageInit(&err);
#endif//复位(清零)当前最大关中断时间CPU_IntDisMeasMaxCurReset();/* 创建 AppTaskPost 任务 */OSTaskCreate((OS_TCB     *)&AppTaskPostTCB,//任务控制块地址(CPU_CHAR   *)"App Task Post",//任务名称(OS_TASK_PTR ) AppTaskPost,//任务函数(void       *) 0,//传递给任务函数(形参p_arg)的实参(OS_PRIO     ) APP_TASK_POST_PRIO,//任务的优先级(CPU_STK    *)&AppTaskPostStk[0],//任务栈的基地址(CPU_STK_SIZE) APP_TASK_POST_STK_SIZE / 10,//任务栈空间剩下1/10时限制其增长(CPU_STK_SIZE) APP_TASK_POST_STK_SIZE,//任务栈空间(单位:sizeof(CPU_STK))(OS_MSG_QTY  ) 5u,//任务可接收的最大消息数(OS_TICK     ) 0u,//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)(void       *) 0,//任务扩展(0表不扩展)(OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务选项(OS_ERR     *)&err);//返回错误类型/* 创建 AppTaskPend 任务 */OSTaskCreate((OS_TCB     *)&AppTaskPendTCB,//任务控制块地址(CPU_CHAR   *)"App Task Pend",//任务名称(OS_TASK_PTR ) AppTaskPend,//任务函数(void       *) 0,//传递给任务函数(形参p_arg)的实参(OS_PRIO     ) APP_TASK_PEND_PRIO,//任务的优先级(CPU_STK    *)&AppTaskPendStk[0],//任务栈的基地址(CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE / 10,//任务栈空间剩下1/10时限制其增长(CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE,//任务栈空间(单位:sizeof(CPU_STK))(OS_MSG_QTY  ) 5u,//任务可接收的最大消息数(OS_TICK     ) 0u,//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)(void       *) 0,//任务扩展(0表不扩展)(OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务选项(OS_ERR     *)&err);//返回错误类型OSTaskDel ( & AppTaskStartTCB, & err );//删除起始任务本身,该任务不再运行}static  void  AppTaskPost ( void * p_arg )
{OS_ERR      err;OS_SEM_CTR  ctr;uint8_t ucKey2Press = 0;     //记忆按键KEY2状态CPU_SR_ALLOC();(void)p_arg;while (DEF_TRUE)//任务体{if ( Key_Scan ( macKEY2_GPIO_PORT, macKEY2_GPIO_PIN, 1, & ucKey2Press ) )//如果KEY2被按下{/* 发布任务信号量 */ctr = OSTaskSemPost((OS_TCB  *)&AppTaskPendTCB,//目标任务(OS_OPT   )OS_OPT_POST_NONE,//没选项要求(OS_ERR  *)&err);//返回错误类型macLED2_TOGGLE();OS_CRITICAL_ENTER();//进入临界段,避免串口打印被打断printf( "KEY2被按下,释放1个停车位,当前车位为 %d 个\n",ctr);OS_CRITICAL_EXIT();    //退出临界段}OSTimeDlyHMSM ( 0, 0, 0, 20, OS_OPT_TIME_DLY, & err );//每20ms扫描一次}}static  void  AppTaskPend ( void * p_arg )
{OS_ERR         err;CPU_SR_ALLOC();OS_SEM_CTR    ctr;//当前任务信号量计数uint8_t ucKey1Press = 0;     //记忆按键KEY1状态(void)p_arg;while (DEF_TRUE)           //任务体{if ( Key_Scan ( macKEY1_GPIO_PORT, macKEY1_GPIO_PIN, 1, & ucKey1Press ) )//如果KEY2被按下{ctr = OSTaskSemPend ((OS_TICK   )0,               //不等待(OS_OPT    )OS_OPT_PEND_NON_BLOCKING,(CPU_TS   *)0,//获取信号量被发布的时间戳(OS_ERR   *)&err);      //返回错误类型macLED1_TOGGLE ();//切换LED1的亮灭状态OS_CRITICAL_ENTER();//进入临界段,避免串口打印被打断if (OS_ERR_NONE == err)printf( "KEY1被按下,申请车位成功,当前剩余车位为 %d个\n", ctr);elseprintf("申请车位失败,请按KEY2释放车位\n");OS_CRITICAL_EXIT();                               //退出临界段}OSTimeDlyHMSM ( 0, 0, 0, 20, OS_OPT_TIME_DLY, & err );}}

4. 任务信号量代替计数信号量实验

可以在调试助手中看到串口的打印信息, 按下开发板的KEY1按键获取信号量模拟停车,按下KEY2按键释放信号量模拟取车,因为是使用任务信号量代替信号量, 所以任务通信号量默认为0,表当前车位为0;我们按下KEY1与KEY2试试,在串口调试助手中可以看到运行信息, 具体见图
在这里插入图片描述

这篇关于(学习日记)2024.04.14:UCOSIII第四十二节:任务信号量的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/897574

相关文章

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题小结

《SpringBoot整合ShedLock处理定时任务重复执行的问题小结》ShedLock是解决分布式系统中定时任务重复执行问题的Java库,通过在数据库中加锁,确保只有一个节点在指定时间执行... 目录前言什么是 ShedLock?ShedLock 的工作原理:定时任务重复执行China编程的问题使用 Shed

Python Invoke自动化任务库的使用

《PythonInvoke自动化任务库的使用》Invoke是一个强大的Python库,用于编写自动化脚本,本文就来介绍一下PythonInvoke自动化任务库的使用,具有一定的参考价值,感兴趣的可以... 目录什么是 Invoke?如何安装 Invoke?Invoke 基础1. 运行测试2. 构建文档3.

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

什么是cron? Linux系统下Cron定时任务使用指南

《什么是cron?Linux系统下Cron定时任务使用指南》在日常的Linux系统管理和维护中,定时执行任务是非常常见的需求,你可能需要每天执行备份任务、清理系统日志或运行特定的脚本,而不想每天... 在管理 linux 服务器的过程中,总有一些任务需要我们定期或重复执行。就比如备份任务,通常会选在服务器资

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06