Raspberry Pico: カスタム C-SDK ライブラリの作成 (パート2) - DEV コミュニティ

Raspberry Pico: カスタム C-SDK ライブラリの作成を表す画像

Githubからシフトレジスタライブラリをダウンロード: https://github.com/admantium-sg/rp2040-shift-register-74HC595

Raspberry Pi Pico(略してPico)は、Raspberry Pi財団からの新しいマイクロコントローラです。デュアルコアARMプロセッサ、2MBのフラッシュメモリ、26個のGPIOピンを提供しています。PicoはC/C++ SDKまたはMicroPythonでプログラムすることができます。私はこのデバイスに魅了され、新しいプロジェクトを始めました。それはHC595Nシフトレジスタを接続し制御するためのライブラリです。

前の記事では、シフトレジスタ操作の基本と、特にHC595Nがどのように機能するかを説明しました。記事は、ライブラリのコアオブジェクトや機能の説明で終わりました。

この記事では、ライブラリ開発を続け、私のアプローチ、開発ステップ、最終結果について詳しく記述します。

この記事は元々 私のブログ に掲載されました。

開発アプローチ

HC595Nライブラリは私の最初の埋め込みソフトウェアオープンソースプロジェクトで、まだ慣れていないC言語に関するものです。以前のプロジェクトでの経験が、このライブラリで何を実現したいのか、そしてどうやって開発するべきかを形作りました。

重要なことは何ですか?私は、HC595Nライブラリで必要な操作を捉えたライブラリを提供したいと思います。ライブラリのオブジェクトやその機能、すなわちAPIはクリアで使いやすく、追加の説明文書が全ての質問に答えるべきです。C言語の開発標準およびコード構造の原則に従うべきです。そして最後に、品質保証としての単体テストを提供し、機能拡張やリファクタリングのためのしっかりとした基盤を提供するべきです。

これを念頭に置いて、私は以下のアプローチに従うことにしました:

  • 最初の機能に必要なすべてのコードを含む単一のCファイルを書き、意味のある名前のオブジェクトや関数を提供することを念頭に置きます。
  • ソフトウェアテスト:この初期機能セットを完全にカバーする単体テストを追加します。
  • ハードウェアテスト:マイクロコントローラ、シフトレジスタ、出力デバイス(LEDなど)を搭載したブレッドボードを組み立て、コードをテストします。
  • 新しい関数を書く前に最初に単体テストを記述することによってテスト駆動開発に移行し、すべての基本機能を最終化します。
  • 良いAPIデザインガイドラインとCコーディング標準に適応します:プロジェクトをヘッダー、実装、およびテストファイルに分割し、新しいオブジェクトとライブラリ機能の作成方法を提供します(オブジェクト固有のものとグローバルの両方)。
  • Cコーディング標準にプロジェクトを構造化します:ヘッダーファイル、実装ファイル、テストファイルがあります。さらに、ライブラリをアップグレードします。
  • ライブラリを特定のハードウェアから独立してテストするために、すべてのハードウェア関連の機能にモックまたはスタブを提供します。
  • コミュニティで受け入れられているハードウェア固有の標準コンパイラスタックを追加します。

では、これらのステップが実践でどのように実現されるか見てみましょう。

基本機能:1ビットの書き込み

ShiftRegisterは、必要な全てのピンと2つの状態serial_pin_stateおよびshift_register_stateを定義する単純なStructオブジェクトです。

typedef struct ShiftRegister
{
  u_int8_t SERIAL_PIN;
  u_int8_t SHIFT_REGISTER_CLOCK_PIN;
  u_int8_t STORAGE_REGISTER_CLOCK_PIN;

  bool serial_pin_state;
  u_int8_t shift_register_state;
} ShiftRegister;

ShiftRegisterオブジェクトは、それぞれのピンにマップされる複合リテラルで初期化することができます。

ShiftRegister reg = {
.SERIAL_PIN=14,
.SHIFT_REGISTER_CLOCK_PIN=11,
.STORAGE_REGISTER_CLOCK_PIN=12
};

このシフトレジスタが作成されたら、今度は単一のビットを書き込む必要があります。Cにはオブジェクトが存在しないので、通常はオブジェクトへのポインタと引数を受け取る関数を定義します。

この関数が呼ばれると、serial_pin_statebool型の引数に設定します。その後、register_stateを変更します。渡されたboolがtrueであれば、現在のレジスタ値に2を加算します。0であれば、全ビットを1つ右にシフトします。

static bool write_bit(ShiftRegister *reg, bool b)
{
  reg->serial_pin_state = b;
  (b) ? (reg->register_state += 0b10) : (reg->register_state <<= 0b01);
  return true;
}

テストケースでは、1に続いて0の2ビットを書き込みます。各ステップ後にpin_stateが正しく設定されているかどうかをテストします。詳細は私のCMockaを使ったテストという記事で見ることができます。

