ラズパイPico プロトコル・アナライザで通信プロトコルを可視化する

前回は３回にわたってラズパイPicoW を使った簡易オシロスコープについて紹介しました。
+ [第３回 ラズパイPicoオシロ 発振回路の矩形波を観測する (Qiita@pipito-yukio)](https://qiita.com/pipito-yukio/items/4a116b21fc908d1fb50f)
+  [第２回 ラズパイPicoオシロ 使用前の補正作業 (Qiita@pipito-yukio)](https://qiita.com/pipito-yukio/items/b0896e2e797354334d62)
+ [第１回 ラズパイPicoオシロ拡張基板で簡易オシロスコープを作る (Qiita@pipito-yukio)](https://qiita.com/pipito-yukio/items/dac1b6531623c550f8b7)

　**今回は下記書籍で提供されているプロトコル・アナライザとラズパイPicoを使い、通信プロトコルのSPIを可視化する方法を解説します。**

　下記書籍で提供されているソフトウェアと記事の内容は非常に良いものなのですが、残念なのは特定プロトコルを使った電子回路(プロトコルを制御するモジュールも含む)とラズパイPicoの接続の具体例が示されていないことです。特に読者が初心者の場合、このプロトコル・アナライザを利用するための機器の接続方法を見つけるのが困難であると推測されます。

　この投稿ではラズパイPico(W)、SPI部品を使った電子回路、電子回路を制御するマイコンモジュールの具体的な接続方法と制御プログラムについて解説します。

　**書籍に掲載されている内容 (操作方法含む) についてはこの投稿では解説できませんので予め書籍にてご確認ください。**

　この投稿で使用しているソフトウェアは下記書籍で提供されているPython GUIアプリケーションとラズパイPico用のファームウェアになります。

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/9903b10c-5067-e2b7-b54a-c8e128d589e5.jpeg">

**インターフェース 2024年3月号 「UART/I2C/SPIをPicoで ゼロから作るシリアル通信」**
+ 第１部　マイコンで必須の通信方式を理解しよう
    + 第２章 シリアル通信の基本技術…クロックとデータ同期
    + 第３章 定番シリアル通信…UART/SPI/I2C/CAN
+ 第３部　Picoで作る本格的プロトコル・アナライザ [ダウンロードサービス有り]
    + **第１章 読者に提供するプロトコル・アナライザの機能とGUI**
    + 第２章 定番シリアル通信…UART/SPI/I2C/CAN

### 検証環境

+ OS: Ubuntu 22.04
+ Python 3.9
    + プロトコル・アナライザアプリが指定するバージョンのPythonの仮想環境を作成
    ※仮想環境作成とライブラリのインストールは 下記項番 3 で別途説明します
+ ラズパイPicoWをUSBケーブルでPCに接続した時のUSBデバイス
**```/dev/ttyACM0```**
+ ホームユーザが dialout 権限グループに属していること
**```$ sudo usermod -aG dialout $USER```**

　以下の画面が Python GUI (Tkinter) アプリを起動し、SPI通信プロトコルのデータ交換をモニターした時の画面キャプチャになります。

![logicAnalyzer_2_measure_spi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/51db6778-2d2c-1429-a76d-fe824526aa7a.png)

### 1. SPI通信プロトコルを使用する部品の電子回路

#### 1-1. 接続回路図 (Fritzing)
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/898655c7-8e32-67f0-9211-621920e5cc99.png">

#### 1-2. ラズパイPicoと観測対象

+ ラズパイPicoW (写真: 左)
  ダウンロードサービスのファームウェアインストール済み
  ※インストール方法については書籍の第３部第１章を参照
    + GPIO12 <- MCP3002: CS (ESP-WROOM-02 CS[OUTPUT])
    + GPIO13 <- MCP3002: SCLK (ESP-WROOM-02 SCLK [OUTPUT])
    + GPIO14 <- MCP3002: MOSI (ESP-WROOM-02 MOSI [OUTPUT])
    + GPIO15 <- MCP3002: MISO (ESP-WROOM-02 MISO [INPUT])
+ 観測対象の電子回路 (写真: 中)
    + MCP3002 アナログコンバータ (SPI)とサーミスタ
※ プロトコル・アナライザアプリとの連携用に2つのLEDを追加
※ 2つのLEDの点灯制御はSPI通信プロトコルの制御に影響しない
+ 観測対象を制御するマイコンモジュール(写真: 右)
ESP-WROOM-02 (PlatformIO/Arduino: C++)

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/2cf0c1ca-69b4-fbc9-861a-4492c750f1a5.jpeg">

### 2. 制御モジュールのSPI制御プログラム

制御プログラムの詳細に関しては下記投稿記事をご覧ください。

[ESP-WROOM-02 サーミスタの温度をアナログコンバーターで取得する (Qiita@pipito-yukio)](https://qiita.com/pipito-yukio/items/9a1bff2c9333d7351f5a)

```
src/
├── SimpleMCP3002.cpp // MCP3002 SPI制御クラス
├── SimpleMCP3002.h
└── main.cpp         // この投稿記事用に簡略化した実装になっています
```

#### 2-1. MCP3002 SPI制御クラス

(1) クラスヘッダ

```SimpleMCP3002.h
#ifndef __SimpleMCP3002_h__
#define __SimpleMCP3002_h__
#include <Arduino.h>
#include <SPI.h>

union ReadData {
    uint16_t value;
    struct {
        uint8_t lowByte;
        uint8_t highByte;
    };
};

class SimpleMCP3002 {
public:
    // static const variables
    static const uint16_t RESOLUTION = 1024;
    static const int8_t INVALID_READ = -1; // Valid read: 0 - 1023
    // Constructor
    SimpleMCP3002(float vRef);
    // Destructor
    ~SimpleMCP3002(){};
    // Startup initialize
    void begin();
    // Public method
    uint16_t analogRead(uint8_t ch);
    // Utility method
    float getVolt(uint16_t adcVal);
    // DEBUG Use
    ReadData getReadData() { return mData; };

private:
    // member variable
    uint8_t mCsPin;
    float mVRef;
    // DEBUG Use
    ReadData mData;
};

#endif // __SimpleMCP3002_h__
```

(2) クラス実装

```SimpleMCP3002.cpp
#include "SimpleMCP3002.h"

// Constructor
SimpleMCP3002::SimpleMCP3002(float vRef) :
    mCsPin(PIN_SPI_SS),
    mVRef(vRef) {
}

void SimpleMCP3002::begin(void) {
    pinMode(mCsPin, OUTPUT);
    digitalWrite(mCsPin, HIGH);
    SPI.begin();
}

uint16_t SimpleMCP3002::analogRead(uint8_t ch) {
    //★★ ここから ★★
    // 特に SPISettingsの設定内容はプロトコル・アナライザのモニター設定同期させます
    SPI.beginTransaction(SPISettings(100000, MSBFIRST, SPI_MODE0)/*100kHz*/);
    digitalWrite(mCsPin, LOW);
    byte _highByte = SPI.transfer(0b01101000 | (ch << 4));
    byte _lowByte = SPI.transfer(0x00);
    digitalWrite(mCsPin, HIGH);
    SPI.endTransaction();
    //★★ ここまでがSPI通信プロトコルのモニター対象になります ★★
    // Read analog value.
    mData.highByte = _highByte & 0x03;
    mData.lowByte = _lowByte;
    // MCP3002: 0 - 1023
    if (mData.value >= 0 && mData.value < RESOLUTION) {
        return mData.value;
    }

    return SimpleMCP3002::INVALID_READ;
}

float SimpleMCP3002::getVolt(uint16_t adcVal) {
    return mVRef * adcVal / RESOLUTION;
}
```

#### 2-2. 電子回路制御のメイン処理

(1) インクルード、定数定義

```main.cpp
#include <Arduino.h>
#include "SimpleMCP3002.h"

#define PIN_LED_START 5 // GPIO5
#define PIN_LED_END 4  // GPIO4

// MCP3002: channel=0 [or 1]
const uint8_t MCP_CH = 0;
const float MCP_VREF = 3.3;
const unsigned long DELAY_TIME = 60 * 1000;
```

(2) MCP3002制御オブジェクトの生成

```cpp
SimpleMCP3002 mpc_adc(MCP_VREF);
```

(3) 初期化処理
+ LED制御ピン (観測開始[赤], 観測終了[青]) の初期設定
+ MCP3002制御オブジェクトの初期化


```cpp
void setup() {
   Serial.begin(9600);
   while (!Serial)
   Serial.println("START Measurement.");
   // LEDピン
   pinMode(PIN_LED_START, OUTPUT);
   pinMode(PIN_LED_END, OUTPUT);
   // Wait MCP3002
   mpc_adc.begin();
   delay(500);
}
```

(4) 指定したピンのLEDを1秒間点灯する関数

```cpp
void led_switch(uint8_t pinNum) {
   digitalWrite(pinNum, HIGH);
   delay(1000);
   digitalWrite(pinNum, LOW);
}
```

(5) 観測処理

60秒間隔で以下の処理を繰り返します。
※ 初回の処理はプロトコル・アナライザアプリ側で補足するのが困難ため繰り返します

+ 観測開始2秒前のLED(赤)を点灯する
※プロトコル・アナライザの [start] ボタンを押す契機とする
+ MCP3002 デバイスから測定値を取得しシリアルログに出力する
+ 観測終了の LED(青)を点灯する
※プロトコル・アナライザの [end] ボタンを押す契機とする

```cpp
void loop() {
   // 観測開始2秒前に赤LEDを点灯させる
   led_switch(PIN_LED_START);
   delay(1000);
   // Measure Thermister
   uint16_t adcValue = mpc_adc.analogRead(MCP_CH);
   ReadData readData = mpc_adc.getReadData();
   Serial.printf("readData: highByte = 0x%X, lowByte=0x%X\n", readData.highByte, readData.lowByte);
   Serial.printf("adcValue = %d\n", adcValue);
   // 観測終了の青LEDを点灯させる
   led_switch(PIN_LED_END);
   delay(DELAY_TIME);
}
```

### 3. プロトコル・アナライザ

　提供されているアーカイブ内の **```readme.txt```** に記載されている動作確認環境を下記に示します。

```
[動作確認環境]
  Windows11
    Python 3.8.5 (Anaconda推奨)

  Raspberry Pi 4 (32bit 2023-05-03版) ※1

  macOS Sonoma 14.1.1
    Python 3.11.6 ※2

[依存ライブラリ]
  pySerial
    インストールはコマンド  pip install pyserial
```

#### 3-1. Python仮想環境のセットアップ

検証環境のPCは Ubuntu 22.04 ですが最新の Python (3.10) では動作しませんでした。

```bash
(pico_analizer) logic_view$ python main.py 
Traceback (most recent call last):
  File "/mnt/vol01-lv/home/utils/Protocol_analizer/logic_view/main.py", line 1, in <module>
    import tkinter as tk
ModuleNotFoundError: No module named 'tkinter'

(py_pico_analizer) utils/Protocol_analizer/logic_view$ python -V
Python 3.10.12
```

　今回は Raspberry Pi 4 (64bit) の Pythonアプリ検証用にソースコードからビルドしたPython (**3.9**)を使用することにしました。※ソースコードからビルドする方法については下記記事をご覧ください。

[Ubuntu環境 Raspberry Pi のpythonアプリ用にpythonをソースコードからビルドする ](https://qiita.com/pipito-yukio/items/6aed846676a056363418)

プロトコル・アナライザアプリ専用のPython仮想環境を作成する。
+ python仮想環境名: pico_analyzer
+ ライブラリ: pyserial
※インストール時のコンソール出力は省略しています。

```bash
$ python3.9 -m venv pico_analyzer
$ . pico_analyzer/bin/activate
(pico_analyzer) $ pip install pyserial
(pico_analyzer) $ pip freeze
pyserial==3.5
```

#### 3-2. プロトコル・アナライザの起動

　ダウンロードしたアーカイブを解凍し、プロトコル・アナライザアプリ **```logic_view/```** ディレクトリを適切なディレクトリにコピーします。

(1) ファームウェアインストール済みのラズパイPicoと検証用PCをUSBケーブル接続する
(2) python仮想環境 (pico_analyzer) に入りメインスクリプトを実行する

```bash
(pico_analyzer) ~/utils/Protocol_analizer/logic_view$ python -V
Python 3.9.18
(pico_analyzer) ~/utils/Protocol_analizer/logic_view$ python main.py 
```

下記画面が起動直後の初期画面になります。

![logicAnalyzer_0_exec_main.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/1bd36f7a-990c-452a-181a-375de3710eae.jpeg)

##### 3-2-1. ラズパイPicoデバイスの設定

[Search USB] ボタンを押下しラズパイPicoデバイスを検索する。
※Linuxの場合は、**```/dev/ttyACM0```** が自動的に設定されます。

##### 3-2-2. SPI通信プロトコルの初期設定

(1) Protocol を **```SPI```** に変更する
(2) **MCP3002 制御クラスの実装に基づきSPIに関する該当箇所の設定を選択する**
(3) Line を **```MOSI/MISO```** に変更し **```MISO```** もモニター対象とする
　※**```SPI```** に切替えた時のデフォルトは **```MOSI```** で、 **```MISO```** はモニタ対象外です

![logicAnalyzer_1_select_spi_with_code.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/f703fa3c-9840-0699-8a56-1d3dc36a1949.jpeg)

##### 3-2-3. 通信プロトコルのモニター

**[モニター時間の制限] 最大150秒まで ※書籍に記載が有ります**

(1) 電子回路の観測開始LED(赤)が点灯したら **[start] ボタンを押下(モニター開始)**

![logicAnalyzer_2_set_spi_panel.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/a747020d-7cd3-3524-f30c-f586b35e1faf.jpeg)

(2) 電子回路の観測終了LED(青)が点灯したら **[stop]　ボタンを押下(モニター終了)**
(3) **[Update]ボタンを押下しモニター結果を表示する**

![logicAnalyzer_2_measure_spi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/d859675a-94fe-df60-31b4-12ff0bdee833.png)

[測定値の値] CH3 SPI: MISO

```
先頭バイト: 0x1, 2バイト目: 0xCC
```

(4) シリアルモニターで取得した値と上記画面の取得値を比較する

![VSCode_mon_mcp3002_serialMonitor.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/2acb5378-a485-2f4c-61c2-f9247c569fed.jpeg)


```
readData: highByte = 0x1, lowByte=0xCC
adcValue = 460
```

(5) MCP3002の仕様書と比較する

![MCP3002_specification_mode0_CS_low.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/89635b90-9529-a8db-4bff-e3d82d25740f.png)

モニター結果表示画面もほぼこの仕様書通りになっているのが確認できます。

### 最後に

　電子回路の部品を制御するプログラムをクラス化することによりマイコンのメイン処理のメンテナンスが容易になります。制御プログラムのクラス化にあたり必要になるのが上記(5)のような部品の仕様書。

　**通信プロトコルの可視化によりGPIOピン毎のデータ交換時の変化 (LOW/HIGH) と交換したデータの値が一目瞭然になりました。** これにより自作ライブラリを作成する場合、仕様通りに実装されているかの確認が容易になります。

　初心者の方にとっても各通信プロトコルの理解にもつながるのではないでしょうか。

　なお今回は手持ちのラズパイPico Wを使用しましたが、このプロトコル・アナライザはパソコンにラズパイPicoを常時USBで接続することを前提にしているので、より値段の安いラズパイPico (ピンヘッダ付き、USBケーブル付属で1,000円程) が使えます。