STM32およびTivaCマイクロコントローラーでのUSBスタックの最終化

最新のマイクロコントローラーにUSBポートが存在することにより、さまざまなコンピューター制御デバイスを独立して製造する大きな機会が開かれます。 ただし、実際には、USBを操作するためにメーカーが提供するライブラリを改善する必要があることがわかりました。 2つの人気のあるMKファミリーのこのような改良の経験に興味がある場合は、catへようこそ。





問題の声明



そのため、USBポートを介して任意の長さのコンピューターメッセージと通信するデバイスを作成します。 これを行う最も簡単な方法は、「仮想シリアルポート」とも呼ばれるUSB Class of Character Devices(CDC)を使用することです。 次に、デバイスを接続するホストシステムで、シリアルポートが自動的に作成されます。シリアルポートを使用して、デバイスとデータを交換し、通常のファイルと同様に操作できます。 ただし、実際には、製造元のUSBスタックでこれに必要な機能の一部がまったく実装されていないか、エラーが発生して実装されていることがわかります。 まず、STM32マイクロコントローラー(最初のケース)を見て、最後に別の一般的なファミリであるTexas Instruments Tiva C(2番目のケース)を見ていきます。 どちらのファミリもARM Cortex M4アーキテクチャを備えています。



STM32-コードを追加するだけ



STMマイクロコントローラーは通常、非常に手頃な価格で豊富な機能を備えています。 製造元は、あらゆる場面で幅広いライブラリを提供しています。 その中には、USBサポート用のライブラリと、チップ上で利用可能な他の周辺機器を操作するためのライブラリがあります。 最近、これらのライブラリはすべてSTM32Cubeと呼ばれる1つのメガパッケージにマージされました。 しかし、同時に、彼らは互換性を特に気にせず、構造名自体が同じであるという事実にもかかわらず、I / Oポートの構成を記述する構造内のフィールドの名前を含む、変更可能なすべてを変更しました。 興味深いことに、例とライブラリの3番目のバージョンもあり、それらはstm32f4-discovery.comにあります。 ただし、このオプションの作成者は、イニシャルを永続化するためにSTMから借用したファイルの名前を変更することを非常に好みます。これにより、他のすべてのコードとの互換性も追加されません。 上記のすべてを考慮して、STMが提供するライブラリの最新のプレキュービックバージョンを基礎とすることにしました。 現在、それらはコンパイラパッケージに含まれています(私はIARを使用しています)。 後で長い間探しないために、ライブラリはプロジェクトに含まれています。これは、下のリンクを使用してgitaから取得できます。 実験には、STM32F4DISCOVERYボードwww.st.com/web/catalog/tools/FM116/SC959/SS1532/PF252419を使用しました 。 別のボードを使用していて、コードがすぐに機能しない場合、問題はおそらく外部水晶発振器の周波数にあります。 ライブラリにはあらゆる種類のマクロ定義が豊富にあり、最新バージョンのライブラリには外部クロック周波数用のマクロが含まれていますが、このパラメーターはコメントなしで数字としてコードに記述されているため、開発者が形を失い、マニュアルを読むことを忘れないようです。 この数値(メガヘルツ単位のクロック周波数)は、マクロ定義PLL_Mのファイルsystem_stm32f4xx.cにあります。



そのため、USBからマイクロコントローラーのシリアルポートにデータを転送する、またはその逆のデータを転送する既製の例を基本とします。 シリアルポートは必要ありません。入力ストリームから出力にデータを転送するだけです。つまり、エコーを実装します。 PuTTYを使用して、動作することを確認します。 しかし、これでは十分ではありません。 デバイスとデータを交換するには、一度に複数の文字を送信する必要があります。 ランダムな長さのパケットを送信し、回答を読み取るPythonのテストプログラムを作成しています。 そして、驚きが待っています。 テストは機能しますが、長くは続かないため、次の読み取り試行は永久にフリーズするか、タイムアウトが設定されている場合は終了します。 デバッガーの助けを借りて問題を調べると、MKはすべての受信データを送信し、最後のメッセージは64バイト長であったことがわかります。 どうしたの?



