STM32 HAL ライブラリは、ピンポン バッファとアイドル割り込みのシリアル DMA トランシーバ メカニズムを実現し、2M ボー レートで簡単に実行できます。

序文

ダイレクト メモリ アクセス(ダイレクト メモリ アクセス、DMA ) により、一部のデバイスが CPU の介入なしに独立してデータにアクセスできるようになります。したがって、DMA を使用すると、大量のデータにアクセスするときに CPU 処理時間を大幅に節約できます。STM32 における一般的な DMA 転送方向: メモリ -> メモリ、ペリフェラル -> メモリ、メモリ -> ペリフェラル。ここでのペリフェラルは、UART や SPI などのデータ トランシーバー デバイスです。

組み込み開発では一般にシリアル ポートと呼ばれるユニバーサル非同期送受信機 ( UART ) は、通常、6400 bps の低いボー レートと 4 ~ 5 Mbps の高エネルギーを備えた中低速の通信シナリオで使用されます。ボー レートが 115200 bps より低く、データ量が大きくないシナリオでは、STM32 チップの主周波数が数十から数百メガヘルツであり、割り込み応答が遅いため、データの送受信に DMA は通常使用されません。低速シリアルポートの場合は、水を撒くだけです。ただし、送受信データ量が多い場合やボーレートがMbpsオーダーに上がる場合にはDMAを使用する必要があり、この際ブロッキング方式や割り込み方式でデータを送受信すると時間がかかります。 CPU 時間が過剰になり、他のタスクのパフォーマンスに影響を与える可能性があります。

STM32 でデータを送受信するための DMA の使用については、インターネット上に多くのルーチンやブログがあり、DMA の使用法を学ぶのは問題ありません。しかし、その多くは基本的な用途に使用されており、高速かつ大容量のデータが使用されるシナリオではデータの異常が発生しやすくなります。高速で信頼性の高いシリアル ポート トランシーバ プログラムには DMA が必要であり、ダブル バッファアイドル割り込み、  FIFO データ バッファも非常に重要なコンポーネントです。これはこの記事で解決する問題でもあります。

STM32CubeMX 構成

この記事で使用されている開発プラットフォーム:

  • STM32F407 (RoboMaster タイプ C ボード)
  • STM32CubeMX 6.3.0
  • STM32Cube FW_F4 V1.26.2
  • クライオン
  • GNU C/C++ コンパイラ

まず高速外部クロックを有効にします。

次にクロックツリーを設定します。最初の場所は外部水晶発振器の周波数で、使用する水晶発振器の実際の周波数に従って記入します。2 番目の場所は通常、使用するチップの最大周波数を記入します。ここで使用する F407 は 168 MHz です。 。記入してEnterを押すと他の箇所の値も自動で計算されるのでとても便利です。

次にシリアルポートを設定します。

  1. シリアルポートを選択します。

  2. モードを非同期 (非同期) に設定します。

  3. ボーレート、フレーム長、パリティ、ストップビット長を設定します。

  4. [追加] をクリックして受信および送信用の DMA 構成を追加します。RX で DMA モードを循環に変更することに注意してください。これにより、DMA 受信をオンにする必要があるのは 1 回だけです。DMA は、受信後にバッファの開始位置に自動的にリセットされます。バッファがいっぱいなので、毎回 DMA を有効にする必要はありません。受信後に DMA を再度有効にします。

  5. シリアルポート合計割り込みを開きます。

  6. 正しい GPIO ピンを選択します。CubeMX が選択するデフォルトのピンがほとんど正しい場合、これは無視されがちですが、BUG が見つかった場合にここに問題があるとは考えにくいので、必ず確認してください。

デバッグ インターフェイス、オペレーティング システム、プロジェクト管理などのその他の設定については詳細に説明されていませんが、GENERATE CODE は日常的な操作の後に生成できます。

シリアルDMA受信

シリアル ポートがデータを受信すると、DMA はそれをバイトごとに転送します RX_Buf 。一定量が転送されると割り込み(アイドル割り込み、ハーフフル割り込み、フルフル割り込み)が発生し、プログラムはコールバック関数に入りデータを処理します。この記事でのデータ処理のステップは、アプリケーションが読み取るためにデータを FIFO に書き込むことです (これについては後で紹介します)。まずはデータ受信のフローチャートを見てみましょう。

