找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1358|回复: 1
打印 上一主题 下一主题
收起左侧

uCOS-II之任务间通信、软件定时器补充

[复制链接]
跳转到指定楼层
楼主
ID:763998 发表于 2020-6-29 09:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

任务间通信

5.4.1 信号量

信号量是用来保护共享资源用的,表示共享资源的个数。共享资源被占用一个,信号量的指会减1,共享资源被释放一个,信号量的值会加1。

(理解:USB口的占用与释放,假设一个电脑有3个USB,插入一个USB设备,电脑的USB资源会减少1,此时电脑的USB资源还有2;拔出USB设备,电脑的USB资源会增加1,此时电脑的USB资源有3个USB)

其实信号量的本质就是一个操作系统计数器(0~65535),实际用于实现任务间同步执行。

需要掌握的函数如下:

实验代码如下:


  1. #include "main.h"
  2. #include "includes.h"

  3. /** 任务0 **/
  4. #define TASK0_PRI 8 //任务优先级
  5. #define TASK0_STK_SIZE               256 //任务栈大小
  6. OS_STK              stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
  7. void Task0 (void *p_arg); //函数声明

  8. /** 任务1 **/
  9. #define TASK1_PRI 9 //任务优先级
  10. #define TASK1_STK_SIZE               256 //任务栈大小
  11. OS_STK              stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
  12. void Task1 (void *p_arg); //函数声明

  13. /** 任务2 **/
  14. #define TASK2_PRI 10 //任务优先级
  15. #define TASK2_STK_SIZE               256 //任务栈大小
  16. OS_STK              stack2[TASK2_STK_SIZE]; //数组作为任务堆栈
  17. void Task2 (void *p_arg); //函数声明

  18. OS_EVENT  *sem;//全局变量,创建信号量、等待信号量、发布信号量都需要用到该指针。

  19. int main(void)
  20. {
  21.               OSSysTickInit();//滴答定时器初始化
  22.               USART1_Init(115200);//初始化串口,用于调试
  23.             
  24.               OSInit();                             //操作系统初始化
  25.             
  26.               sem=OSSemCreate(2);//初始化--默认创建2个信号量
  27.               OSTaskCreate (Task0,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建一个任务,任务名:Task0
  28.               OSTaskCreate (Task1,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建一个任务,任务名:Task1
  29.               OSTaskCreate (Task2,NULL,&stack2[TASK2_STK_SIZE-1],TASK2_PRI);//创建一个任务,任务名:Task2
  30.             
  31.               OSStart();    //启动操作系统
  32. }

  33. void Task0 (void *p_arg) //实现任务Task0
  34. {
  35.               u8 k=0;//给个默认值0,表示无键状态
  36.               KEY_Init();//按键初始化,放在对应任务中
  37.               while(1)
  38.               {
  39.                             k=Key_Scanf(0);//按键扫描
  40.                             switch (k)
  41.     {
  42.                   case KEY_ONE: //按键1按下,发布一个信号量
  43.                                                         OSSemPost(sem);//发送信号量
  44.                                 break;
  45.                   default:
  46.                                 break;
  47.     }
  48.                             OSTimeDly(1);//高优先级释放CPU,延迟不可太长,延时太长会影响按键的灵敏度。
  49.               }
  50. }

  51. void Task1 (void *p_arg) //实现任务Task1
  52. {
  53.               while(1)
  54.               {
  55.                             //等待一个信号量
  56.                             OSSemPend (sem,//信号量
  57.                  0,//超时时间
  58.                  0);//错误类型--没有错误
  59.                             printf("任务1\r\n");
  60.                             OSTimeDly(100);//5ms一次滴答,100*5=500ms,打印一次
  61.               }
  62. }

  63. void Task2 (void *p_arg) //实现任务Task2
  64. {
  65.               while(1)
  66.               {
  67.                             //等待一个信号量
  68.                             OSSemPend (sem,//信号量
  69.                  0,//超时时间
  70.                  0);//错误类型--没有错误
  71.                             printf("任务2\r\n");
  72.                             OSTimeDly(100);//5ms一次滴答,100*5=500ms,打印一次
  73.               }
  74. }
复制代码


因为创建了两个信号量,按下复位按键,同时打印出任务1、任务2。

按下按键1,打印出任务1,因为任务1的优先级比较高。

快速按下按键1,能打印出任务1,和任务2,因为他们都在等信号量。

5.4.2 互斥信号量

用来保护共享资源,但是这个共享资源只有一个。两个任务同时操作一个硬件,这时候需要加互斥信号量保护。

(理解:电话亭的使用,假设电话亭里只有一个电话,有3个人想打电话,需要排队,还需要等待,等待电话亭里面没有人,排在前面的人就能进入电话亭打电话了)

其实互斥信号量的本质就是一个操作系统计数器(0-1)。

注意:互斥信号量中需要有一个空闲的优先级作为优先级反转用,该优先级必须比所有能够获得该互斥信号量的优先级还高。

理解:假设能获得该互斥信号量的所有任务的优先级分别为:4、10、11、13,则该空闲优先级的取值(0~3);在如,假设能获得该互斥信号量的所有任务的优先级分别为:8、10、11、13,则该空闲优先级的取值(0~7)。

需要掌握的函数如下:

实验代码如下:


  1. #include "main.h"
  2. #include "includes.h"

  3. /** 任务0 **/
  4. #define TASK0_PRI 8 //任务优先级
  5. #define TASK0_STK_SIZE               256 //任务栈大小
  6. OS_STK              stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
  7. void Task0 (void *p_arg); //函数声明

  8. /** 任务1 **/
  9. #define TASK1_PRI 9 //任务优先级
  10. #define TASK1_STK_SIZE               256 //任务栈大小
  11. OS_STK              stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
  12. void Task1 (void *p_arg); //函数声明

  13. OS_EVENT  *mutex;//全局变量,创建信号量、等待信号量、发布信号量都需要用到该指针。

  14. int main(void)
  15. {
  16.               OSSysTickInit();//滴答定时器初始化
  17.               USART1_Init(115200);//初始化串口,用于调试
  18.             
  19.               OSInit();                             //操作系统初始化
  20.             
  21.               //创建互斥信号量
  22.               mutex=OSMutexCreate (5,//空闲优先级
  23.                         0);//错误类型
  24.               OSTaskCreate (Task0,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建一个任务,任务名:Task0
  25.               OSTaskCreate (Task1,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建一个任务,任务名:Task1
  26.             
  27.               OSStart();    //启动操作系统
  28. }

  29. void Task0 (void *p_arg) //实现任务Task0
  30. {
  31.               while(1)
  32.               {
  33.                             //获取互斥信号量
  34.                             OSMutexPend (mutex,//互斥信号量
  35.                    0,//超时时间
  36.                    0);//错误类型
  37.                             for(int i=0;i<5;i++)
  38.                             {
  39.                                           printf("任务0--%d\r\n",i);//
  40.                                           OSTimeDly(100);//
  41.                             }
  42.                             //释放互斥信号量
  43.                             OSMutexPost (mutex);
  44.               }
  45. }

  46. void Task1 (void *p_arg) //实现任务Task1
  47. {
  48.               while(1)
  49.               {
  50.                             //获取互斥信号量
  51.                             OSMutexPend (mutex,//互斥信号量
  52.                    0,//超时时间
  53.                    0);//错误类型
  54.                             for(int i=0;i<5;i++)
  55.                             {
  56.                                           printf("任务1--%d\r\n",i);//
  57.                                           OSTimeDly(100);//
  58.                             }
  59.                             //释放互斥信号量
  60.                             OSMutexPost (mutex);
  61.               }
  62. }
复制代码


烧录代码,结果如下图:

实验表明,假设多个任务在访问同一资源,只有等正在访问的任务使用完并释放资源,下一个任务才能访问使用。

假设不加互斥信号量进行互斥访问,代码如下,

结果如下:

5.4.3 消息邮箱

用于任务与任务之间交换数据(任务与任务之间的通信)。消息邮箱只能存放一则消息,消息的内容长短不限制。

需要掌握的函数:

实验代码:


  1. #include "main.h"
  2. #include "includes.h"

  3. /** 任务0 **/
  4. #define TASK0_PRI 8 //任务优先级
  5. #define TASK0_STK_SIZE               256 //任务栈大小
  6. OS_STK              stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
  7. void SendTask (void *p_arg); //函数声明

  8. /** 任务1 **/
  9. #define TASK1_PRI 9 //任务优先级
  10. #define TASK1_STK_SIZE               256 //任务栈大小
  11. OS_STK              stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
  12. void ReceiveTask (void *p_arg); //函数声明

  13. OS_EVENT  *mbox;//全局变量,创建邮箱、发送消息、接收消息,都需要用到该指针。

  14. int main(void)
  15. {
  16.               OSSysTickInit();//滴答定时器初始化
  17.               USART1_Init(115200);//初始化串口,用于调试
  18.             
  19.               OSInit();                             //操作系统初始化
  20.               //创建一个邮箱
  21.               mbox=OSMboxCreate (NULL);//初始化消息的地址:NULL
  22.             
  23.               OSTaskCreate (SendTask,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建发送任务
  24.               OSTaskCreate (ReceiveTask,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建接收任务
  25.             
  26.               OSStart();    //启动操作系统
  27. }

  28. void SendTask (void *p_arg) //发送任务
  29. {
  30.               u8 k=0;//给个默认值0,表示无键状态
  31.               KEY_Init();//按键初始化,放在对应任务中
  32.               while(1)
  33.               {
  34.                             k=Key_Scanf(0);//按键扫描
  35.                             switch (k)
  36.     {
  37.                   case KEY_ONE: //按键1按下,发布一则消息
  38.                                                         OSMboxPost (mbox,//邮箱
  39.                                                                                                                                             "hello 51黑");//邮箱内容
  40.                                 break;
  41.                                           case KEY_TWO: //按键2按下,发布一则消息
  42.                                                       
  43.                                                         OSMboxPost (mbox,//邮箱
  44.                                                                                                                                             "hello world");//邮箱内容
  45.                                                         OSMboxPost (mbox,//邮箱
  46.                                                                                                                                             "hello xixi");//邮箱内容
  47.                                                         OSMboxPost (mbox,//邮箱
  48.                                                                                                                                             "hello haha");//邮箱内容
  49.                                 break;
  50.                   default:
  51.                                 break;
  52.     }
  53.                             OSTimeDly(1);//释放CPU使用权
  54.               }
  55. }

  56. void ReceiveTask (void *p_arg) //接收任务
  57. {
  58.               u8 *str;
  59.               while(1)
  60.               {
  61.                             //接收一条消息
  62.                             str=OSMboxPend (mbox,//邮箱地址
  63.                                                                                                                 0,//死等
  64.                                                                                                                 0);//发送成功
  65.                             printf("接收到的消息:%s\r\n",str);//开始默认打印三次,因为默认开始创建3个信号量。
  66.                             OSTimeDly(1);//释放CPU使用权
  67.               }
  68. }
复制代码


实验结果如下:

按下按键1:

按下按键2:

原因是接收方接收不过来了,造成了数据丢失。这时需要引入消息队列。

5.4.4 消息队列

消息邮箱只能发送一则消息,获取消息的地方如果处理比较慢就会丢失消息。消息队列能存储一队消息,能很好的避免接收方处理能力弱而丢失消息的问题。队列是一种数据结构,遵循先进先出原则。

需要掌握的函数:

实验代码:


  1. #include "main.h"
  2. #include "includes.h"

  3. /** 任务0 **/
  4. #define TASK0_PRI 8 //任务优先级
  5. #define TASK0_STK_SIZE               256 //任务栈大小
  6. OS_STK              stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
  7. void SendTask (void *p_arg); //函数声明

  8. /** 任务1 **/
  9. #define TASK1_PRI 9 //任务优先级
  10. #define TASK1_STK_SIZE               256 //任务栈大小
  11. OS_STK              stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
  12. void ReceiveTask (void *p_arg); //函数声明

  13. OS_EVENT  *q;//全局变量,创建队列、发送消息、接收消息,都需要用到该指针。

  14.             
  15. void *queue[10];//队列

  16. int main(void)
  17. {
  18.               OSSysTickInit();//滴答定时器初始化
  19.               USART1_Init(115200);//初始化串口,用于调试
  20.             
  21.               OSInit();                             //操作系统初始化
  22.             
  23.               //创建一个队列
  24.               q=OSQCreate(queue,//队列
  25.                 10);//队列的大小
  26.             
  27.               OSTaskCreate (SendTask,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建发送任务
  28.               OSTaskCreate (ReceiveTask,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建接收任务
  29.             
  30.               OSStart();    //启动操作系统
  31. }

  32. void SendTask (void *p_arg) //发送任务
  33. {
  34.               u8 k=0;//给个默认值0,表示无键状态
  35.               KEY_Init();//按键初始化,放在对应任务中
  36.               while(1)
  37.               {
  38.                             k=Key_Scanf(0);//按键扫描
  39.                             switch (k)
  40.     {
  41.                   case KEY_ONE: //按键1按下,发布一则消息
  42.                                                         OSQPost (q,//队列地址
  43.                                                                                                                 "lele");//需要发送的内容
  44.                                                         OSQPost (q,//队列地址
  45.                 "学习");//需要发送的内容
  46.                                                         OSQPost (q,//队列地址
  47.                 "不可能");//需要发送的内容
  48.                                 break;
  49.                                           case KEY_TWO: //按键2按下,发布一则消息
  50.                                                         OSQPost (q,//队列地址
  51.                 "哈哈");//需要发送的内容
  52.                                 break;
  53.                   default:
  54.                                 break;
  55.     }
  56.                             OSTimeDly(1);//释放CPU使用权
  57.               }
  58. }

  59. void ReceiveTask (void *p_arg) //接收任务
  60. {
  61.               char *strs;
  62.               while(1)
  63.               {
  64.                             //接收一条消息
  65.                             strs=OSQPend(q,//队列
  66.                                                                                                                 0,//死等
  67.                                                                                                                 0);//错误类型
  68.                             printf("接收到的消息:%s\r\n",strs);//开始默认打印三次,因为默认开始创建3个信号量。
  69.                             OSTimeDly(1);//释放CPU使用权
  70.               }
  71. }
复制代码


实验结果如下:

发送三条消息,接收到三条消息,没有数据丢失。

5.4.5 补充

信号量:就理解成有多个电话的电话亭,这些电话是共享资源,当有很多人使用时,需要排队(优先级),需要等待(等待信号量),当电话亭的电话空闲时(有信号量),就可以让排在前面的人使用,依次使用。

互斥信号量:和信号量基本一致,理解成只用一个电话的电话亭,多个用户要互斥使用这个电话。

消息邮箱:发送一条消息。如果消息发送太快,接收方接收不过来,会造成数据丢失。

消息队列:发送多条消息。实现原理是把多条消息存放在队列中。

最重要的最重要的是:

  • 概念的理解,领会
  • 代码的实现、代码的流程
  • 多实践与思考

5.5 其他补充

5.5.1 延时函数

这里的延时与STM32的延迟有不同的含义,对于STM32F407,系统时钟为21M,即21 000 000 次脉冲为1秒钟=21 000 000个滴答为1秒钟,UCOS的延时规定,200个脉冲为1秒钟=200个滴答为1秒钟,所以UCOS的一个滴答为5ms。

常用第一个函数,第二函数用来延时,会有点误差,因为任务调度会消耗一点时间。

为什么使用第一个函数,能发生任务调度?看代码如下:

因为函数的实现中有任务调度函数。

实际的任务调度代码源头在哪?进入看看

在点击进入发现。进不了了。实际这个函数由汇编代码实现

5.5.2 软件定时器

实现UCOS软件定时器需要注意两点:

  • 打开定时器代码,会发现创建定时器代码都是灰色,需要修改一个宏


第二,定义一个优先级,编译你就会发现这个优先级了,注意优先级不能设置跟其他任务的有效一样。

编写代码验证,编写如下代码:

   #include "main.h"
     #include "includes.h"
      
      
     OS_TMR  *tmr;//定时器。
      
     void MyCallback (OS_TMR *ptmr, void *p_arg);//回调函数
      
     int main(void)
{
               OSSysTickInit();//滴答定时器初始化
               USART1_Init(115200);//初始化串口,用于调试
              
               OSInit();                             //操作系统初始化
               //创建定时器
               tmr=OSTmrCreate (50,//第一次使用,规定10个滴答为1秒钟--所以第一次定时5秒钟
                    10,//第二次以后使用--定时1秒钟
                    OS_TMR_OPT_PERIODIC,//循环模式
                    (void *)MyCallback,//回调函数
                    0,//回调函数参数
                    0,//定时器名字
                    0);//错误类型
              
               //启动定时器
               OSTmrStart (tmr,//要启动的定时器
                  0);//错误类型--成功
              
               OSStart();//启动操作系统
}
  
void MyCallback (OS_TMR *ptmr, void *p_arg)
{
               printf("定时器创建成功\r\n");
}

实验结果如下:

定时器,一次滴答多少时间?


5.5.3 其他函数

给调度器上锁与解锁,一般用于初始化任务。

临界区:是被保护的区域,一般不允许被中断,所以进入之前需要关闭中断,出来时,要打开中断功能。


全部资料51hei下载地址:

程序.7z (373.07 KB, 下载次数: 11)

UCOS之任务间通信、软件定时器补充.docx (4.21 MB, 下载次数: 11)


评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:328014 发表于 2020-6-30 18:19 | 只看该作者
好资料,51黑有你更精彩!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表