# TM1637 PIO Driver

Raspberry Pi Pico (RP2040/RP2350) の PIO (Programmable I/O) で[TM1637](https://akizukidenshi.com/catalog/g/g113224/)を使った7セグメントディスプレイを制御するライブラリです。TM1637は単体で秋月電子通商から買えるほか、AmazonやAliexpressからTM1637を使った7セグメントLEDディスプレイモジュールが安価に入手できます。

TM1637の詳細に関しては秋月にあるデータシートや、ネット上の参考資料を見てもらえばいいと思いますが、I2Cもどきの2線式シリアルで複数桁の7セグメントLEDを制御できるICです。GPIOで制御している作例が多いですが、PIOで制御することでCPUを低速なシリアル通信から解放できます。このライブラリでは、PIOのFIFOを連結して8エントリとしているので、よくある4桁モジュールならCPUがブロックされずに表示を行うことができます。

## 特徴

- **PIO駆動**: 通信プロトコルをPIOで処理するため、CPU負荷が低く、正確なタイミングで動作します。
- **マルチインスタンス**: 複数のディスプレイを異なるピンで同時に制御可能です。
- **CMake対応**: `add_subdirectory` するだけで簡単にプロジェクトに組み込めます。

## 概説

TM1637はI2C風のインタフェースを通じてホストと通信します。Start Conditionでトランザクションを開始し、Stop Conditionでトランザクションを終了する点や、1バイトごとのACKはI2Cと同じです。I2Cとのおもな違いは次の2点です。

 - 下位ビットから送出（LSB First）。I2CはMSB FirstですがTM1637は逆に下位ビットから順に送受信します。これがI2Cとのもっとも大きな違いです
 - アドレスを持たない。1つのバスに1つのTM1637しか接続できません

このライブラリでは、I2CライクなTM1637のホスト→TM1637のプロトコルをPIOで実装しています。次のように値bをpushするとStart Conditionに続いて1バイトをTM1637に送信します。

```.c
pio_sm_put_blocking(pio, sm, b);
```

値の9bit目をオンにすると、送信後にStop Conditionにバスを遷移させ、バスを解放します。

```.c
pio_sm_put_blocking(pio, sm, b | STOP_COND);
```

SCLKをSDA（TM1637の端子名はDIO）の隣のGPIOに固定しているのは、PIO命令setを使って2つのGPIOの入出力を切り替えていて、STOP Condition後にSDAとSCLKを入力に切り替えて高インピーダンス状態にしバスを解放する必要があるからです。

`tm1637out.pio`に実装しているPIOコードでは、TM1637のI2C風プロトコルの大部分をデータシートに沿って実装しています。I2C的な通信を行うPIOコード例として活用してください。

なお、PIOコードではACKをチェックしてNACKを検出したらステートマシン番号のIRQフラグをオンにしていますが、Cコード側の割り込み処理は行っていません。テストしてみた限りNACKが生じることがなさそうであることに加えて、仮にNACKが検出されても対応のしようがない（通信エラーが起きたことがわかるだけ）からです。マルチインスタンス化していることもあり、NACKのための割り込み処理を追加するとかなり複雑になりますが、複雑にするだけの価値が見いだせないので、NACKに対応する処理は削除しました。もしに気なるようならIRQフラグをチェックするコードを追加すればいいでしょう。

## ポータブルなライブラリを作成する

書籍には記載していなかったので、ここで簡単に使い回しができるライブラリの自作方法を、`tm1637_lib`を例に説明しておきます。

まず、自分のプロジェクトにライブラリ名のサブディレクトリを作成します。本例では`tm1637_lib`ですね。

```
your_project/
├── tm1637_lib/          # ライブラリフォルダ
│   ├── CMakeLists.txt
│   ├── TM1637.c
│   ├── TM1637.h
│   └── tm1637out.pio
├── CMakeLists.txt       # 親プロジェクトのCMakeファイル
└── main.c               # アプリケーションメインコード
```

サブディレクトリの下にライブラリ用の[CMakeLists.txt](https://future.quake4.jp/gogs/yoneda/C_Book_Examples/src/master/TM1637_pio/tm1637_lib/CMakeLists.txt)を作成します。`tm1637_lib`ではPIOを使っているので、`pico_generate_pio_header`もライブラリ用のCMakeLists.txtに書いておきます。

```.cmake
# ライブラリのターゲットを作成 (STATICライブラリ)
add_library(tm1637_lib STATIC
    TM1637.c
    tm1637out.pio
)

# PIOヘッダの生成
pico_generate_pio_header(tm1637_lib ${CMAKE_CURRENT_LIST_DIR}/tm1637out.pio)

# ヘッダファイルのパスを公開 (このライブラリを使う側が include できるようにする)
target_include_directories(tm1637_lib PUBLIC ${CMAKE_CURRENT_LIST_DIR})

# ライブラリが必要とする依存関係
target_link_libraries(tm1637_lib PUBLIC
    pico_stdlib
    hardware_pio
)
```

そしてプロジェクトメインのCMakeLists.txtに次のように`add_subdirectory`を追加し、`target_link_libraries`にライブラリ名を追加します。

```cmake
   ...
   add_subdirectory(tm1637_lib)
   ...
   target_link_libraries(your_executable
   pico_stdlib
   tm1637_lib     # これを追加
   ...)
   ...
```

これでポータブルなライブラリが作成できました。以後、プロジェクトディレクトリの下にライブラリ名（ここでは`tm1637_lib`）ディレクトリごとコピーして、メインのCMakeLists.txtに`add_subdirectory`と`target_link_libraries`を追加すれば、自作ライブラリを今後のプロジェクトに使い回すことができます。

## tm1637_libのサンプルコード

```c
   #include "TM1637.h"

   // 初期化 (SDAピン番号, 桁数, 輝度0-7)
   // SCLピンは SDA + 1 となります
   TM1637_t *tm = TM1637_init(4, 4, 4);

   if (tm != NULL) {
       TM1637_putstr(tm, "12:34");
   }
```

## API リファレンス

 *`TM1637_t *TM1637_init(uint8_t sda_base_pin, uint8_t col, uint8_t cont)`*

TM1637を初期化し成功すればハンドルを返す

 **パラメータ**
 - **`uint8_t sda_base_pin`**: SDA（DIO）を接続しているGPIO番号。PIOコードの制約によりsda_base_pin+1のGPIOにSCLKを接続してください。
 - **`uint8_t col`**: 接続しているモジュールのカラム数。TM1637は最大6カラムの7セグメントLEDを制御できます。
 - **`uint8_t cont`**: コントラスト値。0（暗）～7（明）。

 **戻り値**

 - 初期化に成功するとハンドル（TM1638_tのポインタ）を返す。NULLは失敗。

 *`int TM1637_set_contrast(TM1637_t *p, uint8_t cont)`*

コントラスト値を設定する

 **パラメータ**

  - **`TM1638_t *p`**: ハンドル
  - **`uint8_t cont`**: コントラスト値。0（暗）～7（明）

 **戻り値**

  - 成功したら0

 *`int TM1637_putchar(TM1637_t *p, char c, bool dot, uint8_t col)`*

文字を表示

 **パラメータ**

 - **`TM1637_t *p`**: ハンドル
 - **`char c`**: 表示する文字コード
 - **`bool dot`**: ドットセグメントをオンにするならtrue
 - **`uint8_t col`**: 表示するカラム

 **戻り値**

 - 成功したら0

 *`int TM1637_putstr(TM1637_t *t, char *str)`*

文字列を表示する

 **パラメータ**

 - **`TM1637_t *p`**: ハンドル
 - **`char *str`**: 表示する文字列。次の文字にピリオドまたはコロンを挿入すると、その文字のドットセグメントがオンになります。ドットセグメントの形状及び位置は製品によって異なります。ピリオド（.）かコロン（:）が一般的です。

 **戻り値**

  - 成功したら0