フルフル割り込みとハーフフル割り込みはどちらもよく理解されており、シリアル ポート DMA のバッファが半分だけ満たされたときに生成される割り込みです。アイドル割り込みは、シリアルポートが最後のフレームのデータを受信して​​から 1 バイト以内にデータを受信しない場合、つまりバスがアイドル状態になった場合に発生する割り込みです。可変長データを受信する場合に大変便利です。

現在、インターネット上のほとんどのチュートリアルでは、完全割り込みとアイドル割り込みを組み合わせてデータを受信する方法が使用されていますが、一定のリスクがあります。DMA は CPU から独立してデータを転送できます。つまり、CPU と DMA が同時にバッファにアクセスする可能性があります。その結果、CPU が途中でデータを処理すると、DMA がデータの転送を続けて前のバッファを上書きし、データが失われます。したがって、より合理的なアプローチは、ハーフフル割り込みを備えたピンポン キャッシュを実装することです。

バッファーによって実装されたピンポン キャッシュ

ピンポン キャッシュとは、1 つのキャッシュがデータを書き込むときに、デバイスが別のキャッシュからデータを読み取って処理し、データが書き込まれた後、双方がキャッシュを交換して、それぞれデータの書き込みと読み取りを行うことを意味します。これにより、デバイスがデータを処理するのに十分な時間が確保され、バッファ内の古いデータが完全に読み取られずに新しいデータによって上書きされる状況が回避されます。しかし、小さな問題があり、STM32 のほとんどのモデルのシリアル DMA にはバッファが 1 つしかないということです。

そう、ハーフフル割り込みです。1 つのバッファを 2 つに分割して使用できるようになりました。

この図を見て、上記の 3 つの割り込みを理解しましょう。ハーフフル割り込みは受信バッファの前半がいっぱいになった後にトリガーされ、フルフル割り込みは後半がいっぱいになった後にトリガーされます。これら 2 つの割り込みはいずれも行われません。割り込みはトリガーされますが、データ パケットが終了し、後続のデータがない場合、アイドル割り込みがトリガーされます。たとえば、サイズ 25 のデータ パケットをバッファ サイズ 20 のこのプログラムに送信すると、次の図に示すように 3 つの割り込みが生成されます。

プログラムの実装

ST が HAL ライブラリを提供してくれたおかげで原理の導入は完了しました。その後、C 言語で実装するのは非常に簡単です。

まずシリアルポートの DMA 受信を有効にします。

#define RX_BUF_SIZE 20
uint8_t USART1_Rx_buf[RX_BUF_SIZE];
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, USART1_Rx_buf, RX_BUF_SIZE);

USART1_Rx_buf 次にコールバック関数を記述し、 FIFO 内のデータをコールバック関数内の FIFO に移動します。

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    static uint8_t Rx_buf_pos;	//本次回调接收的数据在缓冲区的起点
    static uint8_t Rx_length;	//本次回调接收数据的长度
    Rx_length = Size - Rx_buf_pos;
    fifo_s_puts(&uart_rx_fifo, &USART1_Rx_buf[Rx_buf_pos], Rx_length);	//数据填入 FIFO
    Rx_buf_pos += Rx_length;
    if (Rx_buf_pos >= RX_BUF_SIZE) Rx_buf_pos = 0;	//缓冲区用完后,返回 0 处重新开始
}

このコールバック関数自体は弱い関数なので、自身で書き直す必要があります。これには 2 つの入力パラメータがあり、最初のパラメータは言うまでもなく、2 番目のパラメータは バッファ全体で使用されたサイズSize を参照しますここには非常に魔法のような場所があり、上記の 3 つの割り込みがここに入るので、記述するコードは数行だけです。

しかし、この 3 つの割り込みをどのように区別するかという疑問が生じます。答えは、区別する必要はなく、受信データの開始アドレスとデータ長を毎回計算するだけで受信が完了します。そこで私は 2 つの静的変数を定義しました: 今回受信したデータの長さ = 使用されているバッファーの合計サイズ - バッファー内でこのコールバックによって受信されたデータの開始位置; 開始位置は 0 から始まり、各コールバックはちょうど今回は受信データの長さを加算します。

シリアルDMA送信

シリアル ポート DMA の送信は受信よりもはるかに簡単で、送信データの FIFO から送信バッファにデータをコピーし、HAL ライブラリの送信関数を呼び出すだけで完了します。

const uint8_t TX_FIFO_SIZE = 100;
static uint8_t buf[TX_FIFO_SIZE];				//发送缓冲区
uint8_t len = fifo_s_used(&uart_tx_fifo);		//待发送数据长度
fifo_s_gets(&uart_tx_fifo, (char *)buf, len);	//从 FIFO 取数据
HAL_UART_Transmit_DMA(&huart1, buf, len);		//发送