void test_write_bit(void **state)
{
  ShiftRegister reg = {14, 11, 12};
  write_bit(1, &reg);
  assert_int_equal(reg.serial_pin_state, 1);
  write_bit(0, &reg);
  assert_int_equal(reg.serial_pin_state, 0);
}

ハードウェアテスト

ソフトウェアテストが実行されたら、ハードウェア関連機能を実装する時です。この記事を書いている時点で、私はマイクロコントローラライブラリの開発を始めたばかりでしたので、まだ最善のアプローチを知りませんでした。

HC595Nのデータシートの詳細によると、新しいデータはクロックピンが1サイクル高くなったときにシフトレジスタに書き込まれます。シリアルピンの状態が高いか低いかにかかわらず、シフトレジスタに書き込まれます。これは実際には、ピンにデータを書き込むステートメントで既存のコードを取り囲む必要があることを意味します。

これは次のようになります。

static bool _write_bit(ShiftRegister *reg, bool b)
{
  gpio_put(reg->SERIAL_PIN, b);
  gpio_put(reg->SHIFT_REGISTER_CLOCK_PIN, 1);
  reg->serial_pin_state = b;
  (b) ? (reg->register_state += 0b10) : (reg->register_state <<= 0b01);
  gpio_put(reg->SHIFT_REGISTER_CLOCK_PIN, 0);
  gpio_put(reg->SERIAL_PIN, 0);
  return true;
}

3行目では与えられたbool値をシリアルピンに書き込み、3行目ではシフトレジスタクロックピンを1に設定します。7行目と8行目で両方のピンを0に戻します。

この追加で、コードをアップロードし、8個のLEDを順に点灯させてから再び消す小さな例を書きました。適切にテストされた回路設計でブレッドボード上に見ることができるのは次のような感じです。

8個のLEDが点滅する様子を示す画像

構造リファクタリング

これまで、単一ビットを書き込むための基本機能が実装されています。それらは単体テストと手動のハードウェアテストでサポートされています。

これにより、ライブラリの開発を続けることができます。しかし、まずやるべきことは現在のコードベースを再構成することです:includeフォルダ内の適切なヘッダーファイル、src内の実装、およびexamplestestの別ファイルです。それは次のように見えます:

├── examples
│   ├── 8_led_blink.c
├── include
│   └── admantium
│       └── rp2040_shift_register.h
├── src
│   ├── CMakeLists.txt
│   └── rp2040_shift_register.c
└── test
    └── test.c
...

新しい機能の拡張

構造が作られたら、新しいライブラリ機能を追加することに集中しました。各機能は同じアプローチで開発されました:

  • ヘッダーファイルに定義を追加します。
  • 新しいテストを追加します。
  • 実装を提供します。
  • テスト&デバッグします。

短い時間で、次の機能を完成させました:

  • write_bit(ShiftRegister *, bool): シフトレジスタに単一ビットを書き込み、他の全ビットを右にシフトします。
  • write_bitmask(ShiftRegister *, u_int8_t): 完全なビットマスクを、たとえば 0b10101010 シフトレジスタに書き込みます。
  • flush(ShiftRegister *): シフトレジスタの内容をストレージレジスタにフラッシュします。
  • reset(ShiftRegister *): シフトレジスタの内容をビットマスク0b00000000にリセットします。
  • reset_storage(ShiftRegister *): ストレージレジスタの内容をビットマスク0b00000000にリセットし、shift_register_flush()を実行します。
  • char * shift_register_print(ShiftRegister \*): シフトレジスタの状態をビットマスクとして印刷し、ビットマスク文字列のchar*を返します。

Pico SDKなしでユニットテストを実行する

ユニットテストを動作させるためには、あるコツを使う必要がありました。CMockaライブラリはGCC ARMクロスコンパイラと互換性がないので、プログラムはデフォルトのCコンパイラでコンパイルする必要があり、それにはPico SDKの関数を使用できません。したがって、Pico SDKまたはモック関数のセットをロードするために、プリプロセッサフラグを含めました。

#ifndef TEST_BUILD
#include <pico/stdlib.h>
#endif
#ifdef TEST_BUILD
#include <../test/mocks.h>
#endif

モック関数は単純な空の関数です。

void stdio_init_all() { // 何もしません }
void gpio_init(uint8_t gpio) { // 何もしません }
static void gpio_set_dir(uint8_t gpio, bool out) { // 何もしません }
static void gpio_put(uint8_t gpio, bool value) { // 何もしません }

注意:CMockaは関数コールをモックとしてラッピングするメカニズムを提供しています。これには --wrap <symbol> コールで動作するリンカが必要です。例えば、gpio_put関数をラップするために -Wl,--wrap=gpio_put でコンパイルすることができます。しかし、ライブラリを実装している時に私のCツールチェーンの知識が限られていたので、これを動作させることができませんでした。

APIリファクタリング

ライブラリは既に良い形になっています。それでは、APIを作成しましょう。私のC APIデザインの参考資料は、[21世

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/admantium/raspberry-pico-designing-a-custom-c-sdk-library-part-2-4ihg