STM32三昧(4) Cube IDEでDMA転送、メモリからメモリ、Nucleo

Joseph Halfmoon

Cube IDE使ってSTM32のHALを練習中です。前回DACをソフトウエアで制御し「ゆるゆる」な三角波を発生させてみました。もっと高速な波形を生成するのならDMA利用かね。ということで今回はDMA関係のHALを練習してみることに。まずはメモリ間です。DMAリクエスト元のペリフェラルの設定不要なのでお手軽。

※Windows 11 PC上にインストールしたSTM32CubeIDE Version: 1.13.2上で動作を確認しています。今回のターゲットボードは Nucleo-F072RBです。

STM32F072RBのDMA(Direct Memory Access Controller)

上位機種では2ユニットのDMAを搭載しているSTM32ですが、シリーズの中でもベーシックなSTM32F072RBは、1ユニット、7チャンネルのみのDMAコントローラを搭載してます。DMAは初期設定さえしておけば、

    • ペリフェラルからメモリへの転送
    • メモリからペリフェラルへの転送
    • メモリからメモリへの転送

をペリフェラルのリクエストに応じてCPUにお願することなく「自主的に」実施してくれる仕組みです。ペリフェラルに入力されたデータを勝手にメモリ上のバッファに詰め込んでくれたり、メモリ上に並べてあるデータを所定の周期でペリフェラルへ書き出すなどCPUの負荷を大いに軽減できる仕組みです。使わない手はないっと。

通常、ペリフェラル相手の場合、入出力のペリフェラル自身かタイマなどのリクエストに応じて転送を行うのですが、メモリ間の転送に関してはDMAコントローラのみの設定で実施することができます。

Cube IDEでの設定

今回は再び 『wiki.st.com』内の以下のページを参照させていただきました。

Getting_started_with_DMA

上記のページは、強力なSTM32L476を題材にしてますが、「お求めやすい」STM32F072RBでも基本は一緒みたいです。この辺の統一感がSTM32だな。Cube IDEのDMAの設定が以下に。

DMAsetting

MEMTOMEM設定にしてしまうと、こちらとしては特に選択を変更する必要はありません。お任せ。

これでソースをGenerateすれば、必要な初期設定などを実施してくれるコードが自動で生成されるのでお楽。

今回実験のソース

上記のGetting startedでは、デバッガ使ってメモリ上の配列がコピーされていることを確認しているみたいでした。こちらでは「伝統の」printfデバッグ。USBシリアルにDMA転送前のメモリの様子を出力した後、メモリ間DMA転送を実施、その後、再び同じメモリの様子を出力する、という手順です。

例によってCube IDEのソースでは設定されたソース内の /* USER CODE BEGIN チョメチョメ */から/* USER CODE END チョメチョメ */間に適宜コードを記載することが求められる(そうしないと再ジェネレートで消えてしまう)ので、各「お印」事に自動生成コードに手動追加したコードを書いてます。

    • UART2とprintfの紐づけ、その1
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

printfを使うので、<stdio.h>をインクルードしておかないとなりませぬ。

なお、USBシリアルに接続されているUART2と今回テーマのDMAに対するハンドルは自動生成されてます。そして初期化も自動生成コードに含まれているので、自分でやることはありません。自動生成されたハンドル変数名は以下の通り。

huart2
hdma_memtomem_dma1_channel1
    • UART2とprintfの紐づけ、その2
* USER CODE BEGIN PFP */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END PFP */

printfを使うためには、1文字出力がuart2ハンドルに向けられていないとならないみたいなので、忘れずに要設定デス。

    • DMA実験準備
/* USER CODE BEGIN 0 */
uint8_t  Buffer_Src  [] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
uint8_t  Buffer_Dest [] = { 19, 19, 29, 39, 49, 59, 69, 79, 89, 99};

void printBuffers() {
    printf("Print Buffers\n");
    printf("Src: ");
    for (int idx=0; idx < 10; idx++) {
        printf("%3d ",Buffer_Src[idx]);
    }
    printf("\n");
    printf("Dst: ");
    for (int idx=0; idx < 10; idx++) {
        printf("%3d ",Buffer_Dest[idx]);
    }
    printf("\n");
}
/* USER CODE END 0 */

転送元と転送先のメモリ(配列)を大域変数として配置した後、ついでのそれのダンプ関数も定義してます。printBuffers()すればソース、デスティネーションの内容が分かるっと。

    • DMA実験本体
/* USER CODE BEGIN 2 */
printf("Before DMA\n");
printBuffers();

HAL_DMA_Start ( & hdma_memtomem_dma1_channel1 ,  ( uint32_t )  ( Buffer_Src ),  ( uint32_t )  ( Buffer_Dest ),  10 );
while ( HAL_DMA_PollForTransfer ( & hdma_memtomem_dma1_channel1 ,  HAL_DMA_FULL_TRANSFER ,  100 )  !=  HAL_OK )
{
  __NOP ();
}

printf("After DMA\n");
printBuffers();
/* USER CODE END 2 */

本来はDMAをしかけたら、CPUは転送のことは一端忘れて、他の仕事をするのです。しかし今回は実験なので、転送をしかけた(HAL_DMA_Start)後、転送が終わるのをHAL_DMA_PollForTransferで待ってます。そのビフォーアフターを先ほどのprintBuffers()で観察。

実機実験結果

以下はUSBシリアルに接続した仮想端末上での観察結果。DMAresult

BeforeのDstの時の値が、AfterではSrcの値で上書きされていますな。メモリ間DMAは正常に実施されておるようであります。

STM32三昧(3) Cube IDEでDAC出力、ソフトウエアトリガ、Nucleo へ戻る

STM32三昧(5) Cube IDEでTIMER6割り込み、Nucleo へ進む