FIFOキュー

先入れ先出し (FIFO) は聞きなれないかもしれませんが、キューと呼ぶと馴染みがあるはずです。RX_Bufこの記事では、FIFO を DMA トランシーバ バッファ ( 、TX_Buf) とアプリケーション プログラム間のバッファとして使用します。抽象的に言えば、以下の図に示す受信ステータスのデータの流れを見てください。送信時にはデータの流れが逆になります。

シリアルポートがデータを受信すると、DMAはシリアルポートのレジスタからメモリ内に開いた受信バッファにデータを転送し、 RX_Buf割り込み(ハーフフル割り込み、フルフル割り込み、アイドル割り込み)を発生させ、割り込み内のデータはコールバック関数が RX_Buf FIFO に送信されると、アプリケーションは FIFO が空かどうかを検出するだけで済み、空でない場合はデータを読み取ることができます。

これは余計なことのように思えるかもしれませんが、DMA 受信バッファがすでに存在します。 RX_Buf ここからデータを直接読み取るのは良いことではないでしょうか? ここでの問題は、正しい RX_Buf 処理が DMA によって生成された割り込みのコールバック関数でのみ実行できることです。割り込みコールバック関数はブロックされていますが、データを受信するシリアル ポートには影響せず、DMA はデータをシリアル ポートに転送し続けます RX_Buf。 RX_Buf サイズは常に制限されています。はい、後のデータが前のデータを上書きします。したがって、データが到着したらすぐに処理する必要があり、タイムリーに処理しないとデータが失われますFIFO にもオーバーフローの問題はありますが、発生確率は低く、対処は比較的簡単です。

FIFO を使用するもう 1 つの理由は、アプリケーション層をドライバー層から分離することです。RX_Buf アプリでは、どのような状況でどれだけのデータが取得されるかは関係なく、FIFO からデータを読み取るだけでよく、シリアル ポート DMA の割り込みコールバック関数も書き込み方法が固定されており、データを FIFO にプッシュするだけです。  FIFO は、データが可変長でデータ量が多いシナリオでは間違いなく非常に必要なコンポーネントです。

しかし、注意深い人は、シリアル ポート DMA を構成する STM32CubeMX の上の図にも「FIFO」があることに気づくかもしれません。これと上記の FIFO の違いは何ですか? これは私も混乱しているので、少し説明します。

FIFO と DMA の FIFO は同じ FIFO ではありません

DMAにもFIFOがありますが、その機能はシリアルポートのレジスタとメモリバッファの間にFIFOバッファを追加するもので、データの流れは以下のようになります。

シリアル ポート レジスタは 1 バイトしか格納できないため、ダイレクト モードの DMA はバイトごとにデータをメモリ バッファに 1 回転送する必要があります。DMA の FIFO の実際の効果は、単にデータのバッチを保存してまとめて送信することで、ソフトウェアのオーバーヘッドと AHB バス上のデータ送信数を削減できます。データが連続していて、他のタスクが必要なシナリオに適しています。システムのオーバーヘッドが高くなります。ただし、DMA FIFO は送信前にバッチを蓄積する必要があり、蓄積が不十分な場合は送信されないため、いくつかの制限があります。この記事では DMA の FIFO を使用せず、ダイレクト モードを使用します。

移植 FIFO

長い話が終わったら、いよいよコードを書きます。FIFO リング バッファを自分で実装する代わりに、  RoboMaster AI ロボットのファームウェアで使用されている FIFO を移植しました。

  1. 上記のropoを コピーしfifo.c て fifo.h 自分のプロジェクトにファイルします。

  2. 上記のリンクを fifo.h 削除し て検索し、ミューテックスの次の行の実装を にコピーし  、追加でヘッダー ファイルをインクルードします #include "sys.h"sys.hfifo.hcmsis_gcc.h

    #include <cmsis_gcc.h>
    #define MUTEX_DECLARE(mutex) unsigned long mutex
    #define MUTEX_INIT(mutex)    do{mutex = 0;}while(0)
    #define MUTEX_LOCK(mutex)    do{__disable_irq();}while(0)
    #define MUTEX_UNLOCK(mutex)  do{__enable_irq();}while(0)
    
  3. このFIFOライブラリでは動的に作成されるキューの実装を使用しています mallocが、OSを使用している場合はOSのメモリ管理APIに変更する必要があります。ただし、この記事ではキューを作成する動的方法は使用しません。

