鳥なき里のマイコン屋(145) ラズパイPico、C/C++SDKでDMAを使ってみる

Joseph Halfmoon

今回はラズパイPicoのDMAC(Direct Memory Access Controller)を使ってみるために、前回の母艦PCのArduino環境から母艦ラズパイ4のC/C++SDK環境に戻ってまいりました。とりあえず最低線ということでソフトウエアトリガのメモリ間転送をやってみたのですが、ラズパイPicoのSDK楽です(別シリーズでやったDMAC実験に比べ。)

※「鳥なき里のマイコン屋」投稿順Indexはこちら

(末尾に実験で使用した全ソースを掲げました。)

ラズパイPicoのRP2040と、Microchip ATSAMD51のDMAザックリ比較

別シリーズでやったDMAC実験は以下です。

IoT何をいまさら(99) ATSAMD51、DMAで1バイト転送成功までの長い道

Microchip社(買収されたAtmel社系列のMCU)のArm Cortex-M4搭載のSAMD51マイコンを対象に、SDKなど使わず、直接レジスタレベルでプログラミングしてDMAを使ってみた1件であります。今回ラズパイPicoではSDKで用意されているAPIを使っているので、直接のプログラミングに比べれば遥かにお楽なのは当然といえば当然。さらに言えば、

結構周辺回路が過激な行き方のRP2040、DMAに関しては保守的?

な感じがいたしました。PIOとか他のMCUとは一線を画する新機軸搭載のRP2040なのですが、DMAに関してはフツーな感じがいたします。それに比べるとSAMD51のDMACの方がモダンでゴージャスに思えます。その代わり設定がメンドイ。

ラズパイPicoのRP2040のDMAをかいつまむとこんな感じ

    1. DMAは12チャネル(SAMD51は32チャネル)
    2. DMAの転送元、転送先、転送回数などはチャネル毎のレジスタに設定(SAMD51はメモリ上のデスクリプタに設定)
    3. チャネル間でチェーン(リンク)することが可能(SAMD51はデスクリプタを使ってほぼほぼ「無限」チェーン可能)
    4. DMAのトリガは既定のペリフェラル、タイマ、ソフトウエア、外部がつかえる(SAMD51はイベントシステム接続により、ほぼ全てのペリフェラル等をトリガに使用可能)

まあ、SAMD51でDMAできたのだから、RP2040は赤子の手をひねるようなもんだな(本当か?)

なお、何時も参照させていただいておりますRaspberry Pi Picoのドキュメントですが、DMAに関しては以下に記載があります。

Raspberry Pi Pico SDK Documentation  hardware_dma

プロジェクトの設定

母艦ラズパイ4に戻ってまいりましたので、Cmake のプロジェクトを Raspberry Pi Pico Project Generator を使って生成したいと思います。GUIでの設定はこんな感じ。

picoProjDma生成した CMakeLists.txt を末尾に掲載いたしました。今回はジェネレータで生成したファイルに全く手を入れる必用がありませんでした。

プログラムの作成

例によって、パソコン上のVSCodeから、ラズパイ4にリモート接続してプログラムを作成しました。「課題」としては、

    • 16ワード(64バイト)のメモリ間転送
    • ソフトウエアトリガ
    • 使用するDMAチャネルは0番

です。作成したCのソース全文は末尾に掲げましたが短いものです。ざっくりした手順としては以下のとおりです。

    1. dma_channel_conig構造体の初期値をGET
    2. 構造体初期値で不足するところだけ設定
    3. テスト用にメモリを初期化(ソースに既知のパターン、デスティネーションはゼロ詰め)
    4. ソースアドレス、デスティネーションアドレス、転送回数をAPIで設定
    5. 設定した構造体を引数にとってDMA転送スタートさせる
    6. DMA転送終了をブロッキングで待つ
    7. デスティネーションメモリの内容を見る(パターンが転送されていればOK)

今回は非常にプリミティブな転送だったので、2の設定でデフォルトから変更したのは1か所だけです。メモリ間のブロック転送用への「味付け」、デスティネーションアドレスのインクリメント設定です。他は素直に並べていったら、動いてしまいました。SDK楽。

実験結果

以下に、VSCodeからデバッガを動かして動作させてみたところのキャプチャを掲げます。DMA転送終了後の場所にブレークをかけています。デスティネーションメモリの様子が左側のウオッチ式のところに見えているのが分かりますかね。DMA転送前にはゼロが並んでいたメモリにちゃんと予定通りのパターンが転送されとります。

DMA_VSCODE何か周辺回路でDMAしたくなったら使えそうかな?

鳥なき里のマイコン屋(144) ラズパイPico、Arduino環境で外付けRTC接続 へ戻る

鳥なき里のマイコン屋(146) ラズパイPico、C/C++SDK、マルチコアで割り込み1 へ進む

ビルドに使用した CMakeLists.txt
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# initalize pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/pi/pico/pico-sdk")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

project(dmaTst0 C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(dmaTst0 dmaTst0.c )

pico_set_program_name(dmaTst0 "dmaTst0")
pico_set_program_version(dmaTst0 "0.1")

pico_enable_stdio_uart(dmaTst0 0)
pico_enable_stdio_usb(dmaTst0 1)

# Add the standard library to the build
target_link_libraries(dmaTst0 pico_stdlib)

# Add any user requested libraries
target_link_libraries(dmaTst0
        hardware_dma
        )

pico_add_extra_outputs(dmaTst0)
DMA転送実験のCソース全文
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"

#define MEM_SIZE    (16)
#define DMACH       (0)

int counter = 0;

volatile uint32_t dstMemory[MEM_SIZE] __attribute__ (( aligned (4) ));
uint32_t srcMemory[MEM_SIZE] __attribute__ (( aligned (4) ));

void setupTestMemory() {
  for (int idx=0; idx < MEM_SIZE; idx++) {
    dstMemory[idx] = 0;
    srcMemory[idx] = idx + 100;
  }
}

void dumpMEMORY() {
  printf("Source memory: ");
  for (int idx=0; idx < MEM_SIZE; idx++) {
    printf("%d,",srcMemory[idx]);
  }
  printf("\n");
  printf("Destination memory: ");
  for (int idx=0; idx < MEM_SIZE; idx++) {
    printf("%d,",dstMemory[idx]);
  }
  printf("\n");
}

int main()
{
    stdio_init_all();
    puts("Pico DMA trial, v000");

    dma_channel_config conf0 = dma_channel_get_default_config(DMACH); // DMA channel 0 default configuration
    channel_config_set_write_increment(&conf0, true);

    for (int i=0; i<1000; i++) {
        printf("Counter: %d\n", ++counter);
        setupTestMemory();
        dma_channel_set_read_addr(DMACH, srcMemory, false);
        dma_channel_set_write_addr(DMACH, dstMemory, false);
        dma_channel_set_trans_count(DMACH, MEM_SIZE, false);
        dma_channel_set_config(DMACH, &conf0, true);
        dma_channel_wait_for_finish_blocking(DMACH);
        dumpMEMORY();
        sleep_ms(2000);
    }

    puts("End of Test.");
    return 0;
}