任务间通信 5.4.1 信号量 信号量是用来保护共享资源用的,表示共享资源的个数。共享资源被占用一个,信号量的指会减1,共享资源被释放一个,信号量的值会加1。 (理解:USB口的占用与释放,假设一个电脑有3个USB,插入一个USB设备,电脑的USB资源会减少1,此时电脑的USB资源还有2;拔出USB设备,电脑的USB资源会增加1,此时电脑的USB资源有3个USB) 其实信号量的本质就是一个操作系统计数器(0~65535),实际用于实现任务间同步执行。 需要掌握的函数如下: 实验代码如下:
- #include "main.h"
- #include "includes.h"
-
- /** 任务0 **/
- #define TASK0_PRI 8 //任务优先级
- #define TASK0_STK_SIZE 256 //任务栈大小
- OS_STK stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
- void Task0 (void *p_arg); //函数声明
-
- /** 任务1 **/
- #define TASK1_PRI 9 //任务优先级
- #define TASK1_STK_SIZE 256 //任务栈大小
- OS_STK stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
- void Task1 (void *p_arg); //函数声明
-
- /** 任务2 **/
- #define TASK2_PRI 10 //任务优先级
- #define TASK2_STK_SIZE 256 //任务栈大小
- OS_STK stack2[TASK2_STK_SIZE]; //数组作为任务堆栈
- void Task2 (void *p_arg); //函数声明
-
- OS_EVENT *sem;//全局变量,创建信号量、等待信号量、发布信号量都需要用到该指针。
-
- int main(void)
- {
- OSSysTickInit();//滴答定时器初始化
- USART1_Init(115200);//初始化串口,用于调试
-
- OSInit(); //操作系统初始化
-
- sem=OSSemCreate(2);//初始化--默认创建2个信号量
- OSTaskCreate (Task0,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建一个任务,任务名:Task0
- OSTaskCreate (Task1,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建一个任务,任务名:Task1
- OSTaskCreate (Task2,NULL,&stack2[TASK2_STK_SIZE-1],TASK2_PRI);//创建一个任务,任务名:Task2
-
- OSStart(); //启动操作系统
- }
-
- void Task0 (void *p_arg) //实现任务Task0
- {
- u8 k=0;//给个默认值0,表示无键状态
- KEY_Init();//按键初始化,放在对应任务中
- while(1)
- {
- k=Key_Scanf(0);//按键扫描
- switch (k)
- {
- case KEY_ONE: //按键1按下,发布一个信号量
- OSSemPost(sem);//发送信号量
- break;
- default:
- break;
- }
- OSTimeDly(1);//高优先级释放CPU,延迟不可太长,延时太长会影响按键的灵敏度。
- }
- }
-
- void Task1 (void *p_arg) //实现任务Task1
- {
- while(1)
- {
- //等待一个信号量
- OSSemPend (sem,//信号量
- 0,//超时时间
- 0);//错误类型--没有错误
- printf("任务1\r\n");
- OSTimeDly(100);//5ms一次滴答,100*5=500ms,打印一次
- }
- }
-
- void Task2 (void *p_arg) //实现任务Task2
- {
- while(1)
- {
- //等待一个信号量
- OSSemPend (sem,//信号量
- 0,//超时时间
- 0);//错误类型--没有错误
- printf("任务2\r\n");
- OSTimeDly(100);//5ms一次滴答,100*5=500ms,打印一次
- }
- }
-
复制代码
因为创建了两个信号量,按下复位按键,同时打印出任务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)。 需要掌握的函数如下: 实验代码如下:
- #include "main.h"
- #include "includes.h"
-
- /** 任务0 **/
- #define TASK0_PRI 8 //任务优先级
- #define TASK0_STK_SIZE 256 //任务栈大小
- OS_STK stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
- void Task0 (void *p_arg); //函数声明
-
- /** 任务1 **/
- #define TASK1_PRI 9 //任务优先级
- #define TASK1_STK_SIZE 256 //任务栈大小
- OS_STK stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
- void Task1 (void *p_arg); //函数声明
-
- OS_EVENT *mutex;//全局变量,创建信号量、等待信号量、发布信号量都需要用到该指针。
-
- int main(void)
- {
- OSSysTickInit();//滴答定时器初始化
- USART1_Init(115200);//初始化串口,用于调试
-
- OSInit(); //操作系统初始化
-
- //创建互斥信号量
- mutex=OSMutexCreate (5,//空闲优先级
- 0);//错误类型
- OSTaskCreate (Task0,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建一个任务,任务名:Task0
- OSTaskCreate (Task1,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建一个任务,任务名:Task1
-
- OSStart(); //启动操作系统
- }
-
- void Task0 (void *p_arg) //实现任务Task0
- {
- while(1)
- {
- //获取互斥信号量
- OSMutexPend (mutex,//互斥信号量
- 0,//超时时间
- 0);//错误类型
- for(int i=0;i<5;i++)
- {
- printf("任务0--%d\r\n",i);//
- OSTimeDly(100);//
- }
- //释放互斥信号量
- OSMutexPost (mutex);
- }
- }
-
- void Task1 (void *p_arg) //实现任务Task1
- {
- while(1)
- {
- //获取互斥信号量
- OSMutexPend (mutex,//互斥信号量
- 0,//超时时间
- 0);//错误类型
- for(int i=0;i<5;i++)
- {
- printf("任务1--%d\r\n",i);//
- OSTimeDly(100);//
- }
- //释放互斥信号量
- OSMutexPost (mutex);
- }
- }
-
复制代码
烧录代码,结果如下图: 实验表明,假设多个任务在访问同一资源,只有等正在访问的任务使用完并释放资源,下一个任务才能访问使用。 假设不加互斥信号量进行互斥访问,代码如下, 结果如下: 5.4.3 消息邮箱 用于任务与任务之间交换数据(任务与任务之间的通信)。消息邮箱只能存放一则消息,消息的内容长短不限制。 需要掌握的函数: 实验代码:
- #include "main.h"
- #include "includes.h"
-
- /** 任务0 **/
- #define TASK0_PRI 8 //任务优先级
- #define TASK0_STK_SIZE 256 //任务栈大小
- OS_STK stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
- void SendTask (void *p_arg); //函数声明
-
- /** 任务1 **/
- #define TASK1_PRI 9 //任务优先级
- #define TASK1_STK_SIZE 256 //任务栈大小
- OS_STK stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
- void ReceiveTask (void *p_arg); //函数声明
-
- OS_EVENT *mbox;//全局变量,创建邮箱、发送消息、接收消息,都需要用到该指针。
-
- int main(void)
- {
- OSSysTickInit();//滴答定时器初始化
- USART1_Init(115200);//初始化串口,用于调试
-
- OSInit(); //操作系统初始化
- //创建一个邮箱
- mbox=OSMboxCreate (NULL);//初始化消息的地址:NULL
-
- OSTaskCreate (SendTask,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建发送任务
- OSTaskCreate (ReceiveTask,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建接收任务
-
- OSStart(); //启动操作系统
- }
-
- void SendTask (void *p_arg) //发送任务
- {
- u8 k=0;//给个默认值0,表示无键状态
- KEY_Init();//按键初始化,放在对应任务中
- while(1)
- {
- k=Key_Scanf(0);//按键扫描
- switch (k)
- {
- case KEY_ONE: //按键1按下,发布一则消息
- OSMboxPost (mbox,//邮箱
- "hello 51黑");//邮箱内容
- break;
- case KEY_TWO: //按键2按下,发布一则消息
-
- OSMboxPost (mbox,//邮箱
- "hello world");//邮箱内容
- OSMboxPost (mbox,//邮箱
- "hello xixi");//邮箱内容
- OSMboxPost (mbox,//邮箱
- "hello haha");//邮箱内容
- break;
- default:
- break;
- }
- OSTimeDly(1);//释放CPU使用权
- }
- }
-
- void ReceiveTask (void *p_arg) //接收任务
- {
- u8 *str;
- while(1)
- {
- //接收一条消息
- str=OSMboxPend (mbox,//邮箱地址
- 0,//死等
- 0);//发送成功
- printf("接收到的消息:%s\r\n",str);//开始默认打印三次,因为默认开始创建3个信号量。
- OSTimeDly(1);//释放CPU使用权
- }
- }
-
复制代码
实验结果如下: 按下按键1: 按下按键2: 原因是接收方接收不过来了,造成了数据丢失。这时需要引入消息队列。 5.4.4 消息队列 消息邮箱只能发送一则消息,获取消息的地方如果处理比较慢就会丢失消息。消息队列能存储一队消息,能很好的避免接收方处理能力弱而丢失消息的问题。队列是一种数据结构,遵循先进先出原则。 需要掌握的函数: 实验代码:
- #include "main.h"
- #include "includes.h"
-
- /** 任务0 **/
- #define TASK0_PRI 8 //任务优先级
- #define TASK0_STK_SIZE 256 //任务栈大小
- OS_STK stack0[TASK0_STK_SIZE]; //数组作为任务堆栈
- void SendTask (void *p_arg); //函数声明
-
- /** 任务1 **/
- #define TASK1_PRI 9 //任务优先级
- #define TASK1_STK_SIZE 256 //任务栈大小
- OS_STK stack1[TASK1_STK_SIZE]; //数组作为任务堆栈
- void ReceiveTask (void *p_arg); //函数声明
-
- OS_EVENT *q;//全局变量,创建队列、发送消息、接收消息,都需要用到该指针。
-
-
- void *queue[10];//队列
-
- int main(void)
- {
- OSSysTickInit();//滴答定时器初始化
- USART1_Init(115200);//初始化串口,用于调试
-
- OSInit(); //操作系统初始化
-
- //创建一个队列
- q=OSQCreate(queue,//队列
- 10);//队列的大小
-
- OSTaskCreate (SendTask,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//创建发送任务
- OSTaskCreate (ReceiveTask,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//创建接收任务
-
- OSStart(); //启动操作系统
- }
-
- void SendTask (void *p_arg) //发送任务
- {
- u8 k=0;//给个默认值0,表示无键状态
- KEY_Init();//按键初始化,放在对应任务中
- while(1)
- {
- k=Key_Scanf(0);//按键扫描
- switch (k)
- {
- case KEY_ONE: //按键1按下,发布一则消息
- OSQPost (q,//队列地址
- "lele");//需要发送的内容
- OSQPost (q,//队列地址
- "学习");//需要发送的内容
- OSQPost (q,//队列地址
- "不可能");//需要发送的内容
- break;
- case KEY_TWO: //按键2按下,发布一则消息
- OSQPost (q,//队列地址
- "哈哈");//需要发送的内容
- break;
- default:
- break;
- }
- OSTimeDly(1);//释放CPU使用权
- }
- }
-
- void ReceiveTask (void *p_arg) //接收任务
- {
- char *strs;
- while(1)
- {
- //接收一条消息
- strs=OSQPend(q,//队列
- 0,//死等
- 0);//错误类型
- printf("接收到的消息:%s\r\n",strs);//开始默认打印三次,因为默认开始创建3个信号量。
- OSTimeDly(1);//释放CPU使用权
- }
- }
复制代码
实验结果如下: 发送三条消息,接收到三条消息,没有数据丢失。 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)
|