「あなたはたくさんいますが、私は一人です!」-セールスウーマンの古典的なフレーズは、「そこにあるのか...」という質問で顧客に恐怖を感じました。 そのため、マイクロコントローラーでは、複数のスレッドが単に物理的に全員に一度にサービスを提供できないような遅いものから注意を必要とする場合、まったく同様の状況が発生します。
経験の浅いプログラマーの大部分が「嘘をついている」最も鮮明で問題の多い例を取り上げましょう。 強力でかなり高速なマイクロコントローラーがあります。 com-portアダプタは片側で接続され、ユーザーはコマンドを入力して結果を受け取り、他方で、これらのコマンドに従って、ある角度で回転するステッピングモーターを使用します。 そしてもちろん、ユーザーにとって何かを意味するクールなボタン。 どこで問題を見つけることができますか?
ユーザー側から行きましょう。 Com-port、またはUSART(ユニバーサル同期/非同期受信機/送信機)は非常に優しく気まぐれなものです。 主な気まぐれは、無視できないということです。 単純な理由の1つ-結論を保存するために、99%のケースで受信および送信信号のみが出力され、受信および送信許可信号(rtr / rts、dtr、ctsなど)が船外に残されます。 マイクロプロセッサが少しtheすると、シンボルは失われます。 自分で判断してください:マジックナンバー9600 / 8N1は、1秒間に9600ボーがラインに沿って飛んでおり、1文字が10(1スタート、8データ、1ストップ)パルスでエンコードされていることを示しています。 その結果、最大転送速度は9600/10 = 960バイト/秒です。 または、1バイトあたり1ミリ秒を少し超えます。 TKによると、伝送速度は115200ですか? しかし、マイクロコントローラはまだこのデータを処理する必要があります。 すべてが悪いように見えますが、実際には多くのデバイスが機能し、問題を引き起こしません。 どのソリューションが適用されますか?
最も一般的な(そして正しい)解決策は、キャラクターを送受信するすべての作業をマイクロコントローラーの肩に移すことです。 これで、誰もがハードウェアusartポートの作成方法を学習したので、ほとんどの場合の典型的なソリューションは次のようになります。
void URARTInterrupt() { a=GetCharFromUSART(); if(a!=0x13) buffer[count++]=a; else startProcessing(); }
問題はどこにありますか? まず、問題はstartProcessing呼び出しにあります。 次のキャラクターが失われるので、この機能は仕事で少なくとも少し遅れる価値があります。 STM32L1(最小頻度で1msで84個のコマンドのみを処理することを管理します)を使用しますが、多少重みのあるロジックを使用すると、結果の構成は文字を失います。
スタジオで頭をなでた後、プログラマーはコードを書き換えて、startProcessingを呼び出す代わりに、受信したデータの処理を開始するセマフォを生成します。 そして、私は次の問題を完全に成長させました:startProcessingがバッファから何かを粉砕している間、新しい文字ハンドラはまだ処理されていないバッファを体系的に上書きし始めます。 別の破れたセーターとバッファーに関するスモーキーな小さな本の後、プログラマーはポインターで循環バッファーを実装します。これにより、問題はさらに深く掘り下げられます。
通常、この場所のどこかで、「まあ、そのようなアルゴリズムは多くのプロジェクトで機能し、何の問題もなく動作する」という精神で人々とandするような叫び声に驚いています。 はい、そうです。 しかし、どのプロジェクトで? トランシーバーのように、コントローラーとのすべての通信を半二重モードに転送できる場合のみ。 以下は、ユーザー(P)とコントローラー(K)間のダイアログの例です。
P:ATZ(コントローラー、あなたは生きていますか?)
K:OK(ええ、私はここにいます)
P:M340.1(何かをする)
(一時停止がある場合があり、非常に長いことがあります)
K:OK(メイドタイプ)
問題はどこにありますか? まず、複数のチームを連続して送信することはできません。 そのため、ロジックを変更するか、複数のパラメーターを使用して複雑なコマンドを作成する必要があります。 第二に、要求と応答の間に他の要求と応答が存在することはできません。 ユーザーが運転中にボタンをクリックするとどうなりますか? どうする 唯一の方法があります-ポートのネイティブの性質、つまり非同期性を使用することです。 その結果、ユーザーとコントローラー間のダイアログは次のようになります
P:ATZ(生きている?)
K:OK(はい)
R:M340.1
K:K1 = 1(ボタンが押された)
P:E333.25.25
(沈黙)
K:E = 1(完了したタスクE)
K:M = 1(完了したタスクM)
もちろん、このようなストリームを処理するロジックは少し複雑ですが、そのような非同期性のおかげで、すぐに「古典的な学校」よりも多くの利点が得られます。
まず、ユーザーインターフェイスの応答性が大幅に向上します。 モーターがどこかで動作している、または何かが考慮されていますが、これは「フリーズ」または「スローダウン」する理由ではありません。 そして、顧客がブレーキのサイズがコントローラーのパワーにほとんど依存していないことを理解すると、合理的な質問があります...
第二に、この振る舞いは、実際の生活における小さなボスの振る舞いに近いです。 そして、そのような行動をコピーすることは非常に簡単です-誰もが見、誰もが参加しました。
最後に、制御フローを「チーム」と「ステータス」に分割します。 その結果、タスクのボリュームまたは回転角度のインジケーターをリアルタイムで実装することは難しくありません。
そして、これらすべてのアイデアを使って、反対側、つまりパフォーマーの側を見てみましょう。 コマンドを順番に処理する時間がないだけで十分に遅いです。 また、モーターの開始/停止時間は非常に長いため、「2つのコマンドを受信して一方向に回転し、次に一度に回転する」という精神で基本的な最適化を行うとよいでしょう。
通常のプログラマは何をしますか? 彼は以前の記事とたくさんの本を読んだので、必要に応じてセマフォを配置して論理を描き、モーターへの同時アクセスをブロックするためにミューテックスを使用します。 すべては問題ありませんが、コードは大きくて読みにくいです。 どうする studiovsemoe.comでは、シャリコフのレシピを使用しています。 並んで!」 繰り返しますが、キューの概念は母乳にほぼ吸収されるため、理解に問題はありません。
この例では、単純に3つのキューを作成できます。 1つ目は、ユーザーから受け取ったコマンドです。 すべてがそこに押し込まれ(まあ、または最小限のチェックの後)、すべての入力ポートから受け入れられます。 2番目は、ユーザーに発行する必要があるデータです。 ボタンのステータス、計算結果など。 そして最後に、第3段階は、モーター/読書室/他の誰かのタスクに役立ちます。 そして、これらのキューの間で、ユーザーからのデータをリーダーのタスクに変換するためのストリーム。 FreeRTOSにはキューに「覗く」ための公式機能があるため、次のアクションが現在のアクションを継続/繰り返す場合の最適化を簡単に行うことができます。
そのため、キューは3つしかなく、膨大な数の問題が解決されました。 第一に、受信した文字のバッファを失ったり書き換えたりする潜在的な問題さえありません。 確かに、結果はわずかに高いメモリ消費量になりますが、これは許容できる価格です。 第二に、結論に問題はありません。 すべてのプロセスは1つのキューに書き込み、出力方法、形式などを書き込むだけです。これはユーザーの関心事ではありません。 フレーム内に出力を作成する必要があります-1つの関数を書き換えますが、すべての関数が何かを出力できるわけではありません。 繰り返しますが、同時/オーバーラップ出力には問題はありません(1つのスレッドが12345と他のqwertyを出力しますが、ユーザーは1qw234er5tyのようなものを取得します)。 そして最後に、このアプローチのおかげで、タスクをより小さなタスクに分割し、マイクロプロセッサのスレッド/コアに沿って分散させるのは非常に簡単です。 そしてこれはすべて、開発の迅速化とサポートコストの削減を意味します。 誰もが幸せで、誰もが幸せです。
LEDの点滅のプラクティスから逸脱しないように、次のデモを行います。1秒に1回要素を取り出すキューを作成します(計算のシミュレーション)。 ボタンに触れると、要素(制御コマンド)が1秒間に2回キューに配置されます。 さて、LEDはキューの負荷を示します。 繰り返しますが、読みやすさを改善するコードはエラー処理なしで記述されているので注意してください。
コードのどこかでキューを定義します
xQueueHandle q;
「キューにさらにH要素がありますか?」という原則に従って、LEDの点火コードを変更します。 kindle、そうでない場合、いいえ”
if(uxQueueMessagesWaiting(q)>1) HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_SET); else AL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_RESET); osDelay(100);
スケジューラを開始する前に、8バイトサイズの要素がキュー内に「立つ」ことができるようにキューを初期化します。
q = xQueueCreate( 8, sizeof( unsigned char ) );
さて、キャラクターをキューに入れるためのボタンコード
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_SET) { unsigned char toSend; xQueueSend( q, ( void * ) &toSend, portMAX_DELAY ); } osDelay(500);
何が欠けていますか? Vorker、ジョブキューから削除します。 書いています。
static void WorkThread(void const * argument) { for(;;) { unsigned char rec; xQueueReceive( q, &( rec ), portMAX_DELAY ); osDelay(1000); } }
ご覧のとおり、すべてのパラメーターはセマフォで使用されているパラメーターと似ているため、繰り返しません。 そして、いつものように、結果は簡単に表示できます。
すべてのGibletsを含むコードは、 kaloshin.ru / stm32 / freertos / stage3.rarで入手できます。
次の部分