はじめに
Octaveには、Arduino と通信して GPIO を HIGH / LOW したりするための Octave Arduino Toolkit が用意されています。この記事では、OctaveとArduinoを使ってモータを制御するための、Octave Arduino Toolkit パッケージの使い方を取り扱います。
大まかな流れは以下の通りです。
題目 | 内容 | 参考 |
---|---|---|
導入編 | パッケージをインストールする。 Arduinoに接続する。 |
Wiki |
基本編 | LEDを光らせる。 電圧を読む。 PWMでモータを動かす。 |
Wiki 関数リファレンス |
応用編 | 割り込みで単相型エンコーダを読む。 |
マニュアル 関数リファレンス |
実践編 | モータを制御する。 |
環境
- 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、エンコーダ、モータ等)
導入編
使用する前に、パッケージを読み込みます。
>> pkg load arduino
パッケージがない場合は、インストールしてからパッケージを読み込みます。
(少々時間がかかります)
>> pkg install -forge arduino
>> pkg load arduino
Arduinoに、octave通信用のスケッチを書き込みます。
以下のコマンドを実行するとArduino IDEが立ち上がるので、IDE上で書き込みを実行します。
(一度書き込めばOK)
>> % スケッチ書き込み
>> arduinosetup
>> % IDEが起動しない場合は、IDEのインストールパスを指定して書き込み
>> arduinosetup('arduinobinary', '{InstallDir}/arduino')
arduinoをPCにUSB接続したのち、octaveからarduinoへ接続します。
>> % Arduinoと接続
>> a = arduino;
>> % 接続できない場合は、デバイスポートを指定してArduinoと接続
>> a = arduino("/dev/ttyUSB0");
arduinoが接続されているポートを以下の関数で調べることができます。(環境によっては、通信が上手くいかずに見つけられないことがあるようです。scanForArduinos関数の受信処理にpauseを挟むと上手くいきました。)
>> scanForArduinos
基本編(Lチカ、電圧計測、モータ駆動)
LEDを光らせます。
>> pkg load arduino % パッケージ読み込み
>> ar = arduino; % arduino 接続
>> led_pin="d13"; % LED pin の設定
>> writeDigitalPin (ar, led_pin, 1); % LED点灯
>> writeDigitalPin (ar, led_pin, 0); % LED消灯
電圧を読みます。
>> pkg load arduino % パッケージ読み込み
>> ar = arduino; % arduino 接続
>> sensor_pin="a4"; % sensor pin の設定
>> readAnalogPin(ar, sensor_pin) % 電圧の読み込み
モータドライバとモータを接続し、モータを動かします。(回路設計は割愛)
>> 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 | 2相型ロータリエンコーダ用ライブラリ |
ExampleAddon | アドオンのサンプル |
adafruit | Adafruit Motor Shild v2向けアドオン |
応用編(割り込みによるエンコーダカウンタ)
割り込みで単相型エンコーダを読みこみます。
Octave Arduino Toolkit には、任意のスケッチをArduinoに仕込んで、Octaveから呼び出す「アドオン」の仕組みが用意されています。今回はそれを利用して、エンコーダに接続したポートを監視して、変化毎にカウントアップする関数を割り込ませるようにします。
はじめに、新たにアドオンを作成します。
$ cd {InstallDir}/+arduinoaddons # アドオンのフォルダに移動
$ mkdir +UserAddon # 新たなアドオンを作成
$ touch +UserAddon/SingleEncoder.h # 新たなアドオンのArduino側プログラム作成
$ touch +UserAddon/SingleEncoder.m # 新たなアドオンのOctave側プログラム作成
Arduino側プログラムを実装します。
#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側プログラムを実装します。
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側プログラムを変更するたびに実行します)
>> % パッケージ読み込み
>> pkg load arduino
>> % アドオンを指定してスケッチ書き込み
>> arduinosetup('libraries', 'UserAddon/SingleEncoder')
>> % IDEが起動しない場合は、IDEのインストールパスとアドオンを指定して書き込み
>> arduinosetup('arduinobinary', '{InstallDir}/arduino', 'libraries', 'UserAddon/SingleEncoder')
エンコーダを接続し、適当に回した後、カウンタを読み込みます。(回路設計は割愛)
>> ar = arduino([], [], 'libraries', 'UserAddon/SingleEncoder', 'forcebuild', true); % arduino 接続
>> en = addon(ar, "UserAddon/SingleEncoder"); % addon 接続
>> readEncCount(en) % カウンタ読み込み
実践編(モータの制御)
モータを制御します。
単なるループだと、一定間隔でコマンドを送ることができないため、pauseを利用します。
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が限界でした。)開発初期の簡単なテストなどに使うのが良いと思いました。