目次
- 序文
- 1. キューの概要
- 2. キューの構造
- 3. キューの作成
- 4. メッセージをキューに送信します
-
- 4.1 関数プロトタイプ
- 4.2 関数 xQueueSend()、xQueueSendToBack()、および xQueueSendToFront()
- 4.2 関数 xQueueOverwrite()
- 4.3 関数 xQueueGenericSend()
- 4.4 関数 xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()
- 4.5 関数 xQueueOverwriteFromISR()
- 4.6 関数 xQueueGenericSendFromISR()
- 4.7 タスクレベルの一般的なエンキュー機能
- 4.8 割り込みレベルの一般エンキュー機能
- 5. キューのロックとロック解除
- 6. キューからメッセージを読み取る
- 7. キュープログラム例
序文
プロジェクト アプリケーションを作成する場合、あるタスクが別のタスクと「通信」することがよくあります。オペレーティング システムがない場合は、グローバル変数でこの問題を解決できますが、オペレーティング システムを使用するアプリケーションですべての変数が使用されている場合、情報を渡す必要があります。グローバル変数は「リソース管理」の問題を伴い、メンテナンスが容易ではありません。複雑なロジックを備えたプログラムでは、誰がグローバル変数を使用または変更したかを追跡することができないことがよくあります。FreeRTOS は、このために「キュー」と呼ばれるメカニズムを提供します。
1. キューの概要
タスク間、タスクと割り込み間の通信用にキューを用意し、タスク間、タスク間、タスクと割り込み間でメッセージを送信することができ、固定サイズの限られたデータをキューに格納することができます。タスクとタスク、タスクと割り込みの間で交換されるデータは、キューアイテムと呼ばれるキューに格納されます。キューに保持できるデータの最大数をキュー長といい、キューを作成する際にデータ項目のサイズとキューの長さを指定します。キューはメッセージの送信に使用されるため、メッセージ キューとも呼ばれます。FreeRTOS のセマフォもキューに応じて実装されています。
1.1 データストレージ
通常、キューは先入れ先出し (FIFO) ストレージ バッファ メカニズムを採用しています。つまり、データがキューに送信される (エンキューとも呼ばれます) と、データは常にキューの最後に送信されます。、キューからデータを抽出するとき (デキューとも呼ばれます)、キューの先頭からデータが抽出されます。ただし、後入れ先出し (LIFO) ストレージ バッファリングも使用でき、FreeRTOS のキューも LIFO ストレージ バッファリング メカニズムを提供します。
データはキューに送信された後、キューに格納されますが、これは、キューに格納されたデータの元の値が元のデータへの参照 (つまり、データへのポインタ) ではないことを意味します。転送プロセスは、値の転送とも呼ばれます。UCOS のメッセージキューは UCOS と異なりリファレンス転送を採用しており、渡されるのはメッセージポインタです。ただし、この欠点は、参照渡しされたフラワー メッセージが表示されたままでなければならないこと、つまり、メッセージの内容が有効である必要があることです。この場合、たとえば、関数のローカル変数はいつでも削除されますが、参照渡しの利点も大きく、キューにデータを転送する時間が大幅に短縮されることは明らかです。
値の受け渡しのもう 1 つの利点は、データがキューに送信された後、最初にデータを格納したバッファーを削除または上書きできるため、バッファーを永久に再利用できることです。ただし、ネットワーク上の情報送信の場合、大量の情報を送信する必要がある場合が多いため、必然的にメッセージキューの送信処理に時間がかかります。この場合、ポインタアドレスを渡すことができます。データ群の領域のアドレスに続いて、そのアドレスを先頭とするデータ群を使用することができます。
1.2 マルチタスクアクセス
どのタスクもキューにメッセージを送信したり、キューからメッセージを抽出したりできます。
1.3 デキューのブロック
タスクがキューからメッセージを読み取ろうとするときに、キューから読み取ったメッセージが無効な場合にタスクがブロックされる時間を指定できます。デキューはキューからメッセージを読み取ることであり、デキュー ブロッキングはキューからメッセージを読み取るタスクです。たとえば、タスク A はシリアル ポートで受信したデータを処理するために使用されます。シリアル ポートがデータを受信すると、そのデータはキュー Q に入れられ、タスク A はキュー Q からデータを読み取ります。しかし、このときキューQが空であれば、まだデータが無いということなので、このときにタスクAが読み出しに来たら何も取得してはいけないのですが、どうすればよいでしょうか?タスク A には 3 つの選択肢があり、
1: データを読み取らずに読み取りプロセスを直接終了する、
2: 一定時間待機する (いわゆるブロッキング タイムであり、キュー内のデータの読み取りはこの期間中に終了します)、それ以外の場合は、ブロック時間が到着するのを待った後、遅延リストから準備完了リストに入ります。
3: 待機時間を最大値 portMAX_DELAY に設定します。つまり、データが読み取られない場合は、ブロック状態に入り待機します。データを受信するまで。
どちらを選択するかはブロック時間によって決まり、ブロック時間の単位はクロックの刻み数です。
1.4 エンキューのブロック
キューに参加するということは、メッセージをキューに送信し、そのメッセージをキューに追加することを意味します。デキュー ブロックと同様に、タスクがメッセージをキューに送信するときに、ブロック時間も設定できます。たとえば、タスク B はメッセージ キュー Q にメッセージを送信しますが、この時点でキュー Q がいっぱいである場合、送信は失敗する必要があります。このとき、タスク B でも上記のタスク A と同じ問題が発生します。これら 2 つのケースの処理プロセスは似ていますが、1 つはキュー Q にメッセージを送信する点、もう 1 つはキュー Q からメッセージを読み取る点が異なります。
1.5 キュー操作プロセス図
次の図は、キューに入るプロセスとキューから出るプロセスを簡単に示しています。
1.5.1 キューの作成
上の図では、タスク A がタスク B にメッセージを送信しようとしています。このメッセージは x 変数の値です。まずキューを作成し、キューの長さと各メッセージの長さを指定します。ここでは、長さ 4 のキューを作成します。x の値が渡されることになり、x は int 型の変数であるため、各メッセージの長さは int 型の長さ (STM32 では 4 バイト) になります。つまり、各メッセージは 4 バイトです。
1.5.2 最初のメッセージをキューに送信する
上図では、タスク A の変数 x の値は 10 であり、この値がメッセージ キューに送信されます。この時点で、キューの残りの長さは 3 です。前述したように、キューへのメッセージの送信はコピーによって行われるため、メッセージが送信されると、変数 x を再度使用して他の値を割り当てることができます。
1.5.3 2 番目のメッセージをキューに送信する
上の図では、タスク A が別のメッセージをキューに送信します。これは、x の新しい値 (ここでは 20) です。この時点で、キューの残りの長さは 2 です。
1.5.4 キューからメッセージを読み取る
上の図では、タスク B はキューからメッセージを読み取り、読み取ったメッセージの値を y に割り当てます。これにより、y は 10 と等しくなります。タスク B はキューからメッセージを読み取った後、メッセージをクリアするかどうかを選択できます。このメッセージをクリアすることを選択すると、他のタスクまたは割り込みはこのメッセージを取得できなくなり、キューの残りのサイズは 1 つ増えて 3 になります。これがクリアされていない場合、他のタスクまたは割り込みもこのメッセージを取得する可能性があり、キューの残りのサイズは 2 のままです。
2. キューの構造
Queue_t と呼ばれるキューの記述に使用される構造体があり、この構造体はファイル内にあり、この構造体はファイル queue.c 内にあります。
typedef struct QueueDefinition
{
//指向队列存储区开始地址
int8_t * pcHead;
//指向存储区中下一个空闲区域
int8_t * pcWriteTo;
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u;
//等待发送任务列表,那些因为队列满导致入队失败而进入阻塞态的任务就会挂到此列表上。
List_t xTasksWaitingToSend;
//等待接收任务列表,那些因为队列空导致出队失败而进入阻塞态的任务就会挂到此列表上。
List_t xTasksWaitingToReceive;
//队列中当前队列项数量,也就是消息数
volatile UBaseType_t uxMessagesWaiting;
//创建队列时指定的队列长度,也就是队列中最大允许的队列项(消息)数量
UBaseType_t uxLength;
//创建队列时指定的每个队列项(消息)最大长度,单位:字节
UBaseType_t uxItemSize;
//当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED
volatile int8_t cRxLock;
//当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED
volatile int8_t cTxLock;
//如果使用静态存储的话此字段设置为 pdTURE。
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated;
#endif
//队列集相关宏
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
//跟踪调试相关宏
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
3. キューの作成
3.1 関数の作成
この関数はキューの作成にも使用されますが、キューを作成する静的メソッドでは、ユーザーが必要なメモリを割り当てる必要があります。この関数は本質的にマクロ行の割り当てです。このマクロは最後に関数 xQueueGenericCreateStatic() を呼び出します。関数のプロトタイプは次のとおりです。次のように:
xQueueCreateStatic()
xQueueCreate()
これら 2 つの関数は本質的にマクロであり、実際にキューの作成を完了する関数は xQueueGenericCreate() と xQueueGenericCreateStatic() です。
3.2 関数 xQueueCreateStatic()
この関数は静的メソッドを使用してキューを作成し、キューに必要なメモリはユーザーによって割り当てられます。
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSiz,
uint8_t* pucQueueStorageBuffer,
StaticQueue_t* pxQueueBuffer
)
3.3 関数 xQueueCreate()
この関数は本質的にはマクロであり、キューを動的に作成するために使用されます。この関数は本質的にはマクロであり、キューを動的に作成するために使用されます。このマクロは最後に関数 xQueueGenericCreate() を呼び出します。関数のプロトタイプは次のとおりです。
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
UBaseType_t uxItemSiz,)
3.4 関数 xQueueGenericCreateStatic()
QueueHandle_t xQueueGenericCreateStatic(
const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t *pucQueueStorage,
StaticQueue_t *pxStaticQueue,
const uint8_t ucQueueType
)
3.5 関数 xQueueGenericCreate()
QueueHandle_t xQueueGenericCreate(
const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType
)
4. メッセージをキューに送信します
4.1 関数プロトタイプ
キューが作成された後、キューにメッセージを送信できます。FreeRTOS には、次の表に示すように、キューにメッセージを送信するための 8 つの API 関数が用意されています。
4.2 関数 xQueueSend()、xQueueSendToBack()、および xQueueSendToFront()
これら 3 つの関数は、メッセージをキューに送信するために使用されます。これら 3 つの関数は本質的にマクロです。関数 xQueueSend() と xQueueSendToBack() は同じで、すべて逆方向にキューに入り、新しいメッセージをキューの最後尾に挿入します。関数 xQueueSendToToFront() は、前方にエンキューします。つまり、新しいメッセージをキューの先頭に挿入します。しかし!これら 3 つの関数は、最終的に同じ関数 xQueueGenericSend() を呼び出します。これら 3 つの関数はタスク関数内でのみ使用でき、割り込みサービス関数では使用できません。割り込みサービス関数には専用の関数があり、末尾が「FromISR」で終わります。これら 3 つの関数のプロトタイプは次のとおりです。
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
const void* pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait);
パラメータ:
xQueue: データを送信するキューを示すキュー ハンドル。キューが正常に作成されると、
このキューのキュー ハンドルが返されます。
**pvItemToQueue:** は送信されるメッセージを指し、メッセージは送信時にキューにコピーされます。
xTicksToWait: ブロック時間。このパラメータは、キューがいっぱいの場合にタスクがブロック状態に入り、キューが空くまで待機する最大時間を示します。0 の場合は、キューがいっぱいになるとすぐに戻ります。portMAX_DELAY の場合は、
キュー内にアイドル状態のキュー項目ができるまで待機します (デッド待機)。ただし、マクロ INCLUDE_vTaskSuspend は 1 である必要があります。
戻り値:
pdPASS: キューへのメッセージの送信に成功しました!
errQUEUE_FULL: キューがいっぱいなのでメッセージを送信できません。
4.2 関数 xQueueOverwrite()
この関数はキューにデータを送信するためにも使用され、キューがいっぱいになると、古いデータが他のタスクや割り込みによって持ち去られたかどうかに関係なく、古いデータが上書きされます。この関数は、長さが 1 のキューにメッセージを送信するためによく使用されます。この関数もマクロであり、関数 xQueueGenericSend() が最後に呼び出されます。関数のプロトタイプは次のとおりです。
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
const void * pvItemToQueue);
パラメータ:
xQueue: データを送信するキューを示すキュー ハンドル。キューが正常に作成されると、このキューのキュー ハンドルが返されます。
**pvItemToQueue:** は、送信されるメッセージを指します。送信時にキューにコピーされます。
戻り値:
pdPASS: メッセージがキューに正常に送信された場合、この関数は pdPASS! のみを返します。この関数は実行中にキューがいっぱいかどうかを気にしないので、キューがいっぱいの場合は古いデータを上書きしますので、必ず成功します。
4.3 関数 xQueueGenericSend()
この関数が実際の作業です。上記で説明したすべてのタスク レベルのエンキュー関数は、最終的にこの関数によって呼び出されます。この関数は、後で焦点を当てるものでもあります。最初に関数のプロトタイプを見てみましょう:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
パラメータ:
xQueue: データを送信するキューを示すキュー ハンドル。キューが正常に作成されると、このキューのキュー ハンドルが返されます。
**pvItemToQueue:** は送信されるメッセージを指します。メッセージは送信プロセス中にキューにコピーされます。
xTicksToWait: ブロック時間。
xCopyPosition: キューに参加するには 3 つの方法があります:
queueSEND_TO_BACK: キューを後方にキュー
SEND_TO_FRONT: キューを前方にキュー
OVERWRITE: キューを上書きします。
上で説明したエンキュー API 関数は、このパラメーターを使用して、使用するエンキュー メソッドを決定します。
戻り値:
pdTRUE: キューへのメッセージの送信に成功しました。
errQUEUE_FULL: キューがいっぱいでメッセージの送信に失敗しました
4.4 関数 xQueueSendFromISR()、xQueueSendToBackFromISR()、xQueueSendToFrontFromISR()
これら 3 つの関数はキューにメッセージも送信し、これら 3 つの関数は割り込みサービス関数で使用されます。これら 3 つの関数の本質もマクロです。このうち関数 xQueueSendFromISR () と xQueueSendToBackFromISR () は同じで、すべてキューに逆方向に入力します。つまり、新しいメッセージをキューの最後尾に挿入します。関数 xQueueSendToFrontFromISR () は、前方にエンキューします。つまり、新しいメッセージをキューの先頭に挿入します。これら 3 つの関数は、同じ関数 xQueueGenericSendFromISR() も呼び出します。これら 3 つの関数のプロトタイプは次のとおりです。
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
パラメータ:
xQueue: データを送信するキューを示すキュー ハンドル。キューが正常に作成されると、このキューのキュー ハンドルが返されます。
**pvItemToQueue:** は、送信されるメッセージを指します。送信時にキューにコピーされます。
pxHigherPriorityTaskWoken: この関数を終了した後にタスクを切り替えるかどうかをマークします。この変数の値は、これら 3 つの関数によって設定されます。ユーザーは設定する必要はありません。ユーザーは、この値を保存するための変数を指定するだけで済みます。この値がpdTRUEの場合、割り込みサービス関数を終了する前にタスクスイッチを実行する必要があります。
戻り値:
pdTRUE: キューへのメッセージの送信に成功しました。
errQUEUE_FULL: キューがいっぱいなのでメッセージを送信できません。
4.5 関数 xQueueOverwriteFromISR()
この関数は xQueueOverwrite() の割り込みレベル バージョンです。キューがいっぱいになったときに古いデータを自動的に上書きするために割り込みサービス関数で使用されます。この関数はマクロでもあり、関数 xQueueGenericSendFromISR() が実際に呼び出されます。この関数のプロトタイプは次のとおりです。
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
この関数のパラメータと戻り値は上記 3 つの関数と同じです。
4.6 関数 xQueueGenericSendFromISR()
上で述べたように、4 つの割り込みレベルのキューイング関数は、最終的に関数 xQueueGenericSendFromISR() と呼ばれます。これが作業の真のマスターであり、以下で詳しく説明する関数でもあります。この関数のプロトタイプは次のようになります。
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue,
const void* pvItemToQueue,
BaseType_t* pxHigherPriorityTaskWoken,
BaseType_t xCopyPosition);
パラメータ:
xQueue: データを送信するキューを示すキュー ハンドル。キューが正常に作成されると、このキューのキュー ハンドルが返されます。
**pvItemToQueue:** は送信されるメッセージを指します。メッセージは送信プロセス中にキューにコピーされます。
pxHigherPriorityTaskWoken: この関数を終了した後にタスクを切り替えるかどうかをマークします。この変数の値は、これら 3 つの関数によって設定されます。ユーザーは設定する必要はありません。ユーザーは、この値を保存するための変数を指定するだけで済みます。この値がpdTRUEの場合、割り込みサービス関数を終了する前にタスクスイッチを実行する必要があります。
xCopyPosition: キューに参加するには 3 つの方法があります:
queueSEND_TO_BACK: キューを後方にキュー
SEND_TO_FRONT: キューを前方にキュー
OVERWRITE: キューを上書きします。
戻り値:
pdTRUE: キューへのメッセージの送信に成功しました。
errQUEUE_FULL: キューがいっぱいなのでメッセージを送信できません。
4.7 タスクレベルの一般的なエンキュー機能
逆方向にエンキューするか、前方にエンキューするか、エンキューをオーバーライドするかに関係なく、最後のエンキュー関数 xQueueGenericSend() が呼び出されます。この関数はファイル queue.c で定義されます。削減された関数コードは次のとおりです。
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 ) ||\ (1)
( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue,\ (2)
xCopyPosition );
/**************************************************************************/
/**************************省略掉与队列集相关代码**************************/
/**************************************************************************/
{
//检查是否有任务由于等待消息而进入阻塞态
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) ==\(3)
pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\ (4)
xTasksWaitingToReceive ) ) != pdFALSE )
{
//解除阻塞态的任务优先级最高,因此要进行一次任务切换
queueYIELD_IF_USING_PREEMPTION(); (5)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xYieldRequired != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS; (6)
}
else
{
if( xTicksToWait == ( TickType_t ) 0 ) (7)
{
//队列是满的,并且没有设置阻塞时间的话就直接返回
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL; (8)
}
else if( xEntryTimeSet == pdFALSE ) (9)
{
//队列是满的并且指定了任务阻塞时间的话就初始化时间结构体
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
//时间结构体已经初始化过了,
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL(); //退出临界区
vTaskSuspendAll(); (10)
prvLockQueue( pxQueue ); (11)
//更新时间壮态,检查是否有超时产生
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (12)
{
if( prvIsQueueFull( pxQueue ) != pdFALSE ) (13)
{
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), \ (14)
xTicksToWait );
prvUnlockQueue( pxQueue ); (15)
if( xTaskResumeAll() == pdFALSE ) (16)
{
portYIELD_WITHIN_API();
}
}
else
{
//重试一次
prvUnlockQueue( pxQueue ); (17)
( void ) xTaskResumeAll();
}
}
else
{
//超时产生
prvUnlockQueue( pxQueue ); (18)
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL; (19)
}
}
}
(1) キューにデータを送信するには、キューが満杯かどうかを確認し、満杯の場合は送信しないでください。キューがいっぱいでない場合、またはキューに上書きされている場合は、メッセージをキューに入れることができます。
(2) 関数 prvCopyDataToQueue() を呼び出して、メッセージをキューにコピーします。前述したように、エンキューには、後方エンキュー、前方エンキュー、および上書きエンキューがあり、それぞれの具体的な実装は関数 prvCopyDataToQueue() で完了します。queueSEND_TO_BACK が選択されている場合、メッセージはキュー構造メンバー pcWriteTo が指すキュー項目にコピーされます。コピーが成功すると、pcWriteTo は次のキュー項目を指すように uxItemSize バイトを増やします。queueSEND_TO_FRONT または queueOVERWRITE を選択した場合、メッセージは u.pcReadFrom が指すキュー アイテムにコピーされるため、u.pcReadFrom の位置も調整する必要があります。メッセージがキューに書き込まれると、キュー内の現在のメッセージ数をカウントするメンバー uxMessagesWaiting が 1 つ増加しますが、キュー OVERWRITE を選択した場合、uxMessagesWaiting は 1 つ減り、1 から 1 が減ります。プラス 1 は、キュー内の変更されていない現在のメッセージ数に相当します。
(3) リクエスト キュー メッセージによってブロックされたタスクがあるかどうかを確認します。ブロックされたタスクは
キューの xTasksWaitingToReceive リストにハングされます。
(4) (2)でキューにメッセージが送信されているため、リクエストメッセージによりタスクがブロックされているため、xTaskRemoveFromEventList()関数を呼び出してブロックされているタスクをリストxTasksWaitingToReceiveから削除し、このタスクを削除する準備完了リスト。スケジューラがロックされている場合、これらのタスクはリスト xPendingReadyList にハングされます。ブロック解除されたタスクの優先度が現在実行中のタスクの優先度より高い場合は、タスクの切り替えが必要です。xTaskRemoveFromEventList()関数の戻り値がpdTRUEの場合、タスクの切り替えが必要です。
(5) タスクの切り替えを行います。
(6) pdPASS を返却し、チームへの参加が成功しました。
(7)、(2) ~ (6) はすべて非常に理想的な効果です。つまり、メッセージ キューがいっぱいではなく、キューに入るのに障害がありません。しかし、キューがいっぱいの場合はどうでしょうか? まず設定されたブロッキングタイムが0かどうかを判定し、0であればブロッキングタイムなしを意味します。
(8) (7) から、ブロック時間が 0 であることがわかり、直接 errQUEUE_FULL を返し、キューがいっぱいであることをマークします。
(9) ブロッキングタイムが 0 でなく、タイム構造体が初期化されていない場合は、タイムアウト構造体変数を一度初期化し、関数 vTaskSetTimeOutState()を呼び出してタイムアウト構造体変数 xTimeOut の初期化を完了します。実際には、現在のシステム クロック ビート カウンターの値 xTickCount とオーバーフローの数 xNumOfOverflows を記録します。
(10) では、タスク スケジューラがロックされており、ここでのコードの実行は、現在の状況がキューがいっぱいで、ゼロ以外のブロック時間が設定されていることを示しています。
次に、キューの xTasksWaitingToSend リストにタスクを追加するなど、タスクに対応する措置を講じます。
(11) 関数 prvLockQueue() を呼び出してキューをロックすると、実際には
キュー内のメンバー変数 cRxLock および cTxLock が queueLOCKED_UNMODIFIED に設定されます。
(12) 関数 xTaskCheckForTimeOut()を呼び出してタイムアウト構造体変数 xTimeOut を更新し、ブロック時間が経過したかどうかを確認します。
(13)、ブロック時間にまだ達していない場合は、キューがまだいっぱいかどうかを確認します。
(14) (12) と (13) の後、ブロック時間に達しておらず、キューがまだいっぱいであることがわかり、関数 vTaskPlaceOnEventList() を呼び出してタスクを xTasksWaitingToSend リストと遅延リストに追加します。タスクが実行可能リストから削除されます。知らせ!ブロック時間が portMAX_DELAY でマクロ INCLUDE_vTaskSuspend が 1 の場合、関数 vTaskPlaceOnEventList() はタスクをリスト xSuspendedTaskList に追加します。
(15)、操作が完了したら、関数 prvUnlockQueue() を呼び出してキューのロックを解除します。
(16) xTaskResumeAll()関数を呼び出してタスクスケジューラを復元します。
(17)、ブロック時間はまだ到着していませんが、キューにはアイドル状態のキュー項目があるため、もう一度試してください。
(18)は、(12)に比べてブロッキング時間が長くなりました。そうすれば、それらのリストにタスクを追加する必要がなくなるため、キューのロックを解除してタスク スケジューラを復元します。
(19)、errQUEUE_FULL を返し、キューがいっぱいであることを示します。
4.8 割り込みレベルの一般エンキュー機能
タスク レベルのエンキュー関数について説明した後、割り込みレベルのエンキュー関数 xQueueGenericSendFromISR() を見てみましょう。他の割り込みレベルのエンキュー関数は、この関数によって実装されます。割り込みレベルのエンキュー関数はタスクレベルのエンキュー関数と似ており、関数コードは次のとおりです。
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue,
const void * const pvItemToQueue,
BaseType_t * const pxHigherPriorityTaskWoken,
const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) ||\ (1)
( xCopyPosition == queueOVERWRITE ) )
{
const int8_t cTxLock = pxQueue->cTxLock; (2)
traceQUEUE_SEND_FROM_ISR( pxQueue );
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (3)
{
//队列上锁的时候就不能操作事件列表,队列解锁的时候会补上这些操作的。
if( cTxLock == queueUNLOCKED ) (4)
{
/**************************************************************************/
/**************************省略掉与队列集相关代码**************************/
/**************************************************************************/
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (5)
pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\ (6)
xTasksWaitingToReceive ) ) != pdFALSE )
{
//刚刚从事件列表中移除的任务对应的任务优先级更高,所以
标记要进行任务切换
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE; (7)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
//cTxLock 加一,这样就知道在队列上锁期间向队列中发送了数据
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 ); (8)
}
xReturn = pdPASS; (9)
}
else
{
traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
xReturn = errQUEUE_FULL; (10)
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
(1) キューがいっぱいではないか、エンキューのオーバーライド方法が採用されており、これが最も理想的な状態です。
(2) キューのメンバ変数 xTxLock を読み取り、キューがロックされているかどうかを確認します。
(3) データをキューにコピーします。
(4) キューがロックされている 例えばタスクレベルのエンキュー機能はキュー内のリストを操作する際にキューをロックします。
(5) キュー リスト xTasksWaitingToReceive が空かどうかを確認します。空でない場合は、メッセージの要求時にタスクがブロックされていることを意味します。
(6). リスト xTasksWaitingToReceive から対応するタスクを削除します。このプロセスはタスクレベルのエンキュー関数と同じです。
(7) リスト xTasksWaitingToReceive から削除したばかりのタスクの優先度が現在のタスクの優先度より高い場合、pxHigherPriorityTaskWoken を pdTRUE としてマークし、タスクの切り替えが必要であることを示します。タスクを切り替える場合は、本関数を終了してから、割り込みサービス関数を終了する前にタスクを切り替える必要があります。
(8) キューがロックされている場合は、キューのメンバ変数 cTxLock に 1 を追加します。これは、エンキュー操作が実行されたことを示します。キューのロックが解除されたときに、それに応じて処理されます (prvUnlockQueue())。
(9) キューエントリが完了したことを示す pdPASS を返します。
(10) キューがいっぱいの場合は、キューがいっぱいであることを示す errQUEUE_FULL を直接返します。
5. キューのロックとロック解除
上記タスクレベルゼネラルエンキュー関数および割り込みレベルゼネラルエンキュー関数の説明の際、キューのロックとアンロックについて言及しましたが、キューのロックとアンロックはprvLockQueue()とprvUnlockQueue()の2つのAPI関数です。
まずキュー ロック関数 prvLockQueue() を見てみましょう。この関数は本質的にマクロであり、次のように定義されます。
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{
\
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{
\
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;\
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{
\
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;\
} \
} \
taskEXIT_CRITICAL()
prvLockQueue() 関数は非常に簡単で、キュー内のメンバー変数 cRxLock および cTxLock を
queueLOCKED_UNMODIFIED に設定するだけです。
キューのロック解除関数 prvUnlockQueue() を見てみましょう。
static void prvUnlockQueue( Queue_t * const pxQueue )
{
//上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间,入队或出队的数量,当队列
//上锁以后队列项是可以加入或者移除队列的,但是相应的列表不会更新。
taskENTER_CRITICAL();
{
//处理 cTxLock。
int8_t cTxLock = pxQueue->cTxLock;
while( cTxLock > queueLOCKED_UNMODIFIED ) (1)
{
/**************************************************************************/
/**************************省略掉与队列集相关代码**************************/
/**************************************************************************/
{
//将任务从事件列表中移除
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == \ (2)
pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\ (3)
xTasksWaitingToReceive ) ) != pdFALSE )
{
//从列表中移除的任务优先级比当前任务的优先级高,因此要
//进行任务切换。
vTaskMissedYield(); (4)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
--cTxLock; (5)
}
pxQueue->cTxLock = queueUNLOCKED; (6)
}
taskEXIT_CRITICAL();
//处理 cRxLock。
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
while( cRxLock > queueLOCKED_UNMODIFIED ) (7)
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) !=\
pdFALSE )
{
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cRxLock;
}
else
{
break;
}
}
pxQueue->cRxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
}
(1) 割り込みによってメッセージがキューに送信されたかどうかを確認します。前のセクションで割り込みレベルの一般的なエンキュー機能を説明したときに説明したように、キューがロックされている場合、メッセージの送信後にエンキュー カウンタ cTxLock が 1 つ増加します。はキューに正常に送信されました。
(2) リスト xTasksWaitingToReceive が空かどうかを確認し、空でない場合は、対応するタスクをリストから削除します。
(3) xTasksWaitingToReceive リストからタスクを削除します。
(4) xTasksWaitingToReceive リストから削除したばかりのタスクの優先度が現在のタスクの優先度よりも高い場合、タスクの切り替えが必要であることをマークする必要があります。ここでは、このタスクを完了するために関数 vTaskMissedYield() が呼び出されます。関数 vTaskMissedYield() は、グローバル変数 xYieldPending を pdTRUE に設定するだけです。では、実際のタスクの切り替えはどこで行われるのでしょうか? クロックティック処理関数 xTaskIncrementTick() において、この関数は xYieldPending の値を判断してタスクを切り替えるかどうかを決定します。
(5) すべてが処理されるまで、cTxLock が 1 つ処理されるたびに 1 ずつ減少します。
(6) 処理後、cTxLock を queueUNLOCKED としてマークします。つまり、cTxLock はロックされていません。
(7) cTxLock を処理した後、次に xRxLock が処理されます。処理プロセスは xTxLock と非常に似ています。
6. キューからメッセージを読み取る
キューがあればキューがあり、キューはキューからキューアイテム(メッセージ)を取得するもので、FreeRTOSのキュー機能は以下の表の通りです。
6.1 関数 xQueueReceive()
この関数は、タスクのキューから (リクエスト) メッセージを読み取るために使用されます。読み取りが成功すると、キュー内のデータは削除されます。この関数の本質はマクロであり、実際に実行される関数は xQueueGenericReceive( )。この関数はメッセージを読み取るときにコピー メソッドを使用するため、ユーザーは読み取ったデータを保存するための配列またはバッファを提供する必要があります。読み取ったデータの長さは、キューの作成時に設定された各キュー項目になります。関数プロトタイプの長さは次のとおりです。以下に続きます:
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void * pvBuffer,
TickType_t xTicksToWait);
パラメータ:
xQueue: どのキュー データを読み取るかを示すキュー ハンドル。キューが正常に作成された後、このキューのキュー ハンドルを返します。
pvBuffer: データを保存するためのバッファ。読み取られたデータは、キューの読み取りプロセス中にこのバッファにコピーされます。
xTicksToWait: ブロッキング時間。このパラメータは、キューが空のときにタスクがブロッキング状態になり、キューにデータが入るまで待機する最大時間を示します。0 の場合、キューが空になるとすぐに戻ります。portMAX_DELAY の場合、
キューにデータが入るまで待機します (デッド待機) が、マクロ
INCLUDE_vTaskSuspend は 1 でなければなりません。
戻り値:
pdTRUE:キューからのデータの読み込みに成功しました。
pdFALSE:キューからのデータの読み込みに失敗しました。
6.2 関数 xQueuePeek()
この関数はキューから (リクエスト) メッセージを読み取るために使用され、タスク内でのみ使用できます。この関数はメッセージが正常に読み取られた後はメッセージを削除しません
。この関数はマクロであり、実際に実行される関数は xQueueGenericReceive() です。この関数はメッセージを読み取るときにコピー メソッドを使用するため、ユーザーは読み取ったデータを保存するための配列またはバッファを提供する必要があります。読み取ったデータの長さは、キューの作成時に設定された各キュー項目になります。関数プロトタイプの長さは次のとおりです。以下に続きます:
BaseType_t xQueuePeek(QueueHandle_t xQueue,
void * pvBuffer,
TickType_t xTicksToWait);
パラメータ:
xQueue: どのキュー データを読み取るかを示すキュー ハンドル。キューが正常に作成された後、このキューのキュー ハンドルを返します。
pvBuffer: データを保存するためのバッファ。読み取られたデータは、キューの読み取りプロセス中にこのバッファにコピーされます。
xTicksToWait: ブロッキング時間。このパラメータは、キューが空のときにタスクがブロッキング状態になり、キューにデータが入るまで待機する最大時間を示します。0 の場合、キューが空になるとすぐに戻ります。portMAX_DELAY の場合、
キューにデータが入るまで待機します (デッド待機) が、マクロ INCLUDE_vTaskSuspend は 1 でなければなりません。
戻り値:
pdTRUE:キューからのデータの読み込みに成功しました。
pdFALSE:キューからのデータの読み込みに失敗しました。
6.3 関数 xQueueGenericReceive()
xQueueReceive() 関数でも xQueuePeek() 関数でも、最後に呼び出されるのは xQueueGenericReceive() 関数です。この関数が本物であり、関数のプロトタイプは次のとおりです。
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
void* pvBuffer,
TickType_t xTicksToWait
BaseType_t xJustPeek)
パラメータ:
xQueue: どのキュー データを読み取るかを示すキュー ハンドル。キューが正常に作成された後、このキューのキュー ハンドルを返します。
pvBuffer: データを保存するためのバッファ。読み取られたデータは、キューの読み取りプロセス中にこのバッファにコピーされます。
xTicksToWait: ブロッキング時間。このパラメータは、キューが空のときにタスクがブロッキング状態になり、キューにデータが入るまで待機する最大時間を示します。0 の場合、キューが空になるとすぐに戻ります。portMAX_DELAY の場合、
キューにデータが入るまで待機します (デッド待機) が、マクロ
INCLUDE_vTaskSuspend は 1 でなければなりません。
xJustPeek: 読み取り成功後にキューアイテムを削除するかどうかをマークし、pdTRUEの場合は削除する必要がありません、つまり、後でxQueueReceive()関数を呼び出して取得したキューアイテムと同じです。pdFALSEの場合
、キューエントリは削除されます。
戻り値:
pdTRUE:キューからのデータの読み込みに成功しました。
pdFALSE:キューからのデータの読み込みに失敗しました。
6.4 関数 xQueueReceiveFromISR()
この関数は、割り込みサービス関数でキューからメッセージを読み取る (要求する) ために使用される xQueueReceive() の割り込みバージョンであり、読み取りが成功するとキュー内のデータは削除されます。この関数はメッセージを読み取るときにコピーメソッドを使用するため、ユーザーは読み取ったデータを保存するための配列またはバッファを提供する必要があります 読み取ったデータの長さは、キュー作成時に設定された各キュー項目です 関数プロトタイプの長さは次のとおりです:
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
void* pvBuffer,
BaseType_t * pxTaskWoken);
パラメータ:
xQueue: どのキュー データを読み取るかを示すキュー ハンドル。キューが正常に作成された後、このキューのキュー ハンドルを返します。
pvBuffer: データを保存するためのバッファ。読み取られたデータは、キューの読み取りプロセス中にこのバッファにコピーされます。
pxTaskWoken: この関数を終了した後にタスクを切り替えるかどうかをマークします。この変数の値は関数によって設定され、ユーザーが設定する必要はありません。ユーザーは、この値を保存するための変数を指定するだけで済みます。この値
がpdTRUEの場合、割り込みサービス関数を終了する前にタスクスイッチを実行する必要があります。
戻り値:
pdTRUE:キューからのデータの読み込みに成功しました。
pdFALSE:キューからのデータの読み込みに失敗しました。
6.5 関数 xQueuePeekFromISR()
この関数は、xQueuePeek() の中断バージョンです。この関数は、メッセージが正常に読み取られた後でもメッセージを削除しません。この関数のプロトタイプは次のとおりです。
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
void * pvBuffer)
パラメータ:
xQueue: どのキュー データを読み取るかを示すキュー ハンドル。キューが正常に作成された後、このキューのキュー ハンドルを返します。
pvBuffer: データを保存するためのバッファ。読み取られたデータは、キューの読み取りプロセス中にこのバッファにコピーされます。
戻り値:
pdTRUE:キューからのデータの読み込みに成功しました。
pdFALSE:キューからのデータの読み込みに失敗しました。
7. キュープログラム例
7.1 サンプルデザインの要件
この例では、start_task、task1_task、Keyprocess_task の 3 つのタスクを設計します。 これら 3 つのタスクの機能は次のとおりです。 これら 3 つのタスクの機能は次のとおりです。 start_task: 他のタスクの作成に使用されます: 他の 2 つのタスクの作成に使用されます
。
task1_task : キーの値を読み取り、キューに送信します。 : キーの値を読み取り、キューに送信します Key_Queue とキューの残量を確認し、キューの残量を確認しますおよびその他の情報。
Keyprocess_task : キー処理タスク。キュー Key_Queue 内のメッセージを読み取り、さまざまなメッセージ値に従って対応する処理を実行します。
インスタンスには 3 つのキー KEY_UP、KEY2、KEY0 が必要で、異なるキーは異なるキー値に対応し、タスク task1_task はこれらの値をキュー Key_Queue に送信します。
この例では、2 つのキュー Key_Queue と Message_Queue が作成されます。キュー Key_Queue はキー値を渡すために使用され、キュー Message_Queue はシリアル ポートから送信されたメッセージを渡すために使用されます。
この例では、シリアル ポート 1 受信割り込みとタイマー 2 割り込みの 2 つの割り込みも必要で、その機能は次のとおりです: シリアル ポート 1 受信割り込み: シリアル ポートから送信されたデータを受信し、受信したデータを送信します。データをキュー Message_Queue に送信します。タイマー 2 割り込み: タイミング周期は 500ms に設定され、キュー Message_Queue 内のメッセージがタイミング割り込みで読み取られ、LCD に表示されます。
7.2 コード例
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lcd.h"
#include "key.h"
#include "beep.h"
#include "string.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 256
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define KEYPROCESS_TASK_PRIO 3
//任务堆栈大小
#define KEYPROCESS_STK_SIZE 256
//任务句柄
TaskHandle_t Keyprocess_Handler;
//任务函数
void Keyprocess_task(void *pvParameters);
//按键消息队列的数量
#define KEYMSG_Q_NUM 1 //按键消息队列的数量
#define MESSAGE_Q_NUM 4 //发送数据的消息队列的数量
QueueHandle_t Key_Queue; //按键值消息队列句柄
QueueHandle_t Message_Queue; //信息队列句柄
//LCD刷屏时使用的颜色
int lcd_discolor[14]={
WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };
//用于在LCD上显示接收到的队列的消息
//str: 要显示的字符串(接收到的消息)
void disp_str(u8* str)
{
LCD_Fill(5,230,110,245,WHITE); //先清除显示区域
LCD_ShowString(5,230,100,16,16,str);
}
//加载主界面
void freertos_load_main_ui(void)
{
POINT_COLOR = RED;
LCD_ShowString(10,10,200,16,16,"ATK STM32F103/407");
LCD_ShowString(10,30,200,16,16,"FreeRTOS Examp 13-1");
LCD_ShowString(10,50,200,16,16,"Message Queue");
LCD_ShowString(10,70,220,16,16,"KEY_UP:LED1 KEY0:Refresh LCD");
LCD_ShowString(10,90,200,16,16,"KEY1:SendMsg KEY2:BEEP");
POINT_COLOR = BLACK;
LCD_DrawLine(0,107,239,107); //画线
LCD_DrawLine(119,107,119,319); //画线
LCD_DrawRectangle(125,110,234,314); //画矩形
POINT_COLOR = RED;
LCD_ShowString(0,130,120,16,16,"DATA_Msg Size:");
LCD_ShowString(0,170,120,16,16,"DATA_Msg rema:");
LCD_ShowString(0,210,100,16,16,"DATA_Msg:");
POINT_COLOR = BLUE;
}
//查询Message_Queue队列中的总队列数量和剩余队列数量
void check_msg_queue(void)
{
u8 *p;
u8 msgq_remain_size; //消息队列剩余大小
u8 msgq_total_size; //消息队列总大小
taskENTER_CRITICAL(); //进入临界区
msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到队列剩余大小
msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到队列总大小,总大小=使用+剩余的。
p=mymalloc(SRAMIN,20); //申请内存
sprintf((char*)p,"Total Size:%d",msgq_total_size); //显示DATA_Msg消息队列总的大小
LCD_ShowString(10,150,100,16,16,p);
sprintf((char*)p,"Remain Size:%d",msgq_remain_size); //显示DATA_Msg剩余大小
LCD_ShowString(10,190,100,16,16,p);
myfree(SRAMIN,p); //释放内存
taskEXIT_CRITICAL(); //退出临界区
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化LED端口
KEY_Init(); //初始化按键
BEEP_Init(); //初始化蜂鸣器
LCD_Init(); //初始化LCD
TIM9_Int_Init(5000,16800-1); //初始化定时器9,周期500ms
my_mem_init(SRAMIN); //初始化内部内存池
freertos_load_main_ui(); //加载主UI
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建消息队列
Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8)); //创建消息Key_Queue
Message_Queue=xQueueCreate(MESSAGE_Q_NUM,USART_REC_LEN); //创建消息Message_Queue,队列项长度是串口接收缓冲区长度
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )Keyprocess_task,
(const char* )"keyprocess_task",
(uint16_t )KEYPROCESS_STK_SIZE,
(void* )NULL,
(UBaseType_t )KEYPROCESS_TASK_PRIO,
(TaskHandle_t* )&Keyprocess_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
u8 key,i=0;
BaseType_t err;
while(1)
{
key=KEY_Scan(0); //扫描按键
if((Key_Queue!=NULL)&&(key)) //消息队列Key_Queue创建成功,并且按键被按下
{
err=xQueueSend(Key_Queue,&key,10);
if(err==errQUEUE_FULL) //发送按键值
{
printf("队列Key_Queue已满,数据发送失败!\r\n");
}
}
i++;
if(i%10==0) check_msg_queue();//检Message_Queue队列的容量
if(i==50)
{
i=0;
LED0=!LED0;
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
//Keyprocess_task函数
void Keyprocess_task(void *pvParameters)
{
u8 num,key;
while(1)
{
if(Key_Queue!=NULL)
{
if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue
{
switch(key)
{
case WKUP_PRES: //KEY_UP控制LED1
LED1=!LED1;
break;
case KEY2_PRES: //KEY2控制蜂鸣器
BEEP=!BEEP;
break;
case KEY0_PRES: //KEY0刷新LCD背景
num++;
LCD_Fill(126,111,233,313,lcd_discolor[num%14]);
break;
}
}
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
参考ビデオ: 時間通りの原子