ホストシステムのUSBスタックは多層構造です。 ドライバーレベルでは、データは受信されましたが、キャッシュに残りました。 ドライバーは、新しいデータが到着して古いデータを押し出したとき、またはドライバーが新しいデータが予期されないことがわかったときに、キャッシュされたデータをアプリケーションに転送します。 彼はどこからこの知識を得ることができますか? USBバスはデータをパケットで送信します。 この場合の最大パケットサイズは64バイトです。 次のデータパケットのデータが少ない場合、新しいデータをまだ待つことができません。これは、受信したすべてのデータをアプリケーションに送信するための信号です。 そして、データが正確に64バイトであった場合はどうなりますか? この場合、プロトコルは長さゼロ(ZLP)のパケットの送信を提供します。これは、ストリームを中断する信号です。 それを受信すると、ドライバーは新しいデータが予期されないことを理解します。 私たちの場合、STM32のUSBスタックの開発者はZLPについて何も知らなかったため、彼はそれを受け取りませんでした。



USBスタックの開発者が不当に無視した2番目の問題は、どこにも置くことができない場合にUSB経由で受信したデータをどう処理するかです。 入力バッファがいっぱいです。 概して、彼らは入力バッファの問題をまったく心配していませんでした-受信したすべてのデータがすぐに処理されると仮定しましたが、もちろん常に実行できるとは限りませんでした。 データを受信できない場合のUSBプロトコルでは、NAK応答が提供されます-否定的な確認。 このような応答の後、ホストはデータを再度送信するだけです。 入力バッファのオーバーフローを避けたい場合、フル送信(64バイト)の場所がない場合、チャネルをNAK状態に切り替える必要があります。これにより、すべての着信パケットに対する自動NAK応答が保証されます。



Tiva C-バグのあるレイヤーケーキ



実験には、EK-TM4C123GXL ボード www.ti.com/tool/ek-tm4c123gxlを使用しました 。 コンパイルには、TivaWareライブラリパッケージwww.ti.com/tool/sw-ek-tm4c123gxlが必要です。 ライブラリを調べると、開発者はZLPまたはバッファリングの問題を無視していなかったことがわかります。入力および出力チャネルで使用できるリングバッファが用意されています。 ただし、自動テストでも同じ結果が得られます-データ交換が突然停止します。 デバッガーを使用すると、今回はデータが循環転送バッファーにスタックしており、問題は最後のパケットのサイズ、つまりZLPに関係していないことがわかりました。



ライブラリのソースコードを注意深く調べることによってのみ問題を特定することが可能です。 ZLPを送信するには、デフォルトでは設定されていない特別なフラグを付ける必要があることがわかります。 おそらくこの状況により、他の開発者は、ZLPを送信するコードをもう1か所-USBスタックの下位レベルにあり、既にフラグなしで追加する必要がありました。 この変更により、転送が停止するバグが発生しました。 問題は次のように発生します。 前のパケットの送信が終了したとき、または前のパケットがなかった場合、送信機は次のパケットを受信し、アプリケーションはデータを送信バッファに追加しました。 転送を開始するコードは、USBスタックの下位レベルからの前のパケットの転送が完了したという通知を受け取ります。 問題は、スタックの下位レベルがZLPの転送を開始した場合、完了通知を送信しないことです。 彼は自分で転送を開始しました。 上位レベルは、送信機がZLPパケットを送信している間はデータの送信を開始せず、完了後に送信を開始しません。通知を受信しないため、転送プロセスは停止します。 問題を修正するのは非常に簡単です-ZLPを送信する下位レベルのコードを削除して、これをスタックの上位レベルに提供する必要があります。 解決する必要がある2番目の問題は、転送を開始するプロシージャを、割り込みハンドラのコンテキスト(転送の最後)とアプリケーションコンテキストの両方から呼び出して、転送バッファにデータを追加できることです。 異なるコンテキストからこのプロシージャへの呼び出しをシリアル化するには、実行中の中断を禁止する必要があります。



ソースコード



ここにgithub.com/olegv142/stm32tivc_usb_cdcがあります

stmおよびtiフォルダーには2つのテストプロジェクトがあります-usb_cdc_echoおよびusb_cdc_api。 1つ目は受信したすべてのデータを単純に送り返し、2つ目は必要に応じて簡単に適応できるパケットプロトコルを実装しています。 toolsフォルダーで、pythonでスクリプトをテストします。



All Articles