STM32 DMAを使ってシリアル通信してみる

  • 2020.07.23
  • HAL
STM32 DMAを使ってシリアル通信してみる

今回はDMAを使ってシリアル通信の送信処理を行ってみます。

投稿時の開発環境を記しておきます。

PC:Windows10 OS
IDE: STM32CubeIDE Version1.3.0
Configurator: STM32CubeMX Version5.5.0
Board: STM32Nucleo-F401RE

DMAの概要

DMAは Direct Memory Accessの略です。
通常はペリフェラル(周辺I/O)からCPUを介してメモリーにデータを読み込んだり、メモリーからCPUを介してペリフェラルにデータを書き込みます。
DMAを使うと文字通りダイレクトにメモリーにアクセスしてくれるのでCPUを介す必要がなくなり、その時間を他の処理に回すことができるというわけです。

その反面、読み込みや書き込みのデータ数(バイト数)が決まっている場合には処理が簡単ですが、そうでない場合には処理が複雑になるなどのリスクが出てきます。
例えばUARTの受信処理は、例え送られてくるデータ数が決まっていたとしても通信環境によっては取りこぼしやエラーが発生することで予定外のデータ数になることがあります。
ここでは複雑な受信処理をDMAで扱うことはやめて、比較的簡単に扱える送信処理についてのみDMAの動作を確認することにします。

プロジェクトをつくる

IDEを起動し、File- New – STM32 Project を選択し、Target Selection ウィンドウが出たら Board Selector タブを選択し Boards List から NUCLEO-F401RE を選択し Next ボタンを押します。

Project Name に F401UartDMA と入力し、Finishボタンを押します。
Initialize all peripherals with their default Mode ? と聞いてくるので Yesを押します。
This kind of project is associated with the STM32CubeMx perspective. Do you want to open this perspective now ? と聞いてくるので Yesを押します。

コンフィグレーターの設定を行う

左側の Connectivity の USART2 をクリックし、その右側の DMA Settings をクリックします。

左下の Addボタンをクリックして、出てきたコンボボックスの Select を USART2_TX にします。

続いて Parameter Settings で Baud Rate を 1200bps に変更します。
それ以外の設定はそのまま(以下)にしておきます。

データ: 8bit
パリティ: none
ストップビット: 1bit
フロー制御: none

送信と受信側で設定が異なるとうまく通信できないので注意してください。

Project Explorer で F401UartDMA を選択し、Project – Build Project で一度ビルドしてエラーがないことを確認しておきます。
もし問い合わせのウィンドウが出てきたら Yes を押します。

コーディングする

Project Explorer で F401UartDMA – Core – Src – main.c をダブルクリックしてファイルを開きます。
まず以下のように変数を記述してください。

int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t buffer[16];
  uint32_t t1, t2, t3, t4;
  /* USER CODE END 1 */

次に whileループの部分を以下のようにコーディングします。

/* USER CODE BEGIN WHILE */
  buffer[0] = 'a';
  for (i = 1; i < 10; i++)
  {
	  buffer[i] = buffer[i - 1] + 1;
  }
  while (1)
  {

	t1 = HAL_GetTick();
	HAL_UART_Transmit(&huart2, buffer, 10, 1000);
	t2 = HAL_GetTick();
	t3 = t2 - t1;

	HAL_Delay(1000);

	t1 = HAL_GetTick();
	HAL_UART_Transmit_DMA(&huart2, buffer, 10);
	t2 = HAL_GetTick();
	t4 = t2 - t1;

	HAL_Delay(1000);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

次に Project Explorer で F401UartDMA - Core - Src - stm32f4xx_it.c をダブルクリックしてファイルを開きます。

DMA送信が完了すると DMA1_Stream6_IRQHandler()に来るので完了の手続きを行います。
以下のようにコードを2箇所に追加してください。
変数の宣言の追加と、割り込みハンドラ内に1行コードを追加します。

extern UART_HandleTypeDef huart2;
void DMA1_Stream6_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Stream6_IRQn 0 */

  /* USER CODE END DMA1_Stream6_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_usart2_tx);
  /* USER CODE BEGIN DMA1_Stream6_IRQn 1 */
  huart2.gState = HAL_UART_STATE_READY; // この行を追加する
  /* USER CODE END DMA1_Stream6_IRQn 1 */
}

ビルドする

それではビルドしてエラーがないことを確認しておきましょう。
もし問い合わせのウィンドウが出てきたら Yes を押します。

main()関数の中に MX_USART2_UART_Init() という関数があります。

デフォルトモードで初期化したことによって、この関数が生成されています。

初期化によって PA2 が USART2 の TX (送信)に、 PA3 が USART2 の RX (受信)に割り当てられています。

回路図を見ると、これらの端子はデバッガ用のマイコンとつながっていることがわかります。

そして USBケーブルでパソコンと接続すると、パソコン側で仮想COMポートとして認識されます。

そしてそのままでシリアル通信できる環境ができあがっているのです。

一般的には

マイコン - RS232CのドライバIC <---RS232Cクロスケーブル---> RS232CのドライバIC - マイコン

のような接続になるのですが、STマイクロのボードを使うとUSBケーブルでつなぐだけですむのでシリアル通信のテストを行うのに便利です。

ボードを選んでデフォルトモードで周辺機能を初期化した状態で、USARTの機能を使える準備ができているわけです。

パソコン側の準備を行う

パソコン側もデータを送受信できるアプリを準備しなければなりません。
ここでは Tera Term を使います。

Tera Termを起動してシリアル(E)を選択し、ポート(R):で STLink Virtual Com Port が含まれているCOMポートを選択しOKボタンを押します。
こちらの環境では COM5 が Virtual Com Port に割り当てられていました。
STのシステムが仮想的にCOMポートの環境をつくってくれているので使わせて頂きましょう。

次に メニューの 設定 - シリアルポート から通信パラメータを以下のように設定しOKボタンを押します。
これらは MX_USART2_UART_Init() の中で設定された値に合わせてください。

マイコンボードとパソコンをUSBケーブルで接続しておいてください。

コードの概要

以下の関数はDMAを使わずに送信します。ブロッキングモードで動作します。
HAL_UART_Transmit(&huart2, buffer, 10, 1000);

以下の関数はDMAを使って送信します。ノンブロッキングモードで動作します。
HAL_UART_Transmit_DMA(&huart2, buffer, 10);

HAL_GetTick()は 1msec毎にカウントアップするカウンタを読む関数です。
処理の前と後ろにこの関数を置いて、それぞれの戻り値を引き算することでおおよその処理時間がわかります。

t3 >> t4 となるはずです。

DMA1_Stream6_IRQHandler()内で次のコードを書くことで次回の送信を受け付けることになります。

huart2.gState = HAL_UART_STATE_READY;

プログラムを動かしてみる

それでは Run - Resume してプログラムを動かしてみましょう。

1秒周期で Tera Term に abcdefghij の文字列が表示更新されれば成功です。

こちらの環境では t3 は 83 程度、t4 は 0 となっています。

ボーレートが 1200bpsなので通信時間は 1/1200 * 10bit * 10byte = 約 83msec です。

DMAを使わないで送信する HAL_UART_Transmit() の場合、10バイトのデータを送り終わるまで関数から戻ってこないことがわかります。

それに比べて HAL_UART_Transmit_DMA() の方は遥かに速い時間で戻ってくることがわかります。

割り込み処理のオーバーヘッドを差し引いても、充分なおつりが返ってきます。

いかがでしたでしょうか?

DMAを使うと効率よく送信処理ができることがわかりました。

HALカテゴリの最新記事