FIFOを使用する

シリアルポートDMA受信とシリアルポートDMA送信の2回に分けて紹介しましたが、ここではその使用方法を紹介します。

fifo_s_puts(&uart_rx_fifo, &USART1_Rx_buf[Rx_buf_pos], Rx_length);	//数据填入 FIFO
uint8_t len = fifo_s_used(&uart_tx_fifo);		//待发送数据长度
fifo_s_gets(&uart_tx_fifo, (char *)buf, len);	//从 FIFO 取数据

圧力試験

もちろん、このような一連の送受信処理を低速環境(115200bps)で使用する必要はありませんが、どの程度のボーレートで使用できるのか、どの程度安定しているのかは疑問です。したがって、それをテストする必要があります。

テスト用に、PL2303 と FT232 という 2 つのチップのシリアル ポート モジュールを選択しました。

PL2303

PL2303 データシートは、75 bps ~ 6 Mbps のシリアル ポート ボー レートをサポートしています。しかし、テストした結果、最大ボーレートは約 970000 bpsで、どんなに高くてもデータを受信できず、期待値を大きく下回っていました。親切な人が何が起こっているのか教えてくれることを願っています。

次に、通信の安定性をテストします。17 バイトのデータ パケットを最小自動再送信間隔 10 ミリ秒でマイクロコントローラーに送信すると、マイクロコントローラーはすべてのデータを返します。テストは 58 分間実行され、1958.69 KB のデータを送信し、1958.69 KB を受信しました。パケット損失はなく、安定性は合格でした。スクリーンショットでは、データ パケットは送信されましたが、停止時に受信されなかったため、Tx の値が Rx よりも大きくなっており、実行プロセス全体を通じて 2 つのデータは常に等しいためです。

PL2303 のもう 1 つの小さな問題は、Win10 上のドライバーに問題があり、使用するには古いバージョンのドライバーを自分でダウンロードしてインストールする必要があることです。

FT232

FT232 のデータシートには次のような説明があります。

  • データ転送速度は 300 ボーから 3 Mbaud (RS422/RS485 および TTL レベル)、および 300 ボーから 1 Mbaud (RS232)

私の手持ちの FT232 チップを使用したこのシリアル ポート モジュールの最大ボー レートの測定値は2 Mbps で、最終的に期待に応えました。

2 Mbps での安定性テストも非常に良好で、パケット損失なしで 66 分間実行できました。

次に、ロジック アナライザーを使用して、実際の通信遅延を 2 Mbps で単純にテストします 。テスト方法は、17 バイトのデータ パケットを送信し、マイクロコントローラーは受信後にシリアル ポートを介してすべてのデータを返します。

  • 転送遅延は 約400 μsです 。私のプログラムでは、この時間は主に FIFO が空かどうかを検出する頻度によって決まり、現在の理論値は 1000 Hz です。

  • 単一の 17 バイトのデータ パケットの持続時間は 84 μsで、送受信プロセスの合計長は約 0.5 msです。

115200bps の通信 と比較すると 、17Bytesのデータパケット1個の長さは約1500μsとなり、データパケットの送受信全体の処理時間は約 3100μsとなります。

オンライン ブログによると、STM32F407 は最大 10.5 Mbps をサポートしていますが、マニュアルにはこれが見つかりませんでした。しかし、2 Mbps がその限界ではありません。マイコンがコンピュータに接続されている場合、シリアルポートモジュールによって制限され、基本的に 2 Mbps が上限ですが、マイコンとマイコン間のシリアル通信は依然として盗聴される可能性があります。

参考

acuity. (2020, September 3). 厳密な STM32 シリアル ポート DMA 送受信 (1.5Mbps ボー レート) メカニズム_思考が滑らない限り、常に問題よりもアイデアの方が多いです。-CSDN blog_dma 受信厳密な STM32 シリアル ポート DMA 送受信 (ボーレート 1.5Mbps) の仕組み_厳密なシリアル ポート_Acuity. のブログ - CSDN ブログ

STマイクロエレクトロニクス。(2021年6月)。 STM32F4 HAL および低層ドライバーの説明。 https://www.st.com/content/ccc/resource/technical/document/user_manual/2f/71/ba/b8/75/54/47/cf/DM00105879.pdf/files/DM00105879.pdf/jcr:コンテンツ/翻訳/en.DM00105879.pdf

おすすめ

転載: blog.csdn.net/wanglei_11/article/details/131576165