LoginSignup
0
1

Raspberry Pi Pico実機上でユニットテストを実行する

Last updated at Posted at 2024-04-13

手軽に使えるRaspberry Pi Picoの手軽さを維持しつつ、テストの使い心地(とても重要)のバランスを見極めるのがちょっと悩ましくて楽しかったので共有。

今回は永続化した循環バッファ(リングバッファ、サーキュラーバッファ)をPicoのオンボードフラッシュメモリに保持して、センサーデータを一定期間のあいだ蓄積するサンプルを書いていました。Picoオンボードの温度計を1秒間隔で記録して追記します。蓄積した最古から最新のセンサーデータを昇順の時系列データとして標準出力に出力します。

$ screen /dev/tty.usbmodem21302 115200
--------ASCENDING TIME SERIES
timestamp=30.3,temperature=29.95
timestamp=31.4,temperature=29.95
timestamp=32.5,temperature=29.95
timestamp=33.6,temperature=29.95
timestamp=34.7,temperature=30.42
timestamp=35.8,temperature=29.95
timestamp=36.8,temperature=29.95
timestamp=37.9,temperature=30.42
timestamp=39.0,temperature=29.95
--------ASCENDING TIME SERIES
timestamp=31.4,temperature=29.95
timestamp=32.5,temperature=29.95
timestamp=33.6,temperature=29.95
timestamp=34.7,temperature=30.42
timestamp=35.8,temperature=29.95
timestamp=36.8,temperature=29.95
timestamp=37.9,temperature=30.42
timestamp=39.0,temperature=29.95
timestamp=40.1,temperature=29.48

BOOTSELボタンを押してるあいだは最新から最古の降順で時系列データを出力します。

$ screen /dev/tty.usbmodem21302 115200
--------ASCENDING TIME SERIES
timestamp=150.2,temperature=30.42
timestamp=151.3,temperature=29.95
timestamp=152.4,temperature=30.42
timestamp=153.5,temperature=30.42
timestamp=154.5,temperature=29.95
timestamp=155.6,temperature=29.01
timestamp=156.7,temperature=30.42
timestamp=157.8,temperature=30.42
timestamp=158.9,temperature=30.42
--------DESCENDING TIME SERIES
timestamp=160.0,temperature=29.95
timestamp=158.9,temperature=30.42
timestamp=157.8,temperature=30.42
timestamp=156.7,temperature=30.42
timestamp=155.6,temperature=29.01
timestamp=154.5,temperature=29.95
timestamp=153.5,temperature=30.42
timestamp=152.4,temperature=30.42
timestamp=151.3,temperature=29.95

APIはシンプルに バッファの作成アイテム追加、バッファをトラバースする カーソルのopen, カーソルのアイテム取得 の4種類だけ。アイテムは任意サイズのデータを登録できます。

void cb_create(cb_t *cb, uint32_t address, size_t length, size_t item_size,
               timestamp_extractor_t get_timestamp, bool force_initialize);
void cb_append(cb_t *cb, const void *data, size_t size);
void cb_open_cursor(cb_t *cb, cb_cursor_t *cursor, cb_cursor_order_t order);
bool cb_get_next(cb_cursor_t *cursor, void *data);

二次記憶装置でうごく循環バッファを書いたことがなくていろいろ手間取ったのですが、これは網羅的に自動テストできる環境じゃないとやってられないなーと感じて用意することにしました。

Raspberry Pi Debug Probe

PicoのBOOTSELボタンを押しながらUSBに挿して、*.uf2ファイルをPicoのUSBメモリにドロップして...の手順は手軽なんですが、回数を重ねてくるとやってられなくなります。俺はmicro USBコネクタを抜き差しするマシンだうぉぉーん。そこでARM Serial Wire Debug (SWD)を使えるよう、Raspberry Pi Debug Probe1をPicoと開発マシン(macos)に接続します。

Raspberry Pi Debuf Probe

macosの開発マシンはHomebrewでクロスコンパイルする環境を用意しておきます。これでコマンドラインからプログラムの書き込み・再起動を自動化できます。接続や設定はPico Probeの商品ページ1とGetting started with Pico2のとおりです。接続といってもSWDのスルーホールにピンを差し込むだけなんですが。
SWDピンの接続
刺すだけ。

プログラムの書き込み・再起動はコマンド一つ。

$ openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg \
          -c "adapter speed 5000" -c "program my-project.elf verify reset exit"

ビルドするプログラムはCMakeLists.txtでUSBへの標準出力を有効にしておきます。

CMakeLists.txt
pico_enable_stdio_usb(my-project 1)

ちなみにDebug ProbeとPicoを開発マシンにUSB接続すると、ttyデバイス2つが現れます。

