STM32 UART DMA転送:高速データ通信を実現
組み込みシステムにおいて、UART (Universal Asynchronous Receiver/Transmitter) はデバイス間のシリアル通信の基盤として広く使用されています。しかし、特に高速なデータ転送やリアルタイム性が求められるアプリケーションでは、UARTの処理速度がボトルネックとなる場合があります。この問題を解決するために、STM32マイコンに搭載されているDMA (Direct Memory Access) 機能を利用することで、CPU負荷を軽減し、UART通信のパフォーマンスを大幅に向上させることが可能です。
本記事では、STM32マイコンにおけるUART DMA転送の概念、メリット、設定方法、そして具体的な実装例について詳細に解説します。
1. UART通信の基礎
UARTは、非同期シリアル通信プロトコルであり、送信機と受信機の間でクロック信号を共有する必要がありません。データはシリアルに、つまり1ビットずつ送信されます。UART通信は、以下の要素によって構成されます。
- 送信機 (TX): データをシリアルに変換し、送信ラインに出力します。
- 受信機 (RX): 送信ラインからのシリアルデータを受信し、パラレルデータに変換します。
- ボーレート: 1秒間に送信されるビット数 (bps) を表します。一般的なボーレートには、9600、115200、230400 bpsなどがあります。
- データビット: 1回のデータ転送で送信されるビット数 (通常は8ビット)。
- パリティビット: エラー検出のためのオプションのビット。
- ストップビット: データの終了を示すビット。
UART通信の基本的な流れ
- 送信側は、送信データをシリアルデータに変換し、スタートビット (通常はLow) を付加して送信を開始します。
- 続けて、データビット、パリティビット (オプション)、ストップビット (通常はHigh) を送信します。
- 受信側は、スタートビットを検出すると、それに続くビットをデータとして受信し、パラレルデータに変換します。
- 受信側は、パリティビット (オプション) を使用してエラー検出を行い、ストップビットを確認してデータの受信を完了します。
UART通信の課題
従来のUART通信では、データの送受信処理をCPUが行うため、以下の課題があります。
- CPU負荷の増加: 大量のデータを送受信する場合、CPUはUARTの割り込み処理に多くの時間を費やす必要があり、他のタスクの実行を妨げる可能性があります。
- データ損失のリスク: CPUがUARTの割り込み処理に間に合わない場合、受信データが失われる可能性があります。
- リアルタイム性の低下: CPU負荷が高い場合、リアルタイム性が重要なアプリケーションで問題が発生する可能性があります。
2. DMA (Direct Memory Access) の概要
DMA (Direct Memory Access) は、CPUを介さずにメモリ間でデータを直接転送する機能です。DMAコントローラは、CPUの指示に従って、指定されたメモリ領域から別のメモリ領域へデータを転送します。
DMAのメリット
- CPU負荷の軽減: CPUはデータ転送処理から解放され、他のタスクの実行に集中できます。
- 高速なデータ転送: DMAコントローラはCPUよりも高速にデータを転送できる場合があります。
- リアルタイム性の向上: CPU負荷が軽減されるため、リアルタイム性が向上します。
DMAの動作原理
- CPUは、DMAコントローラにデータ転送のソースアドレス、デスティネーションアドレス、転送サイズなどの情報を設定します。
- DMAコントローラは、CPUの指示に従って、ソースアドレスからデータを読み出し、デスティネーションアドレスに書き込みます。
- データ転送が完了すると、DMAコントローラはCPUに割り込みを発生させ、転送完了を通知します。
STM32のDMA
STM32マイコンには、複数のDMAチャネルが搭載されており、UART、SPI、ADC、タイマーなど、さまざまなペリフェラルとのデータ転送に使用できます。各DMAチャネルは、独立して動作し、異なるペリフェラルとのデータ転送を同時に実行できます。
3. STM32 UART DMA転送のメリット
STM32マイコンでUART DMA転送を使用すると、以下のメリットが得られます。
- 高速なデータ転送: UARTのデータをDMAコントローラが直接メモリに転送するため、CPUの介在による遅延を排除し、高速なデータ転送を実現できます。
- CPU負荷の軽減: CPUはUARTのデータ送受信処理から解放され、他のタスクの実行に集中できます。これにより、システムの全体的なパフォーマンスが向上します。
- リアルタイム性の向上: CPU負荷が軽減されるため、リアルタイム性が重要なアプリケーションで安定した動作を維持できます。
- バッファリングの効率化: 受信データをメモリに直接書き込むため、バッファリング処理を効率化できます。
- 割り込み処理の削減: データ送受信完了時にのみ割り込みが発生するため、割り込み処理のオーバーヘッドを削減できます。
4. STM32 UART DMA転送の設定
STM32 UART DMA転送を設定するには、以下の手順を実行します。
4.1. UARTの初期化
まず、UARTの初期化を行います。これには、ボーレート、データビット、パリティ、ストップビットなどのパラメータを設定することが含まれます。
“`c
include “stm32f4xx_hal.h”
UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate) {
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK) {
Error_Handler(); // エラー処理
}
}
“`
4.2. DMAの初期化
次に、DMAの初期化を行います。これには、DMAチャネル、DMAストリーム、転送方向、転送サイズなどを設定することが含まれます。
“`c
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;
void DMA_Init(void) {
/ DMA controller clock enable /
__HAL_RCC_DMA2_CLK_ENABLE();
/ USART1_RX Init /
hdma_usart1_rx.Instance = DMA2_Stream2;
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; // Continuous reception
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) {
Error_Handler();
}
__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
/ USART1_TX Init /
hdma_usart1_tx.Instance = DMA2_Stream7;
hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL; // Single Transfer
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) {
Error_Handler();
}
__HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);
/ DMA interrupt init /
/ DMA2_Stream2_IRQn interrupt configuration /
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
/ DMA2_Stream7_IRQn interrupt configuration /
HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
}
“`
解説:
hdma_usart1_rxとhdma_usart1_txは、それぞれUART1の受信と送信に使用するDMAハンドルの構造体です。hdma_usart1_rx.Instance = DMA2_Stream2;は、DMA2のStream2を受信に使用することを指定しています。STM32のUARTペリフェラルには、特定のDMAチャネルとストリームが割り当てられているため、データシートを確認する必要があります。hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;は、DMAチャネル4を受信に使用することを指定しています。hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;は、転送方向がペリフェラル (UART) からメモリであることを指定しています。hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;は、ペリフェラルアドレスがインクリメントされないことを指定しています (UARTのデータレジスタは固定アドレスです)。hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;は、メモリアドレスがインクリメントされることを指定しています (受信データを連続したメモリ領域に格納するため)。hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;は、DMAを循環モードで使用することを指定しています。これにより、DMAはバッファの終端に達すると、自動的に先頭に戻ってデータの受信を継続します。hdma_usart1_tx.Instance = DMA2_Stream7;は、DMA2のStream7を送信に使用することを指定しています。hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;は、転送方向がメモリからペリフェラル (UART) であることを指定しています。hdma_usart1_tx.Init.Mode = DMA_NORMAL;は、DMAをノーマルモードで使用することを指定しています。DMAは、指定されたデータ量を一度だけ転送します。__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);と__HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);は、UARTハンドルとDMAハンドルを関連付けます。HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);とHAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);は、DMA受信完了割り込みを設定します。HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0);とHAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);は、DMA送信完了割り込みを設定します。
4.3. UART DMA転送の開始
UARTとDMAの初期化が完了したら、UART DMA転送を開始します。
受信:
“`c
uint8_t rx_buffer[RX_BUFFER_SIZE]; // 受信バッファ
void UART_StartReceiveDMA(void) {
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
“`
送信:
“`c
uint8_t tx_buffer[TX_BUFFER_SIZE] = “Hello, world!”; // 送信バッファ
void UART_StartTransmitDMA(void) {
HAL_UART_Transmit_DMA(&huart1, tx_buffer, TX_BUFFER_SIZE);
}
“`
解説:
HAL_UART_Receive_DMA()は、UART DMA受信を開始する関数です。第一引数にはUARTハンドル、第二引数には受信バッファのアドレス、第三引数には受信バッファのサイズを指定します。HAL_UART_Transmit_DMA()は、UART DMA送信を開始する関数です。第一引数にはUARTハンドル、第二引数には送信バッファのアドレス、第三引数には送信バッファのサイズを指定します。
4.4. 割り込み処理
DMA転送が完了すると、割り込みが発生します。割り込みハンドラ内で、転送完了後の処理を実行します。
“`c
void DMA2_Stream2_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_usart1_rx);
// 受信完了後の処理 (例: 受信データの処理)
// …
}
void DMA2_Stream7_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_usart1_tx);
// 送信完了後の処理 (例: 次のデータの送信)
// …
}
“`
解説:
HAL_DMA_IRQHandler()は、DMA割り込みを処理する関数です。- 割り込みハンドラ内では、転送完了後の処理を実行します。例えば、受信完了の場合、受信バッファ内のデータを処理したり、次の受信を開始したりします。送信完了の場合、次のデータを送信したり、送信完了フラグを設定したりします。
4.5. HAL_UART_RxCpltCallback() と HAL_UART_TxCpltCallback()
HALライブラリでは、受信完了と送信完了のコールバック関数 HAL_UART_RxCpltCallback() と HAL_UART_TxCpltCallback() を使用することもできます。これらの関数は、DMA転送が完了した後に自動的に呼び出されます。
“`c
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// USART1 受信完了後の処理
// 例:受信データを処理し、必要に応じて次の受信を開始する
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE); // 再度受信を開始
}
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// USART1 送信完了後の処理
// 例:次のデータを送信する
}
}
“`
解説:
- これらのコールバック関数は、
stm32f4xx_hal_uart.cファイル内でweak属性で定義されています。そのため、ユーザーコードで同じ名前の関数を定義することで、デフォルトの動作をオーバーライドできます。 HAL_UART_RxCpltCallback()は、受信DMA転送が完了した後に呼び出されます。HAL_UART_TxCpltCallback()は、送信DMA転送が完了した後に呼び出されます。- これらの関数内では、完了後の処理を実行します。例えば、受信完了の場合、受信バッファ内のデータを処理したり、次の受信を開始したりします。送信完了の場合、次のデータを送信したり、送信完了フラグを設定したりします。
5. 実装例:エコーバック
以下のコードは、UART1で受信したデータをそのままUART1で送信するエコーバックプログラムの例です。
“`c
include “stm32f4xx_hal.h”
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;
define RX_BUFFER_SIZE 256
uint8_t rx_buffer[RX_BUFFER_SIZE];
void SystemClock_Config(void);
void Error_Handler(void);
void UART1_Init(uint32_t baudrate);
void DMA_Init(void);
void UART_StartReceiveDMA(void);
int main(void) {
HAL_Init();
SystemClock_Config();
UART1_Init(115200);
DMA_Init();
UART_StartReceiveDMA();
while (1) {
// メインループ
}
}
void SystemClock_Config(void) {
// クロック設定 (省略)
// …
}
void Error_Handler(void) {
// エラー処理 (省略)
// …
}
void UART1_Init(uint32_t baudrate) {
// UART1 初期化 (上記の例を参照)
// …
}
void DMA_Init(void) {
// DMA 初期化 (上記の例を参照)
// …
}
void UART_StartReceiveDMA(void) {
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
void DMA2_Stream2_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_usart1_rx);
// 受信データを送信
HAL_UART_Transmit_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
void DMA2_Stream7_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_usart1_tx);
// 受信DMAを再開
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
“`
解説:
- 初期化:
SystemClock_Config()、UART1_Init()、DMA_Init()関数で、システムクロック、UART1、DMAを初期化します。 - 受信開始:
UART_StartReceiveDMA()関数で、UART1 DMA受信を開始します。受信データはrx_bufferに格納されます。 - 割り込み処理:
DMA2_Stream2_IRQHandler()は、DMA受信完了割り込みハンドラです。受信が完了すると、受信データrx_bufferをHAL_UART_Transmit_DMA()関数で送信します。DMA2_Stream7_IRQHandler()は、DMA送信完了割り込みハンドラです。送信が完了すると、HAL_UART_Receive_DMA()関数で受信DMAを再開します。
- メインループ: メインループは空で、バックグラウンドでDMAによるデータ送受信が行われます。
6. トラブルシューティング
UART DMA転送で問題が発生した場合、以下の点を確認してください。
- クロック設定: UARTとDMAのクロックが正しく設定されているか確認してください。
- ピン設定: UARTのTX/RXピンが正しく設定されているか確認してください。
- DMAチャネル: UARTに割り当てられたDMAチャネルが正しく設定されているか確認してください。
- バッファサイズ: 受信バッファと送信バッファのサイズが適切であるか確認してください。
- 割り込み設定: DMAの割り込みが有効になっているか確認してください。
- DMAモード: DMAの転送モード (ノーマル、循環) が適切であるか確認してください。
- DMA優先度: DMAの優先度が他のペリフェラルよりも高くなっているか確認してください。
- HALライブラリバージョン: HALライブラリのバージョンが最新であるか確認してください。
- デバッグ: デバッガを使用して、DMAの動作をステップ実行し、問題の原因を特定してください。
- データシートとリファレンスマニュアル: STM32のデータシートとリファレンスマニュアルを参照して、UARTとDMAの正しい設定方法を確認してください。
7. まとめ
STM32マイコンにおけるUART DMA転送は、CPU負荷を軽減し、高速なデータ通信を実現するための強力な手法です。本記事では、UART DMA転送の概念、メリット、設定方法、そして具体的な実装例について詳細に解説しました。DMAを活用することで、組み込みシステムのパフォーマンスを大幅に向上させ、より複雑なアプリケーションを実現することが可能です。 ぜひ、UART DMA転送をマスターし、自身のプロジェクトに役立ててください。
8. 今後の展望
- 低電力UART DMA: より省電力なUART DMA転送技術の研究開発が進められています。これにより、バッテリ駆動の組み込みシステムの動作時間を延ばすことが可能になります。
- セキュリティ強化: UART通信におけるセキュリティ脆弱性への対策として、ハードウェア暗号化機能を搭載したUART DMAコントローラが登場する可能性があります。
- インテリジェントDMA: AI機能を搭載したDMAコントローラが開発されることで、データの内容に応じて転送先を自動的に変更したり、エラー検出機能を強化したりすることが可能になります。
補足:
- 本記事では、STM32F4シリーズを例に説明しましたが、STM32の他のシリーズでも同様の手法でUART DMA転送を実装できます。ただし、DMAチャネル、ストリーム、および関連する割り込みの設定は、シリーズによって異なる場合がありますので、データシートとリファレンスマニュアルを必ず参照してください。
- 本記事のコード例は、HALライブラリを使用していますが、標準ペリフェラルライブラリ (SPL) を使用することも可能です。SPLを使用する場合は、対応するAPI関数を使用する必要があります。
- UART DMA転送の設定は、CubeMXなどのツールを使用することで、GUI上で簡単に行うことができます。これらのツールを使用することで、コードの生成と初期化を自動化し、開発時間を短縮できます。
- UART DMA転送は、データ転送速度とCPU負荷のバランスを考慮して、適切な設定を選択する必要があります。例えば、ボーレートが高すぎる場合や、バッファサイズが小さすぎる場合は、データ損失が発生する可能性があります。
- UART DMA転送は、UART通信のエラー検出機能と組み合わせて使用することで、より信頼性の高いデータ通信を実現できます。例えば、パリティチェックやCRCチェックを使用することで、データのエラーを検出することができます。
- UART DMA転送は、組み込みシステムのデバッグが難しくなる可能性があるため、適切なデバッグツールと手法を使用する必要があります。例えば、デバッガを使用して、DMAの動作をステップ実行し、問題の原因を特定することができます。
この情報が、STM32 UART DMA転送に関する理解を深める上で役立つことを願っています。