OctaveとArduinoを使ってモータを制御してみる

# はじめに

Octaveには、Arduino と通信して GPIO を HIGH / LOW したりするための [Octave Arduino Toolkit](https://octave.sourceforge.io/arduino/index.html) が用意されています。この記事では、OctaveとArduinoを使ってモータを制御するための、Octave Arduino Toolkit パッケージの使い方を取り扱います。

大まかな流れは以下の通りです。

|題目|内容|参考|
|---|---|---|
|導入編| パッケージをインストールする。<br>Arduinoに接続する。 | [Wiki](https://wiki.octave.org/Arduino_package) |
|基本編| LEDを光らせる。<br>電圧を読む。<br>PWMでモータを動かす。 | [Wiki](https://wiki.octave.org/Arduino_package)<br>[関数リファレンス](https://octave.sourceforge.io/arduino/overview.html)  |
|応用編| 割り込みで単相型エンコーダを読む。 | [マニュアル](https://octave.sourceforge.io/arduino/package_doc/index.html)<br>[関数リファレンス](https://octave.sourceforge.io/arduino/overview.html) |
|実践編| モータを制御する。 | |

# 環境
* DELL Vostro 1000
* Bodhi Linux 5.0.0
* Octave 4.2.2
* Octave Arduino Toolkit 0.6.0
* Arduino IDE 1.8.8
* Arduino Nano
* 制御対象物（LED、エンコーダ、モータ等）

# 導入編
使用する前に、パッケージを読み込みます。

```matlab:octave
>> pkg load arduino
```

パッケージがない場合は、インストールしてからパッケージを読み込みます。
（少々時間がかかります）

```matlab:octave
>> pkg install -forge arduino
>> pkg load arduino
```

Arduinoに、octave通信用のスケッチを書き込みます。
以下のコマンドを実行するとArduino IDEが立ち上がるので、IDE上で書き込みを実行します。
（一度書き込めばOK）

```matlab:octave
>> % スケッチ書き込み
>> arduinosetup
>> % IDEが起動しない場合は、IDEのインストールパスを指定して書き込み
>> arduinosetup('arduinobinary', '{InstallDir}/arduino')
```

arduinoをPCにUSB接続したのち、octaveからarduinoへ接続します。

```matlab:octave
>> % Arduinoと接続
>> a = arduino;
>> % 接続できない場合は、デバイスポートを指定してArduinoと接続
>> a = arduino("/dev/ttyUSB0");
```

arduinoが接続されているポートを以下の関数で調べることができます。（環境によっては、通信が上手くいかずに見つけられないことがあるようです。scanForArduinos関数の受信処理にpauseを挟むと上手くいきました。）

```matlab:octave
>> scanForArduinos
```

# 基本編（Lチカ、電圧計測、モータ駆動）

LEDを光らせます。

```matlab:octave
>> pkg load arduino % パッケージ読み込み
>> ar = arduino;    % arduino 接続
>> led_pin="d13";   % LED pin の設定
>> writeDigitalPin (ar, led_pin, 1); % LED点灯
>> writeDigitalPin (ar, led_pin, 0); % LED消灯
```

電圧を読みます。

```matlab:octave
>> pkg load arduino % パッケージ読み込み
>> ar = arduino;    % arduino 接続
>> sensor_pin="a4"; % sensor pin の設定
>> readAnalogPin(ar, sensor_pin) % 電圧の読み込み
```

モータドライバとモータを接続し、モータを動かします。（回路設計は割愛）

```matlab:octave
>> pkg load arduino % パッケージ読み込み
>> ar = arduino;    % arduino 接続
>> motor_pin="d5";  % motor pin の設定　※PWMに対応していること
>> writePWMDutyCycle(ar, motor_pin, 1.0); % モータ作動
>> writePWMDutyCycle(ar, motor_pin, 0.0); % モータ停止
```

この他にも便利な関数がたくさんあるため、いろいろ試してみてください。

| 主なライブラリ／アドオン | 主な機能 |
|---|---|
| i2cdev | I2C通信ライブラリ |
| servo | RCサーボモータ用ライブラリ |
| ultrasonic | 超音波センサ用ライブラリ |
| rotaryEncoder | ２相型ロータリエンコーダ用ライブラリ |
| ExampleAddon | アドオンのサンプル |
| adafruit | Adafruit Motor Shild v2向けアドオン |

# 応用編（割り込みによるエンコーダカウンタ）
割り込みで単相型エンコーダを読みこみます。

Octave Arduino Toolkit には、任意のスケッチをArduinoに仕込んで、Octaveから呼び出す「アドオン」の仕組みが用意されています。今回はそれを利用して、エンコーダに接続したポートを監視して、変化毎にカウントアップする関数を割り込ませるようにします。

はじめに、新たにアドオンを作成します。

```shell:shell
$ cd {InstallDir}/+arduinoaddons    # アドオンのフォルダに移動
$ mkdir +UserAddon                  # 新たなアドオンを作成
$ touch +UserAddon/SingleEncoder.h  # 新たなアドオンのArduino側プログラム作成
$ touch +UserAddon/SingleEncoder.m  # 新たなアドオンのOctave側プログラム作成
```

Arduino側プログラムを実装します。

```cpp:SingleEncoder.h

#include "LibraryBase.h"

#define ENC  2                  // エンコーダを接続するピン
#define CMDID_READ_COUNT 0x01   // Octaveと通信でやり取りするコマンドID

// 割り込み時に操作する変数
volatile bool pin = false;
volatile bool pin_old = false;
volatile unsigned long count = 0;

// 割り込み時に実行する関数
void readEncoder() {
  pin = digitalRead(ENC);
  if( pin == !pin_old ) count++;  // チャタリング防止
  pin_old = pin;
}

const char NO_NEED_VALUE[] PROGMEM = "No Needs a value to readCount";

class SingleEncoder : public LibraryBase
{
public:
  SingleEncoder(OctaveArduinoClass& a)
  {
    // アドオンの設定
    libName = "UserAddon/SingleEncoder"; // libNameは{フォルダ名}/{クラス名}
    a.registerLibrary(this);
    // 割り込みの設定
    attachInterrupt(digitalPinToInterrupt(ENC), readEncoder, CHANGE);
  }

  void commandHandler(uint8_t cmdID, uint8_t* data, uint8_t datasz)
  {
    unsigned long tmp;
    switch (cmdID)
    {
      case CMDID_READ_COUNT:
        // カウンタの読み込み
        if(datasz == 0){
          tmp = count; count = 0; // カウンタのリセット
          data[0] = (tmp>>24)&0xff;
          data[1] = (tmp>>16)&0xff;
          data[2] = (tmp>>8)&0xff;
          data[3] = (tmp)&0xff;
          datasz = 4;
          sendResponseMsg(cmdID, data, datasz); // カウンタの送信
        }else{
          sendErrorMsg_P(NO_NEED_VALUE); // エラーメッセージの送信
        }
        break;
        
      default:
        sendUnknownCmdIDMsg(); // エラーメッセージの送信
    }
  }
};
```

Octave側プログラムを実装します。

```matlab:SingleEncoder.m

classdef SingleEncoder < arduinoio.LibraryBase
  % プライベート変数
  properties(Access = private, Constant = true)
      CMDID_READ_COUNT = hex2dec("01");
  end
  % パブリック変数
  properties(Access = protected, Constant = true)
    % LibraryName は{フォルダ名}/{クラス名}
    % CppHeaderFile は{クラスヘッダファイル名}
    % CppClassname は{クラス名}
    LibraryName = 'UserAddon/SingleEncoder';
    DependentLibraries = {};
    ArduinoLibraryHeaderFiles = {};
    CppHeaderFile = fullfile(arduinoio.FilePath(mfilename('fullpath')), 'SingleEncoder.h');
    CppClassName = 'SingleEncoder';
  end
  % クラスメソッド
  methods
    % コンストラクタ
    function obj = SingleEncoder(parentObj)
      obj.Parent = parentObj;
      obj.Pins = [];
    end
    % カウンタの読み込み
    function count = readEncCount(obj)
      cmdID = obj.CMDID_READ_COUNT;
      [tmp, sz] = sendCommand(obj.Parent, obj.LibraryName, cmdID, []);
      count = uint32(tmp(1))*(256*256*256) + uint32(tmp(2))*(256*256) + uint32(tmp(3))*256 + uint32(tmp(4));
    end

  end
end
```

Arduinoに、パッケージ＋アドオンのスケッチを書き込みます。
（Arduino側プログラムを変更するたびに実行します）

```matlab:octave
>> % パッケージ読み込み
>> pkg load arduino
>> % アドオンを指定してスケッチ書き込み
>> arduinosetup('libraries', 'UserAddon/SingleEncoder')
>> % IDEが起動しない場合は、IDEのインストールパスとアドオンを指定して書き込み
>> arduinosetup('arduinobinary', '{InstallDir}/arduino', 'libraries', 'UserAddon/SingleEncoder')
```

エンコーダを接続し、適当に回した後、カウンタを読み込みます。（回路設計は割愛）

```matlab:octave

>> ar = arduino([], [], 'libraries', 'UserAddon/SingleEncoder', 'forcebuild', true); % arduino 接続
>> en = addon(ar, "UserAddon/SingleEncoder");                                        % addon   接続
>> readEncCount(en)                                                                  % カウンタ読み込み
```

# 実践編（モータの制御）
モータを制御します。

単なるループだと、一定間隔でコマンドを送ることができないため、pauseを利用します。

```matlab:main.m
function main
    % パッケージをロード
    pkg load arduino
    % ArduinoにOctave通信用のコアライブラリと自作ライブラリを書き込み
    arduinosetup('libraries', 'UserAddon/SingleEncoder')
    % Arduinoと接続
    ar=arduino([], [], 'libraries', 'UserAddon/SingleEncoder', 'forcebuild', true);
    % Addonと接続
    en=addon(ar, "UserAddon/SingleEncoder");

    % 演算周期
    dt = 0.1;

    % 制御対象を初期化
    ENC = "d5";
    writePWMDutyCycle(ar, ENC, 0);

    unwind_protect
        % 制御ループ
        while(true)
            tic
            % エンコーダのカウンタを読み込み
            cnt = readEncCount(en);
            % カウンタよりモータへの入力値を演算
            u   = control(cnt);
            % モータへの入力値をコマンド送信
            writePWMDutyCycle(ar, ENC, u);
            % 残り時間は待機
            pause(dt-toc)
        end
    unwind_protect_cleanup
        % 終了処理
        writePWMDutyCycle(ar, ENC, 0);
        clear en
        clear ar
    end_unwind_protect
end

function u = control(cnt)
    u=1.0;
    % ここに制御プログラムを実装する
end
```

# おわりに
Octave Arduino Toolkit により、容易にArduinoを操作できるため、制御設計が非常にやりやすくなります。

Octave Arduino Toolkit と同様なパッケージはMATLABにもあり、（というかMATLABにはだいぶ前からありました）そちらの方が容易に同じことができるかもしれません。Octaveの良いところは、オープンソースなため拡張性に優れるところです。上記のプログラムも、パッケージのソースコードを参考にしながら作ることができました。

一方で、当然のことながら、OctaveにしろMATLABにしろ、Arduinoに無駄な処理負荷及び通信負荷をかけることになるため、短い演算周期で制御ループを回すことはできません。（筆者の環境で100msが限界でした。）開発初期の簡単なテストなどに使うのが良いと思いました。

# 参考文献

* [Octave Arduino Toolkit Online Manual](https://octave.sourceforge.io/arduino/package_doc/index.html)
* [Octave Wiki 'Octave Arduino Toolkit'](https://wiki.octave.org/Arduino_package)
* [Octave Forge 'Octave Arduino Toolkit'](https://octave.sourceforge.io/arduino/index.html)
* [ArduinoとMATLABで制御系設計をはじめよう！](https://jp.mathworks.com/academia/books/book102451.html)