$ ls  /dev/tty.usbmodem* 
/dev/tty.usbmodem1101  /dev/tty.usbmodem21302

どっちがProbeでどっちがPicoかは分かりません。ボーレート 115,200bpsでそれぞれに繋いでみて確かめましょう。

$ screen /dev/tty.usbmodem21302 115200

ちなみにPicoの標準出力を開発マシンに繋ぐにあたり、

  • pico_enable_stdio_usb()している場合(Pico -(USB)-> macos)
  • Raspberry Pi Debug ProbeとPicoのUARTを接続している場合(Pico -(UART)-> Debug Probe -(USB)-> macos)

と2パターンあり得ますが、前者のUSB直は手軽で良いのですが、Picoを再起動した際にttyが切れてしまうので、後者の方がテストには都合が良いです。

テストコードの記述

テストコードはプロジェクトにtestsディレクトリを追加します。

$ ls tests
CMakeLists.txt test_create.c test_restore.c
main.c test_append.c test_cursor.c tests.h

個々のテストをtest_*.cファイルに記述します。
プロジェクト最上位のCMakeLists.txttestsディレクトリの情報を追加します。

CMakeLists.txt
add_subdirectory(tests EXCLUDE_FROM_ALL)

EXCLUDE_FROM_ALL オプションを付与して、プロジェクト本体のビルドには含まないようにします。tests/CMakeLists.txtはこんな感じ

tests/CMakeLists.txt
set(CMAKE_BUILD_TYPE Debug)

add_executable(tests
  ../circular_buffer.c
  main.c
  test_create.c
  test_append.c
  test_cursor.c
  test_restore.c
)

target_link_libraries(tests
  hardware_adc
  hardware_flash
  hardware_sync
  pico_stdlib
)

target_include_directories(tests
  PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}/../include
)
pico_add_extra_outputs(tests)
pico_enable_stdio_usb(tests 1)

tests/main.c はテストのエンドポイントになるコードです。test_*.cと一緒に一つのRaspberry Pi Pico用ファームウェアtests.elfとしてビルドします。テストのお作法については割愛。プロジェクトで作成したコードのAPIを操作して期待する状態になるか assert() して最後まで実行できたらprintf("ok")を出力するだけです。
pico-sdkの場合、tests/CMakeLists.txt

tests/CMakeLists.txt
set(CMAKE_BUILD_TYPE Debug)

を記述するか、cmake実行時に

$ cmake -DCMAKE_BUILD_TYPE=Debug ..

しないとassert()が有効にならないので注意。

カスタムターゲットの追加

あとはopenocdでテストプログラムを書き込んで標準出力を眺めるだけ...なのですが、openocdのコマンドライン引数は長すぎて覚えられません。なのでユニットテスト用のtests/CMakeLists.txtにカスタムターゲットrun_testsを追加します。

tests/CMakeLists.txt
add_custom_target(run_tests
  COMMAND ${OPENOCD} -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "program tests.elf verify reset exit"
  DEPENDS tests
)

CMakeLists.txtを編集したので、ビルド環境を作り直しましょう

$ rm -rf build
$ mkdir build
$ cd build
$ cmake ..

これであとは

$ make run_tests

するだけでテストをビルド・実行できるようになりました。今度こそテスト結果を眺めだけ。

$ screen /dev/tty.usbmodem21302 115200
Start all tests
create ...................ok
append ...................ok
cursor ...................ok
restore ..................ok
All tests are ok

標準出力を垂れ流しているだけで、終了コードなどはみていないので、CIで自動化して...とかはできません。必要ならttyの入力を監視するプログラムを走らせればいけそうですね。実際のテストコードはGithubのリポジトリを参照して下さい。特別なものは何も入れていません。

テストに失敗すると、

$ screen /dev/tty.usbmodem21302 115200
Start all tests
create ...................ok
append ...................ok
cursor ...................assertion "item.timestamp == (uint64_t)(0 + i)" failed: file "src/github.com/oyama/pico-persistent-circular-buffer/tests/test_cursor.c", line 42, function: test_cursor_descending

このような出力で失敗箇所がわかります。

ちなみに永続化循環バッファの実装は落ち度があって、フラッシュメモリの単一領域をerase/programしています。なのでeraseした瞬間にPicoが電源を失うとデータもぜんぶ失います。かわいいですね。 よりロバストでWear-Leveling機能付きなCircular Bufferを実装しました。
フラッシュメモリはデータの更新に消去・書き込みの2ステップが必要で、消去は4096バイト単位、書き込みは256バイト単位など非対称?なのが悩ましいところです。いにしえのHDDではなくフラッシュメモリに最適なデータ構造と操作アルゴリズムの勉強をしないとなーと強く思った次第です。

  1. Raspberry Pi Debug Probe 2

  2. Getting started with Pico

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1