本帖最后由 聪聪哥哥 于 2025-9-29 21:41 编辑
之前使用时间任务调度的方式,完成任务调度处理。今天和大家分享另外一种任务调度的方法。
一:消息队列的基础知识:
1、消息队列只 是一种先进先出的队列型数据结构口,实际上是系统内核中的一个内部链表。消息被顺序插入队列中,其中发送进程将消息添加到队列未尾,接受进程从队列头读取消息。
2、多个进程可同时向一个消息队列发送消息,也可以同时从一个消息队列中接收消息。发送进程把消息发送到队列尾部,接受进程从消息队列头部读取消息,消息一旦被读出就从队列中删除。在主程序中进行处理任务,删除任务。若不删除任务,会导致任务堆栈的溢出,从而导致程序的进入硬件错误。
二:结构:
1、消息队列中消息本身由消息类型和消息数据组成,通常使用如下结构:
- typedef struct MESSAGE0 //消息结构体
- {
- int iWParm; //消息参数
- int iLParm;
- int Mnumber; //消息值,取值1-254
- }MESSAGE;
1) iWparm 指定了消息类型,为正整数。
引入消息类型之后,消息队列在逻辑上由一个消息链表转化为多个消息链表。发送进程仍然无条件把消息写入队列的尾部,但接收进程却可以有选择地读取某个特定类型的消息中最接近队列头的一个,即使该消息不在队列头。相应消息一旦被读取,就从队列中删除,其它消息维持不变。
2)iLParm指定了消息的数据。我们可以定义任意的数据类型甚至包括结构来描述消息数据。
2,消息队列的创建:
定义消息队列的深度和当前指针的个数
- MESSAGE ArrayMessage[MES_MAX]; //消息队列最大64
- static char CurrentMesgPoint; //当前需要操作的消息指针
3:创建消息队列的底层驱动函数,组成环形队列,具体如下:
- /***************************************************************************************
- SendMessage()
- 发送消息到消息队列
- Mnumber:消息值
- WParm:消息参数1
- LParm:消息参数2
- ****************************************************************************************/
- void SendMessage(char Mnumber,int WParm,int LParm)
- {
- char i;
- CurrentMesgPoint++; //这样处理的原因是避免在SendMessage函数重入导致的消息被改写的BUG
- if(CurrentMesgPoint >= MES_MAX)
- CurrentMesgPoint = 0;
- i = CurrentMesgPoint;
- if(ArrayMessage[i].Mnumber == 0)
- {
- ArrayMessage[i].Mnumber = Mnumber;
- ArrayMessage[i].iWParm = WParm;
- ArrayMessage[i].iLParm = LParm;
- return;
- }
- }
4,当在主程序中,处理完某个消息的时候,需要将当前的消息任务删除,否则会导致堆栈溢出,直接进入硬件错误:
- /***************************************************************************************
- DeleteMessage()
- 从消息队列中删除消息
- MPoint:消息队列数组下标
- ****************************************************************************************/
- void DeleteMessage(char MPoint)
- {
- ArrayMessage[MPoint].iWParm = 0;
- ArrayMessage[MPoint].iLParm = 0;
- ArrayMessage[MPoint].Mnumber=0; //避免函数重载出现BUG,对Mnumber的改写放在最后面
- }
三:具体的实现过程如下所示:
3.1 在定时器2中,添加几个不同的时间间隔的消息任务:
- void TIM2_IRQHandler(void)
- {
- if(TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
- {
- time2Point++ ;
- if(time2Point %100 == 0)
- SendMessage(MW_TIMERINT,1,0); //发出TIM 100ms中断消息
- if(time2Point %200 == 0)
- SendMessage(MW_TIMERINT,2,0); //发出TIM 200ms中断消息
- if(time2Point %500 == 0)
- SendMessage(MW_TIMERINT,5,0); //发出TIM 500ms中断消息
- if(time2Point %1000 == 0)
- {
- time2Point = 0 ;
- SendMessage(MW_TIMERINT,10,0); //发出TIM 1000ms中断消息
- }
- }
- TIM_ClearITPendingBit( TIM2, TIM_IT_Update );
- }
3.2 在不同的任务中,添加不同的输出任务:
- void System_Tim_Driver(char Tim_Intev)
- {
- switch(Tim_Intev)
- {
- case 1:
- printf("CH32F207! 100ms 任务输出!!\n");//100ms
- break;
- case 2:
- break;
- case 5:
- printf("CH32F207! 500ms 任务输出!!\n");//100ms
- break;
- case 10:
- break ;
- default:break;
- }
- }
3.3 在主程序中,添加对消息任务的处理,采用轮询的方式,进行任务的查找,处理,删除过程:
- while( 1 )
- {
- mspoint ++;
- if(mspoint>=MES_MAX) mspoint = 0; //消息循环查询
- if(ArrayMessage[mspoint].Mnumber !=0)
- {
- switch(ArrayMessage[mspoint].Mnumber)
- {
- case MW_TIMERINT:
- System_Tim_Driver(ArrayMessage[mspoint].iWParm);
- break;
- default:
- break;
- }
- DeleteMessage(mspoint); //删除已经处理的消息
- }
- };
四:串口输出测试如下所示:
感想:
1、采用消息队列方式比之前采用时间任务调度具有更多的灵活性,通信的进程不但没有时间上的要求,也不需要进行同步处理。只需要根据消息触发的顺序,进行执行,从响应时效性上更加的灵活。
2、消息队列是一种先进先出的队列型数据结构;
3、消息队列将输出的信息进行了打包处理,可以保证以消息为单位进行接收;为了防止消息队列的溢出,可以将任务队列加大,从而使实现任务深度的增加,防止任务的丢失。
|