在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。
1. 队列
1.1 队列基本概念
队列(queue)可以用于"任务到任务"、“任务到中断”、"中断到任务"直接传输信息。
- 队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)
- 每个数据大小固定
- 创建队列时就要指定长度、数据大小
- 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
- 也可以强制写队列头部:覆盖头部数据
1.2 传输数据的两种方法
使用队列传输数据时有两种方法:
- 拷贝:把数据、把变量的值复制进队列里
- 引用:把数据、把变量的地址复制进队列里
FreeRTOS使用拷贝值的方法,这更简单:
- 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据
- 无需分配buffer来保存数据,队列中有buffer
- 局部变量可以马上再次使用
- 发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发送任务来释放数据
- 如果数据实在太大,你还是可以使用队列传输它的地址
- 队列的空间有FreeRTOS内核分配,无需任务操心
- 对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必须确保双方任务对这个地址都有访问权限。使用拷贝方法时,则无此限制:内核有足够的权限,把数据复制进队列、再把数据复制出队列。
1.3 队列的核心
- 队列的核心:关中断、环形缓冲区、链表。
- 关中断:实现互斥
- 环形缓冲区:保存数据
- 链表:实现休眠和唤醒
- 使用队列的好处:
- 读时队列中无数据、写时队列满,则任务进入休眠(阻塞状态),不抢占CPU资源,不参与运行,可以把时间留出来给其他任务运行。
- 使用队列可以保证数据在传输过程中不被破坏。
- 支持休眠和唤醒,使程序运行的效率更高。
1.3.1 队列怎样实现互斥访问----关中断
- 互斥的核心:关中断、开中断
- 关中断:
- 使时钟中断无法产生,任务就无法切换;
- 关中断可以认为是关全部中断,也可以是部分中断,包括Tick中断。关了中断后,就相当于裸机程序
- 关中断:
1.3.2 环形缓冲区
1.3.3 怎样休眠/唤醒–链表
- API函数允许指定阻塞时间。
- 如果多个任务阻塞在一个队列上,那么最高优先级别的任务会第一个解除阻塞。
- 注:中断程序中必须使用“FromISR”结尾的API函数!
有两个链表:
- 写队列不成功而挂起
- 每当任务企图向一个满的队列写数据时,任务会进入阻塞状态,直到队列中出现有效空间或者阻塞时间到期。
- 读队列不成功而挂起
- 每当任务企图从一个空的队列读取数据时,任务会进入阻塞状态(这样任务不会消耗任何CPU时间并且另一个任务可以运行)直到队列中出现有效数据或者阻塞时间到期。
写队列:
- 写队列不成功而阻塞 :
- 将任务放入
List_t xTasksWaitingToSend;
从该链表中可以找到写此队列不成功的任务 - 将任务从ReadList ——> DelayList;
- 将任务放入
- 被唤醒 :别的任务读队列,有空间了可以写;超时时间到
- 从
List_t xTasksWaitingToSend;
中移除 - DelayList ——> ReadList ;
- 从
读队列:
- 读队列不成功而阻塞:
- 放入
List_t xTasksWaitingToReceive;
从该链表中可以找到读此队列不成功的任务 - 将任务从ReadList ——> DelayList;
- 放入
- 被唤醒 :别的任务写数据了;超时时间到,
- 从
xTasksWaitingToReceive;
中移除 - DelayList ——> ReadList ;
- 从
1.4 队列的超时唤醒
1.5 读队列流程
- 当有多个任务来读该队列时,需要做一些保护:关中断
- 判断队列中有无数据
- 无 Data
- (1)不阻塞,直接返回,返回ERR
- (2)休眠
- 将任务放入:
xTasksWaitingToReceive;
- 将该任务从 ReadList ——> DelayList中
- 如果有任务优先级 > 当前任务优先级
- 任务切换
- 将任务放入:
- 有 Data
- 无 Data
- 有Data:本来 队列 中就有数据,或 从休眠中被唤醒(写数据了;超时到了)
- 任务从队列中读数据;
- 拷贝方式;
- 将队列中数据数减一;
- 读数据后,队列中有空间了,如果有 任务 A / B 在等待写数据的话,就将其唤醒。唤醒:
- 把
xTasksWaitingToSend
中的第 1 个任务移除 - 将其从 DelayList——> ReadList 中
- 如果恢复的任务优先级 > 当前任务
- 任务切换
- 把
- 任务从队列中读数据;
1.6 队列写流程
任务和中断都可以向队列发送消息。
如果队列未满或者允许覆盖入队,FreeRTOS会将消息拷贝到队列队尾,否则根据用户指定的阻塞超时时间进行阻塞。
当其他任务从其等待的队列中读入了数据(队列未满),该任务将自动由阻塞态转为就绪态。
当任务等待的时间,超过了指定的阻塞时间,计时队列中还不允许入队,任务也会自动从阻塞态转为就绪态,此时发送消息的任务或中断程序会收到一个错误码 errQUEUE_FULL。
- 关中断
- 判断队列中有无空间
- 无 空间
- (1)不阻塞,直接退出,返回ERR
- (2)休眠(阻塞)
- 将任务放入:
xTasksWaitingToSend
- 将该任务从 ReadList ——> DelayList中
- 将任务放入:
- 有 空间
- 无 空间
- 有 空间:本来 队列 中就有空间,或 从休眠中被唤醒(读数据了有空间了;超时到了)
- 任务往队列中写数据(拷贝方式);
- 有任务在等待读队列,唤醒它。唤醒:
- 把
xTasksWaitingToReceive;
中的第 1 个任务移除 - 将其从 DelayList——> ReadList 中
- 如果恢复任务的优先级 > 当前运行的任务优先级
- 任务切换
- 把
- 没有任务在等待读队列
- 数据拷贝成功 ----> 任务切换
一个任务写队列时,如果队列已经满了,它会被挂起,何时被唤醒?
- 超时:
- 任务写队列不成功时,它会被挂起:从ready list移到delayed list中
- 在delayed list中,按照"超时时间"排序
- 系统Tick中断不断发生,在Tick中断里判断delayed list中的任务时间到没?时间到后就唤醒它
2. 队列函数
2.1 创建队列
- 定义一个句柄变量,用于保存创建的队列:
QueueHandle_t xQueue1;
- 使用
xQueueCreate()
创建一个队列;
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, // 队列长度,最多能存放多少个数据
UBaseType_t uxItemSize // 每个数据的大小:以字节为单位);
2.1.1 队列结构体
2.1.2 xQueueGenericCreate函数
在xQueueGenericCreate
内部会分配队列空间、初始化队列。
2.2 向队列发送消息函数(写队列)
- 举例
// 举例
// 创建队列
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));
// 写队列:将 input 中的数据写入到 g_xQueuePlatform
xQueueSend(g_xQueuePlatform,&input,0);
// 读队列:从 g_xQueuePlatform 中读数据,放入到 idata中
xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY);
// 删除队列
vQueueDelete(g_xQueuePlatform);
2.2.1 写队列函数
- xQueueSend()函数
- xQueueSend() 等 同 于 xQueueSendToBack()。
- 用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以 引用的形式。
- 该函数绝对不能在中断服务程序里面被调用,中断中必须使用带有中断保护 功能的 xQueueSendFromISR()来代替。
- xQueueSendFromISR()
xQueueSendFromISR(): 用于在中断服务程序中向队列尾部发送一个队列消息。
xQueueSendToBackFromISR 等同于 xQueueSendFromISR ()。
2.2.2 通用消息队列发送函数 xQueueGenericSend()(任务)
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue, // 指针,指向要发送的消息
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
- xQueue:消息队列句柄;
- pvItemToQueue: 指针,指向要发送的消息
- xTicksToWait:指定阻塞超时时间
- xCopyPosition:发送数据到消息队列的位置:
- queueSEND_TO_BACK:发送到队尾;
- queueSEND_TO_FRONT:发送到队头;
- queueOVERWRITE:以覆盖的方式发送
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue, // 指针,指向要发送的消息
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
{
BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
for( ;; )
{
taskENTER_CRITICAL();
{
/* 队列未满,或,以覆盖的方式发送,无队列满或未满,都可以发送*/
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
|| ( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
// 队列未满,将消息拷贝到队列中
xYieldRequired =
prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* 有任务在等待读数据 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 将任务从事件链表xTasksWaitingToReceive中删除,
事件链表按由新阿基顺序排序,链表中的第一个优先级最高。
放入到就绪链表。
如果恢复的任务优先级比当前运行任务的优先级高,
那么需要进行一次任务切换*/
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xYieldRequired != pdFALSE )
{
/* 没有等待的任务,拷贝成功也需要任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
taskEXIT_CRITICAL();
return pdPASS;
}
else /* 队列已满 */
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 不指定阻塞时间,直接退出 */
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
else if( xEntryTimeSet == pdFALSE )
{
/* 指定了超时时间,系统会初始化阻塞超时结构体变量,
初始化进入阻塞的时间 xTickCount和溢出次数 xNumOfOverflows,
为后面的阻塞任务做准备*/
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* 挂起调度器 ,不会进行任务切换,
但不能禁止中断的发生,所以还需要给队列上锁*/
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* 检查指定的超时时间是否已经过去了 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{ // 没过
/* 队列是满的 */
if( prvIsQueueFull( pxQueue ) != pdFALSE )
{ /* 根据用户指定的超时时间来阻塞一下任务*/
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
/* 将其放入到写队列不成功链表中,xTasksWaitingToSend*/
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
/* 队列解锁 */
prvUnlockQueue( pxQueue );
/* 恢复调度器,如果调度器挂起期间有任务解除阻塞,
并且解除阻塞的任务优先级比当前任务高,就需要进行一次任务切换*/
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
}
else // 队列没满,有空间
{
/* 重新发送消息 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else // 超时时间已过,返回一个errQUEUE_FULL错误代码,退出
{
/* The timeout has expired. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
}
}
2.2.2.1 prvCopyDataToQueue
- 尾部入队
- 头部入队
2.2.2.2 xTaskRemoveFromEventList
BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList )
{
TCB_t *pxUnblockedTCB;
BaseType_t xReturn;
/* 事件链表按优先级顺序排序,链表中第一个的优先级最高*/
pxUnblockedTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxEventList );
configASSERT( pxUnblockedTCB );
( void ) uxListRemove( &( pxUnblockedTCB->xEventListItem ) );
/*调度器未挂起 */
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{ /* 从阻塞链表移除 */
( void ) uxListRemove( &( pxUnblockedTCB->xStateListItem ) );
/* 添加到就绪链表 */
prvAddTaskToReadyList( pxUnblockedTCB );
}else{
/* 调度器挂起,将 任务放入xPendingReadyList链表(任务已就绪,但调度器挂起)*/
vListInsertEnd( &( xPendingReadyList ), &( pxUnblockedTCB->xEventListItem ) );
}
if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority )
{
xReturn = pdTRUE;
xYieldPending = pdTRUE;
}else{
xReturn = pdFALSE;
}
#if( configUSE_TICKLESS_IDLE != 0 )
{
prvResetNextTaskUnblockTime();
}
#endif
return xReturn;
}
2.2.2.3 vTaskPlaceOnEventList
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )
{
configASSERT( pxEventList );
/* 按优先级大小插入到事件链表中,高优先级插入到链表头部,
确保 最高优先级的任务先唤醒 */
vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
}
2.3 从队列中读取消息函数
2.3.1 读队列函数
- xQueueReceive()
xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。接收的消息是以拷贝 的形式进行的,所以我们必须提供一个足够大空间的缓冲区。 具体能够拷贝多少数据到缓 冲区,这个在队列创建的时候已经设定。
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
- xQueuePeek()
接收到了消息但不删除
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
- xQueueReceiveFromISR()与 xQueuePeekFromISR()
xQueueReceiveFromISR()是 xQueueReceive ()的中断版本,用于在中断服务程序中接收 一个队列消息并把消息从队列中删除;
xQueuePeekFromISR()是 xQueuePeek()的中断版本, 用于在中断中从一个队列中接收消息,但并不会把消息从队列中移除。
2.3.2 从队列读取消息函数 xQueueReceive
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
for( ; ; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* 看看队列中有没有消息 */
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* 拷贝消息到用户指定区域 */
prvCopyDataFromQueue( pxQueue, pvBuffer );
traceQUEUE_RECEIVE( pxQueue );
/* 读取消息并且消息出队,当前队列中的消息个数减一*/
pxQueue->uxMessagesWaiting = ( UBaseType_t ) ( uxMessagesWaiting - ( UBaseType_t ) 1 );
/* 是否有等待发送消息的任务 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
// 有,将其从xTasksWaitingToSend移除,放入就绪队列
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
// 如果恢复任务的优先级比当前任务高,进行任务切换
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
taskEXIT_CRITICAL();
traceRETURN_xQueueReceive( pdPASS );
return pdPASS;
}
else
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 不阻塞,直接返回 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
traceRETURN_xQueueReceive( errQUEUE_EMPTY );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* 阻塞一定的超时时间 */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* 检查超时时间是否已经过去了 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
/* 时间未过,队列为空 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
/* 将其放入xTasksWaitingToReceive链表 */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{/* 如果有任务优先级比当前任务高,会进行一次任务切换 */
taskYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果队列有消息了,就再试一次获取消息 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
/* 超时时间已过,退出 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
/* 如果队列还是空的,返回错误码errQUEUE_EMPTY */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
traceRETURN_xQueueReceive( errQUEUE_EMPTY );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
2.3.2.1 prvCopyDataFromQueue
2.4 消息队列删除函数 vQueueDelete()
void vQueueDelete( QueueHandle_t xQueue )
队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都
会被系统回收清空,而且不能再次使用这个消息队列了。
vPortFree( pxQueue ); // 释放消息队列的内存
3. 队列的应用
3.1 13_queue_game
本节代码为:13_queue_game。以前使用环形缓冲区传输红外遥控器的数据,本程序改为使用队列。
01_game_template使用轮询的方式从环形缓冲区读取红外遥控器的键值,13_queue_game把环形缓冲区改为队列。
- (1)创建队列
- (2)红外中断IRReceiver_IRQ_Callback:解析出按键后,写队列
- (3)挡球板任务platform_task:读队列,控制挡球板的运动
3.2 多设备玩游戏
本节代码为:14_queue_game_encoder。实现红外遥控器和旋转编码器控制挡球板的运动。
- 创建挡球板队列A:g_xQueuePlatform;
- 创建旋转编码器队列B:g_xQueueRotary;
- 挡球板任务platform_task:读队列A(g_xQueuePlatform),控制挡球板的运动
- 红外中断IRReceiver_IRQ_Callback:解析出按键后,写队列A(g_xQueuePlatform)
- 旋转编码器:
- 旋转编码器中断RotaryEncoder_IRQ_Callback:解析出编码器状态,写队列B(g_xQueueRotary)
- 旋转编码器任务RotaryEncoder_task:读队列B(g_xQueueRotary),处理数据,写挡球板队列A(g_xQueuePlatform)
3.3 队列集
3.3.1 队列集简述
在上一个代码中,
假设有2个输入设备:红外遥控器、旋转编码器,它们的驱动程序应该专注于“产生硬件数据”,不应该跟“业务有任何联系”。比如:红外遥控器驱动程序里,它只应该把键值记录下来、写入某个队列,它不应该把键值转换为游戏的控制键。在红外遥控器的驱动程序里,不应该有游戏相关的代码,这样,切换使用场景时,这个驱动程序还可以继续使用。
把红外遥控器的按键转换为游戏的控制键,应该在游戏的任务里实现。
要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得到数据后再分别转换为游戏的控制键。
InputTask如何及时读取到多个队列的数据?要使用队列集。
队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:
- 创建队列A,它的长度是n1
- 创建队列B,它的长度是n2
- 创建队列集S,它的长度是“n1+n2”
- 把队列A、B加入队列集S
- 这样,写队列A的时候,会顺便把队列A的句柄写入队列集S
- 这样,写队列B的时候,会顺便把队列B的句柄写入队列集S
- InputTask先读取队列集S,它的返回值是一个队列句柄,这样就可以知道哪个队列有有数据了;然后InputTask再读取这个队列句柄得到数据。
3.3.2 程序框架
- 修改“Core\Inc\FreeRTOSConfig.h“,增加:
/* USER CODE BEGIN Includes */
#define configUSE_QUEUE_SETS 1
/* Section where include file can be added */
/* USER CODE END Includes */
- 在STM32CubeMX里把堆
TOTAL_HEAP_SIZE
调大,比如8000: - 红外遥控器代码:
- 声明一个队列
- 创建红外遥控器队列
- 写队列
/*创建红外队列*/
static QueueHandle_t g_xQueueIR;
/* 获取红外的队列句柄 */
QueueHandle_t GetQueueIR(void)
{
return g_xQueueIR;
}
/* 中断函数中写队列*/
void IRReceiver_IRQ_Callback(void)
{
/* -----写队列----*/
data.dev = 0;
data.val = 0;
xQueueSendToBackFromISR(g_xQueueIR,&data,NULL);
}
/* 红外接收器的初始化函数 */
void IRReceiver_Init(void)
{
/* 创建队列 */
g_xQueueIR = xQueueCreate(IR_QUEUE_LEN, sizeof(struct ir_data));
}
- 旋转编码器代码
- 声明一个队列
- 创建旋转编码器队列
- 写队列
static QueueHandle_t g_xQueueRotary; /* 旋转编码器队列 */
/* 旋转编码器创建队列的第三个参数 */
static uint8_t g_ucQueueRotaryBuff[10*sizeof(struct rotary_data)];
/* 旋转编码器创建队列的第四个参数 :用来保存队列的数据结构*/
static StaticQueue_t g_xQueueRotaryStaticstruct;
static int32_t g_count = 0;
static int32_t g_speed = 0; /* 速度(正数表示顺时针旋转,负数表示逆时针旋转,单位:每秒转动次数) */
/* 获取旋转编码器的队列句柄 */
QueueHandle_t GetQueueRotary(void)
{
return g_xQueueRotary;
}
void RotaryEncoder_IRQ_Callback(void)
{
/* 写队列 */
rotarydata.cnt = g_count;
rotarydata.speed = g_speed;
xQueueSendToBackFromISR(g_xQueueRotary,&rotarydata,NULL);
}
void RotaryEncoder_Init(void)
{
/* 创建旋转编码器队列,*/
g_xQueueRotary = xQueueCreateStatic(ROTARY_QUEUE_LEN,sizeof(struct rotary_data),g_ucQueueRotaryBuff,
&g_xQueueRotaryStaticstruct);
}
- 游戏
- 创建队列集,包含:
- 红外遥控器队列
- 旋转编码器队列
- 创建输入任务
InputTask
:检测多个输入设备并调用对应处理函数- 读队列集,得到有数据的队列句柄
- 读队列句柄得到数据,处理数据
- 红外遥控器处理数据
ProcessIRData
:- 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列
- 旋转编码器处理数据
ProcessRotaryData
- 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列
- 挡球板任务
- 读挡球板队列
- 移动挡球板
void game1_task(void *params)
{
/* 创建队列,队列集,创建输入任务InputTask*/
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));
g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN+ ROTARY_QUEUE_LEN);
g_xQueueIR = GetQueueIR();
g_xQueueRotary = GetQueueRotary();
xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);
xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);
xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
}
/* 输入任务
* 检测多个输入设备并调用对应处理函数
*/
static void InputTask(void *params)
{
QueueSetMemberHandle_t xQueueSetHandle;
while(1)
{
/* 读队列集,得到有数据队列句柄*/
xQueueSetHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);
if (xQueueSetHandle)
{
/* 读队列句柄得到数据,处理数据*/
if (xQueueSetHandle == g_xQueueIR)
{
ProcessIRData();
}
else if (xQueueSetHandle == g_xQueueRotary)
{
ProcessRotaryData();
}
}
}
}
/* 读队列句柄得到数据,处理数据
* 读取红外遥控器键值并转换为游戏控制键,写入挡球板队列
*/
static void ProcessIRData(void)
{
struct ir_data irdata;
static struct input_data input;
/* 读红外队列 */
xQueueReceive(g_xQueueIR, &irdata, 0);
if (irdata.val == IR_KEY_LEFT)
{
input.dev = irdata.dev;
input.val = UPT_MOVE_LEFT;
}
else if (irdata.val == IR_KEY_RIGHT)
{
input.dev = irdata.dev;
input.val = UPT_MOVE_RIGHT;
}
else if (irdata.val == IR_KEY_REPEAT)
{
/* 保持不变 */
}
else
{
input.dev = irdata.dev;
input.val = UPT_MOVE_NONE;
}
/* 写挡球板队列 */
xQueueSend(g_xQueuePlatform,&input,0);
}
/* 读队列句柄得到数据,处理数据
* 读取旋转编码器数据并转换为游戏控制键,写入挡球板队列
*/
static void ProcessRotaryData(void)
{
struct rotary_data rdata;
static struct input_data input;
int left, cnt;
/* 读旋转编码器队列 */
xQueueReceive(g_xQueueRotary,&rdata,0);
/* 处理数据 */
/* 判断速度:负数表示向左转动,正数表示向右转动 */
if (rdata.speed < 0)
{
left = 1;
rdata.speed = 0 - rdata.speed;
}
else
{
left = 0;
}
// cnt = rotarydata.speed / 10;
// if (!cnt)
// cnt = 1;
if (rdata.speed > 100)
cnt = 4;
else if (rdata.speed > 50)
cnt = 2;
else
cnt = 1;
/* 写挡球板队列 */
input.dev = 1;
input.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
for (int i = 0; i < cnt; i++)
{
xQueueSend(g_xQueuePlatform,&input,0);
}
}
/* 挡球板任务 */
static void platform_task(void *params)
{
byte platformXtmp = platformX;
//uint8_t data;
//uint8_t last_data;
static struct input_data idata;
// Draw platform
draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
while (1)
{
/* 读取红外遥控器 */
// 读环形缓冲区
//if (0 == IRReceiver_Read(&dev, &data))
/* 读队列 */
xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY);
uptMove = idata.val;
// Hide platform
draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
// Move platform
if(uptMove == UPT_MOVE_RIGHT)
platformXtmp += 3;
else if(uptMove == UPT_MOVE_LEFT)
platformXtmp -= 3;
uptMove = UPT_MOVE_NONE;
// Make sure platform stays on screen
if(platformXtmp > 250)
platformXtmp = 0;
else if(platformXtmp > g_xres - PLATFORM_WIDTH)
platformXtmp = g_xres - PLATFORM_WIDTH;
// Draw platform
draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
platformX = platformXtmp;
}
}
3.5 队列集-mpu6050
在上个代码的基础上,添加mpu6050控制挡球板运动
- driver_mpu6050.c代码中:
- 创建队列
- 创建mpu6050任务
- 读数据
- 解析数据
- 写队列
g_xQueueMPU6050
- game1.c代码:
- 将mpu6050队列
g_xQueueMPU6050
加入队列集; - 读取MPU6050数据并转换为游戏控制键,写入挡球板队列
- 将mpu6050队列
/* --------------在driver_mpu6050.c----------------*/
static QueueHandle_t g_xQueueMPU6050;/*MPU6050队列*/
/* 获取MPU6050的队列句柄 */
QueueHandle_t GetQueueMPU6050(void)
{
return g_xQueueMPU6050;
}
int MPU6050_Init(void)
{
g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
}
void MPU6050_Task(void *params)
{
int16_t AccX;
struct mpu6050_data result;
int ret;
extern volatile int bInUsed;
while (1)
{
while (bInUsed);
bInUsed = 1;
ret = MPU6050_ReadData(&AccX, NULL, NULL, NULL, NULL, NULL);
bInUsed = 0;
/* 读数据 */
if (0 == ret)
{
/* 解析数据 */
MPU6050_ParseData(AccX, 0, 0, 0, 0, 0, &result);
/* 写队列 */
xQueueSend(g_xQueueMPU6050,&result,0);
}
/* delay */
vTaskDelay(50);
}
}
/* --------------game1.c----------------*/
/* 读队列句柄得到数据,处理数据
* 读取MPU6050数据并转换为游戏控制键,写入挡球板队列
*/
static void ProcessMPU6050Data(void)
{
struct mpu6050_data mpudata;
static struct input_data input;
/* 读MPU6050队列 */
xQueueReceive(g_xQueueMPU6050,&mpudata,0);
/* 处理数据 */
/* 判断x轴的角度:大于90°则挡球板向左移动,小于90°则挡球板向右移动 */
if (mpudata.angle_x > 90)
{
input.val = UPT_MOVE_LEFT;
}
else if (mpudata.angle_x < 90)
{
input.val = UPT_MOVE_RIGHT;
}
else
{
input.val = UPT_MOVE_NONE;
}
/* 写挡球板队列 */
input.dev = 1;
xQueueSend(g_xQueuePlatform,&input,0);
}
分发数据给多个任务(赛车游戏)
实现逻辑:
- 红外遥控器的中断函数解析出按键值后,写入3个队列:3个赛车任务读取其中一个队列得到按键数据。
- 采用game2.c代码
- 红外遥控器代码
static QueueHandle_t g_xQueueIR;/*红外队列*/
static QueueHandle_t g_xQueues[10]; /* 队列数组 */
static int g_Queue_cnt = 0;
/* 获取红外的队列句柄 */
QueueHandle_t GetQueueIR(void)
{
return g_xQueueIR;
}
/* 使用该函数写三个队列 */
void RegisterQueueHandle(QueueHandle_t g_xQueueHandle)
{
if (g_Queue_cnt < 10)
{
g_xQueues[g_Queue_cnt] = g_xQueueHandle;
g_Queue_cnt++;
}
}
static void DispatchKey(struct ir_data *pidata)
{
int i;
/* 写三个队列 */
for (i = 0; i < g_Queue_cnt; i++)
{
xQueueSendToBackFromISR(g_xQueues[i],pidata,NULL);
}
}
void IRReceiver_IRQ_Callback(void)
{
struct ir_data data;
/* -----写队列----*/
data.dev = 0;
data.val = 0;
DispatchKey(&data);
}
void IRReceiver_Init(void)
{
RegisterQueueHandle(g_xQueueIR);
}
- game2代码
/* 1. 创建队列
* 2. 注册队列
* 3. 显示汽车
* 4. 读取队列,控制汽车的移动
*/
static void CarTask(void *params)
{
struct car *pcar = params;
struct ir_data irdata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(struct ir_data));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
while(1)
{
/* 读取按键值:读队列*/
xQueueReceive(xQueueIR, &irdata, portMAX_DELAY);
/* 控制汽车往右移动 */
if (irdata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x += 20;
if (pcar->x > g_xres - CAR_LENGTH)
pcar->x = g_xres - CAR_LENGTH;
/* 重新显示汽车 */
ShowCar(pcar);
}
}
}
}
/* 1. 画路标
* 2. 创建3个汽车任务
*/
void car_game(void)
{
//int x;
int i,j;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 画出路标 */
for (i = 0; i < 3; i++)
{
for (j = 0; j < 8; j++)
{
draw_bitmap(16 * j, 16 + 17 * i, roadMarking, 8, 1, NOINVERT, 0);
draw_flushArea(16 * j, 16 + 17 * i, 8, 1);
}
}
xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);
}
标签:FreeRTOS,队列,void,else,任务,data,pxQueue
From: https://blog.csdn.net/luckyme_/article/details/143028234