第二十四項 無線制御

電子工作創作表現(2019/01/17)

スライドPDF

Wi-FiやBTだけではない無線

  • Wi-FiやBluetoothなどは安く便利だが、混線のリスクが高い
     
  • 他の無線で通信する方法
以前ESP-WROOM-02でWi-Fi接続ができるという話をしました。
Wi-Fiは既製品のルーターが使えるなど環境構築自体が安価で簡単になっていますが、出力もそこまで強くなく、一般的に使われている電波なので混線のリスクが高いです。
展示会や舞台など大勢の人が携帯電話やポケットWi-Fiを持って入る空間では特に混線のリスクが高まるため、信頼性が求められる状況では避けることが多いです。
今回は無線通信の基本から、Wi-FiやBluetooth以外の通信方式の紹介をしていきます。

無線通信で気にするべき要素

  • 伝送距離
     
  • 通信速度
     
  • 通信品質(繋がりやすさ)
有線を使わず無線と使う時というのは、ケーブルの取り回しが難しいという状況だと考えられます。まずどのくらいの距離まで届くのか。一般的にデータシート等に書かれている通信距離というのは何もない空間での理想的な環境条件における距離なので、実際に使いたい環境で届くのか試す必要があります。
やりとりする内容によっては通信速度も鍵になってきます。周波数が高いほど通信速度を上げることが可能になりますが、障害物などに弱くなってしまう傾向があり、繋がりやすさ=通信品質に影響してきます。

無線の特性を左右する「周波数」

  • 電波を使う無線通信は、周波数で住み分けをしている。単位は「Hz」
     
  • 船舶航空などは110~150MHzの低周波、BluetoothやWi-Fiは2.4GHzの高周波
これらの特性は、電波を発する時の周波数によって左右されることが多いです。単位は音と同じHzです。
船舶や航空機のように、公共の場で広く使われる周波数は110~150MHzのような低周波、逆に我々が使う無線は800MHz以上の高周波帯がよく用いられます。BluetoothやWi-Fiは2.4GHzがよく使われています。

電波と法律

  • ある機器がその周波数で電波を送信と、他の機器は同じ周波数を使うことが出来ない
     
  • 周波数や出力に法律規制がある。日本はかなり厳しめ
     
  • 海外のデバイスは「技適」があるか要確認
先ほど混線の話をしましたが、電波というのは同じ周波数を複数の通信で使うことはできません。そのため、誰かが勝手に使いだすとその電波が届く範囲の人たちは同じ周波数を使えなくなってしまうので、乱用を防ぐため周波数の使い道や出して良い強さが法律で定められています。船舶や航空無線のような帯域を扱うためには免許も必要です。

免許が不要な装置の場合でも、日本国内で電波を発する装置を使用するには、そのデバイスに対して総務省の許可が必要となります。これが「技適」と呼ばれるもので、このマークが無い装置の使用は基本的にNGです。最近の例で言うと、Raspberry pi4が発売されたのは2019年の6月でしたが、技適が登録され無事使えるようになったのは同11月からとなっています。

ただ最近法改正があり、技適の無いマークでも届け出をすれば実験目的で180日間に限り電波を発射することができるようにもなっているそうです。

電子工作向けの無線デバイス

  • 電子工作向けの無線デバイスがいくつか存在
     
  • 免許不要の「特定小電力無線局」
     
  • 2.4GHz帯か900MHz帯が多い
ということをふまえて、電子工作で扱う事の出来る無線デバイスをいくつか紹介します。
特定小電力無線局に該当する2.4GHz帯と、サブギガ帯と呼ばれる920MHz帯の2種類です。

無線モジュールのメリット

  • チャンネル等を細かく設定して干渉をある程度防げる
     
  • 「マルチホップ」など特殊な通信構成
     
  • プロトコルがシンプル
電子工作用の無線モジュールを使うメリットとして上げられるのが、チャンネル等を細かく設定することで機器同士の干渉などをある程度防げる、選択肢が多いこと。他にはマルチホップのように特殊な通信構成を組むことで効率よく安定した通信環境を構築することが出来るという事などが挙げられます。

Xbee/Twe-Lite

  • 近距離通信規格の「Zigbee」をベースにしたモジュール
     
  • 規制も緩く気軽に使えるが、人の多い場所では△
有名なモジュールとして上げられるのが「Zigbee」と呼ばれる無線規格の機能を搭載したモジュールで、XbeeやTwe-Liteなどがあります。
2.4GHzと900Mhz/800MHzの三種類がありますが、日本国内で使用が許されているのは2.4GHz帯のみなので、Wi-FiやBluetooth等の影響は避けられません。
一方でX-CTUという高機能なソフトウェアがあったり、入手性も良いので導入として始めるには丁度良いデバイスだと思います。ネット上にも情報がたくさん載っています。

IM920

  • 最大1㎞とかなり長距離通信が可能
     
  • 900MHz帯国内仕様なので、通信時間に制限がある
2つ目がIM920というモジュールです。こちらは920MHz帯を日本国内で使用できるように設定されたもので、通信時間が1時間のうち6分間(それでも1時間あたり2万回のやりとりが可能)と制限されていますが、開けたところであれば最大で1㎞程度の長距離通信が可能なデバイスです。
920MHzという帯域を使っているので2.4GHz帯より障害物にも強いということ、大勢の人がいる場所でも携帯電話のようなデバイスに影響されにくいというメリットがあります。

LoRaWan

  • LPWA(LowPowerWideArea)の一種
     
  • 間欠動作で長期間バッテリー動作する
     
  • IoT向けと言われている通信技術
最後はLoRaWanというLPWAの一種です。こちらも900MHz帯が使われており、間欠動作(必要な時だけ動作する機能)により消費電力を最小限に抑えるという働きがあります。
作品やパフォーマンスというよりは畑で土壌の温度を数時間おきに計測して送信するといった実用向きな仕様ではありますが、考え方次第で面白い使い方ができると思います。

IM920の接続

  • 送信機と受信機の回路構成は同じ 
     
  • 送信側と同じ色に光る
今回はIM920のデモをやってみます。IM920に接続するのに最低限必要なのは電源とGND・そしてシリアルのTX/RXです。
NeoPixelを追加して赤→緑→青…と色の変化が同期するようにしました。

送信側

  • 通常のUSBとのシリアル通信のように送る
     
  • IM920のプロトコルに従ってデータを送信する
前回のDMXと同様に、Arduinoの0/1番ピンからシリアル通信でやりとりを行います。送ってほしいデータをIM920にリクエストをすると、そのデータを解釈して通信範囲のデバイス全体、もしくは特定のデバイスに向けて送信してくれるのでデータシートを読みながら必要なコマンドを実装する必要があります。
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel pixels(1, 6, NEO_GRB + NEO_KHZ800);

String mes = "";

void setup() {
  Serial.begin(19200);
  pixels.begin();
}

void loop() {
  int dl = 500;
  //01,02,03...という順番で1バイトの数値を送っている。txdaは全体に送信するコマンド
  delay(dl);
  Serial.print("txda 01\r\n");
  pixels.setPixelColor(0, pixels.Color(50, 0, 0));
  pixels.show();

  delay(dl);
  Serial.print("txda 02\r\n");
  pixels.setPixelColor(0, pixels.Color(0, 50, 0));
  pixels.show();

  delay(dl);
  Serial.print("txda 03\r\n");
  pixels.setPixelColor(0, pixels.Color(0, 0, 50));
  pixels.show();
}

受信側

  • シリアル通信として受信
     
  • 送信元のノード、受信データの電波強度が付加される
受信側も同様にシリアル通信でデータを受け取るので、USBシリアルをする時などと殆ど処理は変わりません。送信側では「txda 01」というデータを送りましたが、受信される時には、「ヘッダー・送信元のノード・受信データ」の要素が追加された状態でArduinoまで届けられるので、その情報をパースしてあげる必要があります。
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel pixels(1, 6, NEO_GRB + NEO_KHZ800);

String mes = "";

void setup() {
  Serial.begin(19200);
  pixels.begin();
}

void loop() {
  while (Serial.available() > 0)
  {
    byte bt = Serial.read();

    //来るデータにはダミー、送信元ID、電波強度の情報が入っているので、実際の情報は12文字目以降に含まれる
    if (bt == '\n') {
      if (mes.substring(12, 13) == "1") pixels.setPixelColor(0, pixels.Color(50, 0, 0));
      if (mes.substring(12, 13) == "2") pixels.setPixelColor(0, pixels.Color(0, 50, 0));
      if (mes.substring(12, 13) == "3") pixels.setPixelColor(0, pixels.Color(0, 0,  50));
      mes = "";
      pixels.show();
    } else {
      mes += char(bt);
    }

  }
}

touchdesignerで受信

  • Arduino Leonardでパススルー
     
  • 直接シリアル変換を付けても良い
文字列のやりとりなので、Touchdesignerに接続することも勿論可能です。今回はArduinoLeonardで受けたシリアル通信をそのままTDに流し込みます。ArduinoUNOの場合はUSBのシリアルとTX/RXは同じラインなのでどちらか片方しか使えませんが、Leonardの場合0/1番ピンはSerial1という別のラインになっているので、シリアル通信を使いながらUSBシリアルでPCともやり取りするという小技が可能になっています。
void setup() {
  Serial1.begin(19200);
  Serial.begin(19200);
  pixels.begin();
}

void loop() {
  while (Serial1.available() > 0)
  {
    Serial.write(Serial1.read());
  }
}

serialDATで文字をパース

  • Arduino内で処理していた物を再現
     
  • Serial DATのコールバックで処理する
IM920から来るデータをTouchdesignerで受け取り、Pythonで解釈します。1,2,3という番号をRGBに割り当てているので、それに応じた条件分岐をしてTOPの色に割り当てています。
def onReceive(dat, rowIndex, message, bytes):
    op('constant1').par.colorr = 0
    op('constant1').par.colorg = 0
    op('constant1').par.colorb = 0

    if message[12] == '1':
        op('constant1').par.colorr = 1

    if message[12] == '2':
        op('constant1').par.colorg = 1

    if message[12] == '3':
        op('constant1').par.colorb = 1

    return

参考:https://tony-mooori.blogspot.com/2017/03/im920arduinoarduino.html

第二十三項 DMXによる連携

第二十三項 DMXによる連携

電子工作創作表現(2019/01/10)

スライドPDF

後期課題出します

  • 締め切り:1/24の授業まで
     
  • 後期課題ページを参照

DMXとは?

  • 舞台装置(照明や特効など)で用いられる通信規格

  • ムービングライトやフォグ・AC100Vディマーなど
     

  • 遠距離で構成がシンプル、堅牢性が高い

DMXは通信規格の一種で、舞台装置で用いられる事が多い規格です。
一つの親から複数の子への一方向通信ですが、USBシリアルなどよりも長距離伝送が可能で、プロトコルも単純なため堅牢性が高いシステム構成ができるというメリットがあります。 

PCとDMX

  • ENTTEC社の「OpenDMX」が定番
     
  • LAN上にDMX信号を載せられる「Artnet」
フェーダーのついた調光器のような業務用機器が製造されていますが、PCからもコントロールすることが出来ます。oFやMax/MSP、TouchDesignerではENTTECが開発しているOpenDMXという規格のUSB->DMX変換を使ったり、LANに接続して使えるArtnetという信号を送ったりすることができます。

接続方法は「デイジーチェーン」

  • 親->子->子…のように数珠繋ぎしていく
     
  • 長距離・多数のデバイスでも配線がシンプル
更に大きな特徴としてあげられるのが「デイジーチェーン」という結線方法です。
親から子のDMX INに接続したら、次の子には最初の子のDMX OUTからの信号を入れてあげるという方法を取っているので、各デバイスを一筆書きで繋げるように結線すれば、使用するケーブルの総量は比較的少量かつシンプルな構成で済みます。

ArduinoとDMX

  • IC「LTC485」などのICを足せば、送受信ができる
     
  • 子として:DMXを受信する自作の照明や装置の開発
     
  • 親として:低コスト(高リスク)な送信機の開発
     

    DMXは通信プロトコルの一種なので、プログラムを実装すればArduinoからもDMXを送受信することができます。
    通常のArduino(Leonardがおススメ)にLTC485などのICを追加することで比較的簡単に実現可能です。2000円くらいでシールドも出ているようです。\
    Arduinoから送受信ができれば、子として通常の舞台装置のDMXラインに載る自作の照明や駆動装置を作ることができます。
    また、親として低コストで簡易的な送信機を作ることも可能です。

信号線

  • XLR5ピンまたは3ピンがよく使われる

  • ライン自体はHOT/COLD/GNDの3線

DMXはHOT/COLD/GNDの3線があれば通信することができるので、ケーブルにはXLRの5ピンまたは3ピンが使われます。
間違って照明機器と音響機器が繋がらないよう規格上は5ピンと定められているのですが、ケーブル資源を流用できる・大量に流通している3品の方が安く手に入るなどの理由から安物のDMX機器では3ピンのコネクタが使われる事も多いようです。

LTC485Nを使う

  • LTC485NとArduinoのTX/RXを接続する
     

  • Arduino Leonardoだとベター

  • DMXのハードウェアレベルの仕様は、RS485(EIA-485)に準拠する

このXLRの3線をArduino側に用意したIC、LTC485Nと接続します。
DMXの通信規格はRS485という電気的な規格をベースとしていますが、このICはArduinoのシリアル通信ポートをRS485に対応させるためのICです。
つまりTX/RXから行われるシリアル通信を、RS485準拠の差動通信という方式に変換することで電気的にDMX通信を可能にしてあります。

TX/RXは0、1番ピンで固定なのでそれぞれ接続します。
そしてRS485は双方向では無いので、状況に応じて送信と受信状態を切り替える必要があります。DE/REピンがその設定にあたりますが、後述するライブラリで2番ピンを使うので2番ピンに接続するようにします。

DMX通信ライブラリ

  • 今回使用するのはDMXSerial
     
  • 送受信どちらも可能
物理的なレイヤーはICを追加し、コネクタを用意して電気的に繋げることでクリアーできました。DMXは通信プロトコルなのでそのやりとりをArduinoにプログラムする必要があります。DMXSerialというライブラリがシンプルにDMXを扱えるようになるので、このライブラリを使ったサンプルを用意しました。

DMXを受信する

  • 0~255の値が、512ch分送られてくる
     
  • 使いたい先頭チャンネルを決めて、そこから使っていく
ArduinoからDMXを受信します。DMXの信号は512個の数字が繰り返し送られてくるという実にシンプルなプロトコルになっています。1チャンネルの値の範囲は0~255で1バイト分なので、より細かい解像度でやり取りしたい場合は2chを消費して16ビットとするなどの工夫が必要です。
サンプルはNeoPixelの色をコントロールするようになっていて、変数startChから3つ分のチャンネルを使うようになっています。DMX機器はこのように決めた先頭チャンネルから順番に使っていくという習わしになっていて、1つのラインでチャンネルが被らないようにしなくてはいけません。高級なムービングだと1台で30chくらい使ったりすることもあるので、全く同じ動きを期待する機器はチャンネルをあえて同じにするなど、配分に気を付ける必要があります。
#include <DMXSerial.h>
#include <Adafruit_NeoPixel.h>

Adafruit_NeoPixel pixels(1, 6, NEO_GRB + NEO_KHZ800);
int startCh = 1;//DMXのスタートアドレス

void setup()
{
  pixels.begin();
  DMXSerial.init(DMXReceiver);
  Serial.begin(115200);
}

void loop()
{
  unsigned long lastPacket = DMXSerial.noDataSince();

  if (lastPacket < 5000) {
      byte dmx[4];
      for (int i = 0;i < 4;i++)
      {
        dmx[i] = DMXSerial.read(startCh + i);
        Serial.print(dmx[i]);
        Serial.print('\t');
      }
      Serial.print('\n');
      pixels.setPixelColor(i, pixels.Color(dmx[0], dmx[1], dmx[2]));
      pixels.show();
  }
}

TDから送信

  • DMXout CHOPで送信
TouchDesignerから受信させてみます。DMX outCHOPとENTTECのDMXUSBから送ったDMX信号を、Arduinoに送ります。
Arduino側のスタートチャンネルは1から3つ分なので、TOPの単色を分解してそのままCHOPにRGBとして流せばTOPと同じ色にNeoPixelが光ります。

DMXを送信する

  • initの設定を変えれば、送信機として振る舞う
     
  • 送信と受信を同時にはできない
送信も同じ要領でプログラムに書き込むことができます。
ここではサイン波を生成してDMXの値として送っていますが、ボリュームやフェーダーを付ければもちろん自作の照明卓のようなものを作ることができるわけです。
RS-485の話の時に言ったように、受信と送信を同時に行うことはできません。高速に切り替えれば出来ないことはなさそうですが、そういうことが必要な場面で使うプロトコルではありません。
#include <DMXSerial.h>

void setup() {
  DMXSerial.init(DMXController);
}

void loop() {
  int out = (sin(millis() / 100.0) + 1) * 128;
  DMXSerial.write(1, out);
  delay(12);
}

TDで受信

  • DMX in CHOPで受信
あまり利用頻度としては少なそうですが、TouchDesignerで受信することもできます。
この時、DMX outとDMX inを同時に同じ機器に向けて接続することはできないので注意してください。
c1-c512のチャンネルが生成され、Arduinoから出力した値がTDに表示されます。この信号をDMXUSBのTHRUから照明等に送れば信号のモニタリングをすることが可能ですが、受けたデータを上書き修正して送る、というようなことはできません。

参考:https://qiita.com/loveandsheep/items/e1295ec9ce589eaa85c9

19年度後期課題

後期課題

【締め切り】

1/24の授業まで

提出フォーム

こちらのフォームを授業までに記入してください。

【課題】

以下の中から好きな物1つを選んで提出してください。
また追ってGoogleフォームを作りますので、どちらを提出するかそちらに記入お願いします。

課題A:作品プレゼンテーション

電子工作を使用して作った作品のプレゼンを行ってください。
実際の作品か記録映像のどちらかがあれば、形式は自由とします。

課題B:実装レポート記事の作成

プログラムや電子工作は、試したことを自分でまとめると後ですぐにまた使えるのでとても便利です。また、そういった知見を個人のブログ記事や、QiitaというWebサービスを使ってみんなで共有するという慣習があります。
作品を作る時間が取れなそうという人は、そのとっかかりとなるセンサーを試してみて、自分用のノートとして記事をアップしてみてください。個人ブログやQiita・noteなどパブリックに見られる物なら何でもOKです。

第二十二項 Arduino以外のマイコンボード

第二十二項 Arduino以外のマイコンボード

電子工作創作表現(2019/12/13)

スライドPDF

お知らせ

  • Ginza Sony Parkでの展示「Affinity in Autonomy」
     
  • 音響作品(SSVR)、デバイス系の展示などが色々あります
https://www.sonydesign.com/ja/Affinity_in_Autonomy/

Arduino以外のマイコンボード

  • Arduino以前は高価で複雑な物が多かった
     
  • Arduino以降様々な互換ボードや、近い思想の製品が開発される
今までの講義は、Arduino UNOをベースとした電子部品の使い方についてしていました。初心者でも扱いやすいマイコンボードというと、Arduino以前は値段も高く使いづらい物が多かったのですが、2000年代に訪れたArduinoの登場が一つのブレイクスルーになっていると言えます。
今回はそのArduino以降生まれた様々なハードウェアについて、特徴等を解説していきたいと思います。

Arduinoとの違い

  • Arduinoには無い特別な機能(Wi-Fi, 高速)
     
  • 要素を削って更に安くした製品
Arduinoは簡単に扱うということを基本的な目標としているので、デフォルトではあくまでベーシックで最低限の機能のみが使えるようになっています。その機能の範疇からはみ出て、ある用途に特化したりコストパフォーマンスを上げたりしたものが色々開発されていました。
Arduino同様長く使われ続けている物もあるのですが、中にはすぐ開発が終了してしまって手に入らなくなってしまうような物もあります。
単純に人気・有名な物や大手メーカーが開発しているものは長く続く傾向があるので、そういった物を学ぶと長く使えるノウハウとして有用です。以前紹介したESPシリーズもこの範疇に入ります。
個人やクラウドファンディングで開発されたものは開発や製造がすぐに終了してしまったりするので、単発作品で使う分には便利ですが、長く付き合おうという時には少し慎重になる必要があるでしょう。

マイコンボードとSBC

  • 一つのチップで一つのプログラムが動くのが「マイコンボード」
     
  • パソコンに近い機能を持つのが「シングルボードコンピュータ」
今日紹介するものは、大きく分けて二種類あります。まず一つ目がArduinoと近い部類で、専用のICに一つのプログラムを書き込んで実行する「マイコンボード」と呼ばれるもの。小さくシンプルで安いという特徴があります。

もう一つがマイコンよりも高性能なチップ(SoCなど)を使って殆どPCのように扱うことができる「シングルボードコンピュータ」です。

こちらはLinuxベースで動く物が多いため、ProcessingやOpenframeworks、Puredataなど、マルチプラットフォームで軽量なソフトウェアは動かすことができますが、TouchdesignerやMAX/MSPあたりを動かそうとすると難しいかもしれません。

マイコンボード1:Mbed

  • IDEがヴラウザベースなので、ネット環境があれば自分のPCも不要
     
  • Arduinoよりも全体的に高スペック(48MHz/96MHz)
MbedはARM社が作ったマイコンボードで、Arduinoよりも高速で開発環境がクラウドベースという特徴があります。
アカウントを作ってその中で開発するので、インターネットがある環境であれば開発環境をインストールせずにどこでも開発できるという特徴があります。

マイコンボード2:Nucleo

  • Mbedベースで更に高機能なモデル(48MHz~216MHz)
     
  • マイクロマウス等でも使われ信頼性が高い
NucleoはMbedの環境で開発ができるSTMicro社のボードです。種類が非常に豊富なので、目的に合った無駄のないボードを選定しやすくなっています。
クロックも48MHz~200MHz近くまで幅広い選択肢があります。

マイコンボード3:Teensy

  • Mbedよりも更にハイスペック(400MHzなど)

  • この高スペックだが相当薄く小さめ

Arduinoよりも全体的に高速なMbed系デバイスですが、Arduino互換でクロックが早い物もあります。Teensyは600MHzとマイコンボードの中ではかなり高速なCPUを積んでいて、しかも殆どが面実装部品で構成されているため小型化がしやすいというメリットがあります。
バーサライタ(参考https://homemadegarbage.com/teensy4)のようにLEDを高速に切り替えたりするような用途に向いているでしょう。

シングルボードコンピュータとは

  • 通常のGPIOに加えて、HDMIやUSBなど普通のPC的要素が載っている
     
  • LinuxやWindowsなどのOSが搭載
続いてシングルボードコンピュータです。SBCはマイコンボードのようにデジタル入出力があるのに加え、USBやHDMIなど通常のPCで使うようなポートを備えている事が多いです。ソフトウェアについても、LinuxやWindowsのような通常のOSが動いていて、その中でプログラムを書いて動作させるという方法が主です。

シングルボード1:Raspberry pi

  • Linux系OSやIoT用のWindowsが動作する
     
  • 初心者向けSBCの中で最もポピュラー
Raspberry piはシングルボードコンピュータの中でもっともポピュラーと言って良いでしょう。全体的に中~上級者向けですが、Raspberry piは初学者、特に小中学生でも扱えるようにドキュメントや専用OSを充実させているのでとても入手も操作も簡単になっています。
4~5000円で買える通常のシリーズの他に、要素を削ぎ落して500円で買えるようにしたRaspberry pi Zeroというモデルがあります。

シングルボード2:Latte Panda

  • 通常のWindowsが動作する
     
  • Arduinoクローンが載っているので、Arduinoスケッチもそのまま動作する
シングルボードはLinuxが動く物が多いですが、そんな中でWindowsに対応しているのがこのLattePandaです。ちょっと変わって構成で、Windowsが動作するICとArduinoが動作するICがあります。そのためWindowsで映像や音響を鳴らしながらArduinoスケッチを並列で実行するという使い方をすることができます。

シングルボード3:Jetson

  • NVidiaが開発しており、GPUが強力(機械学習向け)
     
  • グラフィックが比較的強い
Jetsonは機械学習に特化したSBCです。GPU大手のNVIDIAが開発していて、他のボードに比べグラフィックが強いので、映像送出デバイスとして使えるでしょう。

シングルボード4:BeagleBoneBlack

  • ポート数が抑えめな分コンパクト
     
  • 低レイテンシー拡張「BELA」が使える
テキサスインスツルメンツのBeagleBoneBlackは、ポートがUSBやイーサネットのみと必要最低限に抑えられている分、他のボードに比べ幾分かコンパクトになっています。
BELAという超低レイテンシーのオーディオ拡張ボードを使えるので、電子楽器デバイスなどを作りたい時にとても向いています。

BELA:https://www.youtube.com/watch?v=Os2ljj1cIog

第二十一項 ロータリーエンコーダとノイズ対策・割り込み

電子工作創作表現(2019/12/04)

スライドPDF

ロータリーエンコーダ

  • 無限回転できるツマミ
     
  • パラメータの選択などに使える
今回ロータリーエンコーダを紹介します。パーツとしては地味でちゃんと使おうとすると意外と込み入っているのですが、使えるようになっておくと便利なのでやっておこうと思います。

ロータリーエンコーダの機能

  • 無限に回転させられる
     
  • ボリュームのようなアナログ値ではなく、デジタル信号
パッと見ボリュームとほぼ同じ見た目ですが、ロータリーエンコーダは無限に回すことができるので、1回転以上する必要があるような場面で使われます。そのため、ボリュームのようなアナログの値ではなく一定の角度ごとにカウントされるデジタルの信号をやりとりすることになり、analogread()のように一行でサクサクとはいかず、ある程度プログラムを書いてあげる必要があります。

ロータリーエンコーダの用途

  • 選択肢が可変や大量にある時の選択ツールとして
     
  • 機械で動作する物の移動量を知る装置として
ミキサーのボリュームのように、パラメータの最小最大レンジが決まっているような場合はボリュームが使えますが、何かたくさんある選択肢の中から選ぶ機能のように、何回転もさせたい場合に使われます。また、機械に取り付けて回転を計測し、モーターや人がどのくらい物を動かしたかという事を検知する時にも使えます。

ロータリーエンコーダの種類

  • 機械式と光学式
     

  • インクリメンタルとアブソリュート

  • 主なメーカーはALPSとオムロン

エンコーダの種類には、まず機械式と光学式というものがあります。スイッチが中に入っていて、回るとスイッチのON/OFFが切り替わる方式が機械式、光センサーが中に入っていて、円盤の動きを読み取って状態を知るのが光学式で、光学式の方が比較的大きく高価ですが、信号が安定していて摩耗などの劣化もしにくいというメリットがあります。

回転の数え方にも種類があります。一定の角度動いたら1カウントするという相対的な数え方をするのがインクリメンタル、ボリュームに似ていますが今どの角度を向いているのかが分かるというのがアブソリュート方式です。

ALPS電気のEC12シリーズ

  • 秋月で80円ほど
     
  • クリック/ノンクリックタイプや、高さも違うものなど様々
今回は安価で手に入りやすい、ALPS電気の機械式インクリメンタルのエンコーダを実演します。秋月で手に入るもので、個人で入手でいるもので最もオーソドックスな部品です。

シンプルに接続

  • A相・B相から出力されるので、それぞれをプルアップで入力する
     
  • おおまかな値は取れるが、「チャタリング」ノイズが問題になりやすい
ロータリーエンコーダで値を正確に取るには、少し回路が複雑なので、順を追って説明します。まずシンプルにArduinoと直結しようとすると、真ん中のピンがCOM=GNDピンになるので、こうなります。今回2番3番ピンにつないでいますが、UNOの場合2番3番が良いです。理由は後述します。

これで信号は取れるのですが、機械式は「チャタリング」と言われるノイズが発生してしまうため、正確な回転数が取れません。

ロータリーエンコーダの信号

エンコーダから来る信号をグラフ化したものです。赤と青の線がそれぞれA相とB相のHIGH-LOWを表しており、回転させたときにこのように交互に上下する状態が理想です。

チャタリングノイズ

ところが、機械式のエンコーダでは動いた時にHIGHとLOWの境目で、信号がパタパタと暴れることがあります。これが「チャタリングノイズ」と呼ばれるもので、プログラムで回転数をカウントしようとした時にこのノイズのせいで余計にカウントされてしまいます。
参考:https://www.marutsu.co.jp/pc/static/large_order/1405_311_ph

ノイズ対策1:コンデンサ

それを回避するための方法として、まずひとつ目は信号線とGNDの間にコンデンサを入れてあげる方法です。コンデンサは電荷を貯めたり放出したりできる素子で、急峻なノイズをある程度吸収してくれるという性質があります。これを入れるだけでも大分信号が整ってきます。コンデンサにはどのくらい電気を貯められるかがF(ファラッド)という単位で示されていますが、このロータリーエンコーダの場合は0.01μF(マイクロファラッド)のコンデンサを入れるのがデータシートで推奨されています。

ノイズ対策2:シュミットトリガ

更に堅牢な作りにしたいという場合は、シュミットトリガを入れるとより信号が安定します。これはHIGH/LOWのしきい値にある程度バッファを持たせるためのもので、通常のデジタルピンは例えば5Vの半分である2.5V以上ならHIGH、それ以下ならLOWという判断をします。しかしそれだと、2.5V付近をちょっと行ったり来たりした場合またチャタリングのような信号のブレが出てしまうので、例えば4V以上ならHIGH、1V以下ならLOWに切り替わり、それ以外の時は以前の状態を維持するという振る舞いをさせることができるのがこのシュミットトリガで、そのような性質を「ヒステリシス」と呼んだりもします。

カウント用プログラム

  • プログラム内でカウントする

  • loopの中でカウントできるが、取りこぼしが起こる

このようにして回路を接続したら、以下のようなコードを書いて数をカウントしていきます。ロータリーエンコーダの方で数字を出してくれるわけではないので、Arduino側で処理する必要があります。2つの状態は0~3まで4種類あり、時計回りは1->3->0->2...となり、反時計周りは2->0->3->1...と繰り返されていきますので、現在の状態とその一個前の状態を比較することで、counterの値が増減するようになっています。

これでもある程度の速度はカウントできるのですが、実は取りこぼしが起きてしまいます。あくまで現在の状態を拾えているのはdigitalRead()を呼んでいる瞬間だけなので、素早く回したり、ロータリーエンコーダのカウント以外の処理が長くなったりすると、カウントを取りこぼすようになってしまいます。
bool pinA = false;
bool pinB = false;
byte current = 0;
byte previous = 0;
long counter = 0;

int cw[] = {1, 3, 0, 2};
int ccw[] = {2, 0, 3, 1};

void setup() {
  pinMode(2, INPUT);
  pinMode(3, INPUT);

  Serial.begin(115200);
}

void loop(){

  //現在の状態を取得
  pinA = digitalRead(2);
  pinB = digitalRead(3);

  //2ビットで0~3の数字に変換
  current = pinA + pinB * 2;

  //currentの値が、CW/CCWのどちらか期待する方向だったらカウントを増減
  if (current == cw[previous]) counter++;
  if (current == ccw[previous]) counter--;

  //直前の値として記憶
  previous = current;

  Serial.println(counter);
}

割り込みを入れる

  • loop中にReadするのではなく、ピンの変化で関数を呼び出す
     
  • 重い処理をしていても、中断して実行される
そういった時のために、実はArduinoには「割り込み」という機能がついています。UNOの場合は2番ピンと3番ピン、Arduino Leonardoの場合は0,1,2,3,7などのピンにその機能がついていて、そのピンのHIGH/LOWが変化した瞬間に用意されたプログラムを実行するというもので、取りこぼしたくない信号の変化を取る時にとても便利な機能です。

割り込みを入れた場合のコード

  • attachInterrupt()関数で宣言する
     
  • 割り込み内で使う変数にはvolatileをつける
先程のコードのloopにあった部分を、割り込み用関数rotaryとして別に定義します。2番ピン、3番ピンに変化があった時にすぐこの関数を呼び出すようattachInteerruptという命令を呼び出して、ピンと関数を関連付けます。こうすることで、loopの中でdelay関数のようにプログラムの流れを長時間専有する処理を加えたとしても、エンコーダが動くたびにカウント処理が挟まるので正確に動かした量を検知することができます。
volatile bool pinA = false;
volatile bool pinB = false;
volatile byte current = 0;
volatile byte previous = 0;
volatile long counter = 0;

int cw[] = {1, 3, 0, 2};
int ccw[] = {2, 0, 3, 1};

int input_A = 2;
int input_B = 3;

void setup() {
  pinMode(input_A, INPUT);
  pinMode(input_B, INPUT);
  Serial.begin(9600);

  //割り込みを宣言
  attachInterrupt(digitalPinToInterrupt(input_A), rotary, CHANGE);
  attachInterrupt(digitalPinToInterrupt(input_B), rotary, CHANGE);

  //初期位置を取得
  rotary();
}

void loop(){
  delay(500);
  Serial.println(counter);
}

void rotary()
{
  //現在の状態を取得
  pinA = digitalRead(input_A);
  pinB = digitalRead(input_B);

  //2ビットで0~3の数字に変換
  current = pinA + pinB * 2;

  //currentの値が、CW/CCWのどちらか期待する方向だったらカウントを増減
  if (current == cw[previous]) counter++;
  if (current == ccw[previous]) counter--;

  previous = current;
}

第二十項 ArduinoとLAN・Wi-Fi

電子工作創作表現(2019/11/29)

スライドPDF

Arduino同士やPCとの通信

  • Arduinoが基本的に扱えるのはシリアル通信のみ
     
  • インターネットやLANに接続する手段もある
Arduino単体でPCと通信する方法としては、USBを介したシリアル通信があります。手軽に実行できますがソフトウェアでやらなければいけない要素が多く、かなり取っつきづらいです。
一方で追加のパーツが必要になりますが、EthernetケーブルやWi-Fiを通してLANを使って通信する方法というのも実はあって、今回はそれらを紹介していきます。

LAN接続のメリット

  • LANハブが使えるので、デバイス間通信が楽
     
  • 距離の制約にも強い
     
  • プロトコルもOSCなどが使用可能
LANを使って接続をすれば、LANハブを使い複数のArduinoを制御したり、かなり離れた距離のデバイスを制御したりといったことが可能になります。またソフトウェア側もOSCなどを使えるので、MAX/MSPやTouchdesignerで定番の機能をそのまま使うことができます。

LANに接続する方法

  • Arduino Ethernetを使う(有線)
     
  • ArduinoにW5500 Ethernetモジュールを付ける(有線)
     
  • ESP-WROOMシリーズを使う(無線)
LANに接続する代表的な方法にこの3つがあります。一つずつ解説していきます。

Arduino Ethernetを使う

  • ArduinoにEthernetポートが付いたモデル
     
  • 今は純正の取り扱いが無い
まず一つ目はArduino Ethernetという特殊なArduinoを使う方法です。USBの他にEthernetポートが付いているので、有線LANに差してすぐ使うことができます。他にArduinoEthernetシールドという、従来のArduinoにシールドを差して使うモデルも過去あったのですが、現在純正品は販売されていません。その代わりKEYSTUDIOなど他で作られたクローン版があるので、そちらを使うことができます。

ArduinoにW5500モジュールを付ける

  • ひと手間増えるが、外せば普通のArduinoとしても使える
     
  • SPI通信で接続。Arduino Ethernetはこれが既に載っているモデル
もう一つは、Ethernet用のチップが別になっているW5500というモジュールをArduinoに付ける方法です。既にArduinoを持っている人は、こういったモジュールを接続することでも実現できます。Ethernet2というライブラリを使って、SPI通信でArduinoと接続することでLAN用のパケットをやり取りできるようになります。

ちなみに、Arduinoでよく使われるLAN用のチップにW5100とW5500があり、5100はEthernet.h、5500の方はEthernet2.hを使う必要があるので注意が必要です。

ESP-WROOMシリーズを使う

  • Arduino互換で動作するマイコン
     
  • 安くて小さい。
     
  • IOポートは少ないが、これ一個でWi-Fi接続が可能
そして最後が今日紹介するESP-WROOMシリーズで、有線ではなくWi-Fiを使って接続する方式です。Arduinoとは別の製品ですが、ArduinoIDEでプログラムを開発できるように設計されているので、ほぼArduinoと同じように扱うことができます。IOポートはやや少なめですが、単独でWi-Fiが扱えて値段も安いです。

ESP-WROOM-02

  • スタンダード品。もう少しグレードの高い32も流通
     

  • 秋月で単品が400円

  • おススメはスイッチサイエンスのモジュール品

今回持ってきたのはESP-WROOM-02というスタンダードなモデルです。もう少し機能が多く、Bluetooth機能もついた32というモデルもあります。02は秋月で単品だと400円という値段で売っていて、これはマイコン+Wi-Fi機能付きでかなり破格といえます。

ただし、単品のピンが2mmピッチと小さめで、ブレッドボードやユニバーサル基板に載せづらいです。USBピンもついてないので、最初は秋月やスイッチサイエンスのモジュール化されたモデルを購入するのがおススメです。

ESP-WROOM-32

  • 02はArduinoと比べるとピン数がかなり少なめ
     
  • BluetoothやSDカード読み取り機能など高機能
ちなみに、この02を高機能にしたESP32というチップもあり、そちらは少し高くてサイズが大きいけど高機能なモデルになっています。Arduinoと比較すると02の方は少しピンが少ない印象がありますが、32の方は20以上のIOピンが備わっているので、Arduinoの感覚で行っても全然足りなくなるということは無さそうです。

ESPを使えるように設定する

Arduino公式のマイコンではないので、ArduinoIDEに追加でICの情報を追加する必要があります。詳しくはスイッチサイエンス等のWebページに書いてあるので、ここを参照しながら追加するのが良いでしょう。

書き込んでみる

nodemcu

  • Arduinoと比べ少し時間がかかる
     
  • 書き込み方式はckではなくnodemcuがおススメ
ボード情報を書き込んだら、Uploadボタンを押せば通常のArduinoと同じように書き込むことができます。ESPは書き込みモードで立ち上げなくてはいけないckモードと、通常のArduinoと同じPCからリセットをかけるnodemcuモードがあって、ckはUploadを押した後手動でリセットボタンを押す必要があるので、ここの設定はnodemcuモードがおススメです。
void setup() {
  pinMode(16, OUTPUT);
  pinMode(4, OUTPUT);
}

void loop() {
  digitalWrite(16, HIGH);
  digitalWrite(4, LOW);
  delay(1000);

  digitalWrite(16, LOW);
  digitalWrite(4, HIGH);
  delay(1000);
}

Wi-Fiに接続して、OSCを受信する

  • taihideaki氏の「AdruinoOSC」
     
  • 一つのアドレスに対して実行する内容をラムダ式で記述
ESPシリーズは、ボード設定を入れた段階で基本的なArduino用のライブラリも一通り入っています。TCPやUDPなどの通信方式のサンプルは入ってますが、OSCの送受信をしようとするとまた別でライブラリが必要になります。

今回は「ArduinoOSC」というライブラリを使ってOSCの受信を試してみます。
#include "ArduinoOSC.h"
OscWiFi osc;

String ssid = "ssid";
String passwd = "pw";
IPAddress ip(10, 0, 0, 31);
IPAddress gateway(10, 0, 0, 1);
IPAddress subnet(255, 255, 255, 0);

void setup()
{
  pinMode(16, OUTPUT);
  pinMode(4, OUTPUT);

  WiFi.begin(ssid, passwd);
  WiFi.config(ip, gateway, subnet);

  Serial.begin(115200);
  Serial.println(WiFi.localIP());
  osc.begin(8888);

  osc.subscribe("/led1", [](OscMessage& m) //OSCを受信すると、カッコの中身が実行される
  {
    analogWrite(16, m.arg<int>(0));
  });

  osc.subscribe("/led2", [](OscMessage& m)
  {
    analogWrite(4, m.arg<int>(0));
  });
}

void loop()
{
    osc.parse(); // should be called
}

TDのouc out CHOPから送信

  • チャンネル名がアドレスになる
     
  • アドレスやポートの他”Data Format"をSampleに指定
touchdesignerからOSCを送信してみます。先ほどのソースコードで"/led1・/led2"というアドレスを受信するようにしたので”led1”というチャンネルをOSC outCHOPに入れて送信します。頭のスラッシュ記号は自動的に入れられるので、チャンネル名には含めません。

設定項目としては、アドレスやポートの他に”Data Format”という項目があり、そこをSampleにする必要があります。

ESPからOSCを送信する

  • osc.sendで1行で送ることができる
     
  • 複数の違うタイプの値も連続して入れられる
マイコン側から、OSCを送信することもできます。ArduinoOSCではosc.sendにアドレスとポート、値を入れれば送信が可能です。値の種類はbool, int, float, String等が使えます。loopを以下のように記述して、マイフレーム起動時間とランダムな数値を送ってみます。
void loop()
{
  delay(12);//マイコンのループは早いので、Delayを入れる
  osc.parse();
  osc.send("192.168.1.255", 9999, "/array", millis() / 1000.0f, float(random(100)));
}

TDから受信

今度はTDで受信をしてみます。osc inCHOPでは、ポート番号だけを指示すれば受信できるようになっています。
/arrayというアドレスで送った複数のデータは、array1, array2...という風にインデックスを付けた状態で受け取ることができます。CHOPでは文字列を受信できないので、文字列などをやり取りしたい場合はOSC in DATを使う必要があります。array1にはmillisで取得したArduino起動からの時間、array2ではrandomで生成した0~100のランダムな数値が入ってきています。

第十九項 ステッピングモーター制御とSPI通信

電子工作創作表現(2019/11/22)

スライドPDF

ステッピングモーター

  • 前回紹介したステッピングモーターの紹介
     
  • モータードライバを使った実例
前回はステッピングモーターとは何かという所まで話しましたが、今日は実際に動かすために必要な情報を説明していきます。ステッピングモーターは単体でArduinoと接続しただけでは制御できません。DCモーターと同様に大きな電流を必要としますので、モータードライバを使います。

ステッピングモーターの動作原理

  • A相とB相それぞれ2方向、4種類の信号パターン
     
  • 信号の切り替えが「1ステップ」となる
バイポーラステッピングモーターの場合、A相とB相の電流方向を交互に切り替えていって回転させます。電流方向が切り替えられれば良いので、DCモーターの時紹介したブリッジ回路が2ユニットあれば、原理的にはそれでステッピングモーターを動かす事が可能です。
参考:https://www.youtube.com/watch?v=jM09I8DGLZo

モータードライバ「L6470」

  • 表現用途に適したモータードライバ
     
  • 加減速やマイクロステップ機能がある
ステッピングモータードライバもやはり色々あるのですが、今回は作品用途に向いているであろう「L6470」モータードライバを紹介します。安くてシンプルなモータードライバの場合ArduinoのStepperというライブラリを使っても動かす事ができますが、単純な動作しかできないので自分は普段こちらのドライバを使っていますし、おススメです。役に立つ大きな機能として「マイクロステップ」と「加減速」というものがあります。

マイクロステップのメリット

  • フルステップの1.8度は意外と大きい
     
  • ステップ信号を連続的に変化させて、より細かな動作ができる
     
  • 静音性にもつながる
マイクロステップは、従来デジタル(HIGHかLOW)のステップ信号を連続的に変化させて、ステッピングモーター本来のステップ角(1.8度が一般的)よりも細かな制御をするというものです。フルステップで動作する1.8度という角度は意外と大きくて、動かすものが大きくなるほど目立ってきます。加えて音も激しくなるので、静かな環境を必要とするシーンではかなり気になってきます。マイクロステップはそういった懸念をクリアにしてくれるメリットがあります。

加減速のメリット

  • 自然な動きを作ることができる
     
  • 急発進では動かせないような物も動かせる
もう一つの加減速は、動き始めと動き終わりの速度にカーブを付ける機能です。アニメーションなどでも加減速は重要な要素で、加減速抜きで急加速・急減速をすると動かす対象に無理な力がかかりますし、ステッピングモーターの場合は脱調の要因となります。初速から最高速度で動かすのは余計なトルクが必要となるので、本来動かせる物も動かせなかったりということがあります。

入手方法

  • 秋月電子
     
  • ストロベリーリナックス
     
  • Sparkfun「Auto driver」
L6470というとIC単体の事を指しますが、これを使うためにはいくつか周辺部品が必要となり、それらをモジュール化された基板が販売されています。確認できているところだと秋月電子・ストロベリーリナックス・Sparkfunを扱っているスイッチサイエンスなどです。秋月が最も安く、ストロベリーリナックスはそれより少し高いですがとても小さく、42ミリ角のステッピングモーターの後ろに取り付けられるような仕様になっています。

サンプル

l670

  • ストロベリーリナックス製モジュール
     
  • モーター駆動電圧は8V~45Vまで対応
今回はストロベリーリナックス製のモジュールをサンプル基板として用意しました。
良く使われるステッピングモーターの電圧として9~24Vが多いので、これ一個あれば大概のステッピングモーターは動かすことができます。

制御方法

  • 「SPI通信」という方式でArduinoから制御
     
  • コマンド情報を送信する
制御方法ですが、SPI通信という通信方式を使います。これもArduinoやラズパイのようなマイコンの多くが搭載している通信方式で、決められたコマンドを送信してデバイスを操作するためのものです。

ボード間通信

  • 基板上のIC同士や、基板対基板など近距離での通信方式
     
  • USBのような数十㎝~数m単位での通信距離には適さない
     
  • SPI / I2C / UARTなど
以前6軸センサーを扱った時に説明したのがI2Cという通信方式ですが、SPI通信もそれに近い方式で、基板やIC間での通信によく用いられます。構造がシンプルゆえに、あまり長距離や大容量のデータ伝送には向いていません。
SPIとI2Cが使えれば、大抵のモジュール基板は扱えるようになります。

一般的なSPIの結線

spi_circuit

SPI通信はマスターとスレーブという主従関係に基づいて通信が行われます。プログラムを書くArduinoがマスターとなることが殆どですが、Arduinoがスレーブになって受信することも可能です。結線には電源ラインのVCC・GNDの他に3本または4本の線が必要になります。送信・受信とも同じ名前なのが送信の合図を出すSS/CSピン・信号のタイミングを知らせるSCKピンです。

もう2本、データを送信するSDOとデータを受信するSDIというピンがありますが、これは自分(Arduino)の送信は相手の受信につながなくてはいけないので、SDIとSDO・またSDOとSDI同士をつなげる必要があります。どちらにとってのIN/OUTか分かりにくい場合はMISO(Master In Salve Out)やその逆MOSIと言う表記がされている場合もあります。指示を送るなどMasterからの送信のみできれば良い場合は、MISOを繋ぐ必要はないので3本線になります。

ボード上の結線

board_line

L6470モジュールにもこのSPI端子が出ているので、説明書の掲載部分(https://strawberry-linux.com/pub/l6470-manual.pdf)に従って接続します。SPI通信に必要なものはここの3番~8番までの6本で通信できるようになっています。

電圧選択の注意

voltage

ちなみに、秋月とストロベリーリナックスのモジュールはジャンパーピンというピンでICを動作させる電圧を選択します。(モーターを動かす電圧ではなく、L6470本体を動作させる電圧。ロジック電圧とも)
Arduinoから供給する5Vを使うので5Vを今回選択していますが、例えばラズパイのロジック電圧は3.3vになっているので、3.3Vを選ぶ必要があります。ここを間違えて3.3Vモードの時にうっかり5Vを流したりすると結構簡単に死んでしまうので注意してください。

SPI通信のプロトコル

  • 通信する上での仕様を「プロトコル」と呼ぶ
     
  • どんなコマンドを送れば良いかはL6470データシートに記載
結線をして、モーター用の電源を供給すれば、これでArduinoから必要なコマンドを送れば制御できます。どんな信号を送るかというのはデータシートに記載されていますが、便利な分かなり色々なパラメータがあり実装は大変です

L6470用のライブラリ

https://github.com/loveandsheep/l6470duino
 
他にも自分用のライブラリを作っている人が多数

なので今回は、簡単に制御できるよう情報をまとめて実装しておいたライブラリを使用します。
こちらもArduinoのメニューからインストールできるようになっているので、インストールしてみてください。
日本だと「北の国から電子工作」(http://spinelify.blog.fc2.com/blog-entry-41.html)というWebサイトでサンプルを配布されていたりもします。自分が使いやすいと思ったライブラリを見つけて使うというのもテクニックの一つです。

動かす

  • 初期値を設定してから動作させる
     
  • Driver.Run()で速度を指定して動作
以下、動かすためのサンプルです。driverクラスを作り、initで初期化します。初期化した後に、駆動時に使う電圧の設定などを行い、最後にRunというコマンドで回転を指示しています。デフォルトでマイクロステップと加減速が働いているので、とてもスムーズに動作します。
#include "l6470duino.h"

l6470duino::driver Driver;

void setup() {
  // put your setup code here, to run once:
  Driver.init(1);
  Driver.voltage_acc (0xDF);
  Driver.voltage_dec (0xDF);
  Driver.voltage_run (0xDF);
  Driver.voltage_hold(0x30);

}

//動いては止まってを繰り返す
void loop() {
  Driver.Run(20000, false);
  delay(2000);
  Driver.SoftStop();
  delay(2000);
}

フルステップを再現

  • 加減速を高めに設定する
     
  • マイクロステップを切る
マイクロステップと加減速がこのドライバの売りと言いましたが、ここであえてその機能をオフにしてみます。
stepmodeは2のn乗で指定するようになっているので、最大が2の7乗、128分の1ステップになっています。
机に置いて動作させると、その違いがかなり顕著です。機械にとって振動というのは、はんだのクラックやネジのゆるみなど、ただうるさいだけでない色々なトラブルの元にもなります。
#include "l6470duino.h"

l6470duino::driver Driver;

void setup() {
  // put your setup code here, to run once:
  Driver.init(1);
  Driver.voltage_acc (0xDF);
  Driver.voltage_dec (0xDF);
  Driver.voltage_run (0xDF);
  Driver.voltage_hold(0x30);

//フルステップ動作
  Driver.setAcc(300);
  Driver.setDec(300);
  Driver.setStepMode(0);

}

void loop() {
  Driver.Run(20000, false);
  delay(2000);
  Driver.SoftStop();
  delay(2000);
}

指定の位置に送る

  • 指定の位置へ送るのはGoto
     
  • マイクロステップ分を加味する
Runは指定の速度でずっと動き続けるコマンドですが、Gotoコマンドを使うとPWMサーボのように特定の座標まで移動させることができます。角度ではなくステップ数で指示する必要があり、マイクロステップが有効な場合はその分倍にしてあげる必要があります。例えば1/128マイクロステップの時に1週=200ステップ進みたい場合は、200*128=25600ステップ進める必要があります。
#include "l6470duino.h"

l6470duino::driver Driver;

void setup() {
  // put your setup code here, to run once:
  Driver.init(1);
  Driver.voltage_acc (0xDF);
  Driver.voltage_dec (0xDF);
  Driver.voltage_run (0xDF);
  Driver.voltage_hold(0x30);

  Driver.setMaxSpeed(10); //Goto移動時の最高速度は前もって設定
}

void loop() {
  Driver.Goto(200 * 128); //200ステップxマイクロステップ分
  delay(2000);
  Driver.Goto(0); //元の位置へ
  delay(2000);
}

第十八項 モーター制御・PWM/ステッピングモーター

電子工作創作表現(2019/11/1)

スライドPDF

モーターの続き

  • 前回は、シンプルに回転するだけの「DCモーター」
     
  • 今回はもう少し複雑・繊細な動きをするモーター
前回はモーターの基本原理と、最もベーシックな部品であるDCモーターを紹介しました。
今回はもう少し複雑な動きをできるようなモーターを紹介していきます。

動き物を作る

  • 必要な部品は工業系部品か、ロボコン関連用品が充実
     
  • 「ツクモロボット王国」
     
  • 千石電商
ちなみに、作品やパフォーマンスで動きものを作ろうとすると色々と作り物が必要になってきますが、こういったメカ物を取り扱っているのはかなり限られています。秋葉原ではツクモロボット王国辺りに行くと、初心者向けのキットからギヤやシャフトのようなパーツまで揃っているので、まずはキットを改造するというところから初めて見るのも良いかもしれません。すぐそばの千石電商本店にもメカパーツがいくらか揃っている印象でした。

PWMサーボモーター

  • 指定の角度を向けるためのモーター
     
  • 厳密には違うが、単純に「サーボモーター」と言うとこのPWMサーボを指す
     
  • ロボット等に使用されることが多い
今日まず紹介するのがPWMサーボモーターです。単にサーボモーターとかサーボと言う事もありますが、ACサーボなどもう少し大きいサーボモーターもあるので、ここではPWMサーボと呼びます。ラジコンに使われる事も多いので、RCサーボと呼んだりもします。
DCモーターは正転・逆転の方向と速度を制御できますが「ここまで進んだら止める」とか「ここに戻る」のような処理をしようとするとセンサーなどで位置を取らなくてはいけません。ですがこのPWMサーボはセンサーを内蔵しているので、PWMで指示を送ると指定の角度まで進んだ後そこで自動的に停止してくれます。

サーボモーターの特徴

servo

  • ドアの開け閉め、スイッチなど指や関節のような動作が得意
     
  • 駆動範囲は270度~多くても2回転
     
  • 3本線で、PWMを送って角度を指示する
ロボットに使われたりスイッチを押す、蓋を開け閉めするなど指や関節に近い動作を得意とします。そのかわりDCモーターのように無限回転はできず、大体270度~多くて2回転くらいのところを往復するような構造になっています。
ドライバは不要、というよりは内蔵されていると言った方が正確で、電源とPWM信号を送れば制御することができます。

Arduinoから制御する

circuit

  • Arduinoのサーボ制御ライブラリServo.h
ArduinoではPWMサーボをコントロールするためのライブラリがあらかじめ用意されているので、それを使えば簡単に制御することができます。
回路としてはVCCをVinまたは5V、GNDをGND、コントロール用のピンをArduinoの出力ピンに接続します。
最大12個までつなげられ、0~13番・アナログの0~5番ピンでも使うことができます。

#include 

Servo servo1;

void setup() {
  servo1.attach(9);  // 9番ピンにサーボ出力を設定
}

void loop() {
  delay(1000);
  servo1.write(0);
  delay(1000);
  servo1.write(random(180)); //0~180度の間でランダムに角度を指定する
}

パルス幅と角度の指定

  • パルス幅と角度の関係は、メーカーによって異なることがある
     
  • attachで有効な角度の範囲をパルス幅に指定することができる
     
  • デフォルトは544~2400マイクロ秒
write関数では角度を指定できると書きましたが、これはメーカーの仕様によって異なる場合があります。
PWMサーボは「パルス幅」の時間が角度に対応するようになっており、Servoライブラリのデフォルトである544~2400マイクロ秒と違う場合は指示した角度と実際の角度が違って来ることになります。これはattach関数に追加で指示することができるので、データシートと照らし合わせながら設定すると良いでしょう。
  servo1.attach(9, 800, 2300); //メーカーの仕様に合わせてパルス幅を調整する

パルス幅を直接指定

  • writeMicroseconds()で直接指定することも可能
     
  • writeよりも細かく指示できる
また、attachで指示せず直接パルス幅を記述することもできます。その場合はwriteMicrosecondsという関数を使います。
例えばGWSのサーボ角度範囲は0~180度で800~2300usとなっています。(https://gwsus.com/gws_com_tw_www/english/product/servo/servo.htm) この場合write関数で指示できるのは180段階ですが、writeMicroseconds関数を使えば1500段階になります。サーボの性能にも依存しますがソフトウェア上はより高い解像度で角度を指示することが可能になります。
#include 

Servo servo1;

void setup() {
  servo1.attach(9);  // 9番ピンにサーボ出力を設定
}

void loop() {
  delay(1000);
  servo1.writeMicroseconds(800);
  delay(1000);
  servo1.writeMicroseconds(random(800, 2300)); //0~180度の間でランダムに角度を指定する
}

attachとdetach

  • attachを呼んだ瞬間モーターにトルクが発生する
     
  • dettachを呼ぶと脱力する(静音&省電力)
PWMサーボはattachを呼んだ瞬間から動作をはじめます。動く時にはギヤの音が鳴り、目的の位置に着いてからも「ジジジ…」と調整する音がなり続けます。これはトルクが常にかかり続けているためで、手で回そうとしても回らないか、すぐ戻ろうとします。物を持ち上げ続けるなど力が必要な場合はこのままで問題ありませんが、バッテリー駆動で電気を節約したい、止まっている間は音が鳴らないようにしたいなどの場合はdettachを呼ぶことで脱力させることができます。
#include 

Servo servo1;

void setup() {
  servo1.attach(9);  // 9番ピンにサーボ出力を設定
  Serial.begin(9600);
}

void loop() {
  while (Serial.available())
  {
    byte bt = Serial.read();
    if (bt == 'a') 
    {
      servo1.attach(9);
      servo1.writeMicroseconds(random(800, 2300)); //aを受け取ったらランダムの場所に動く
    }
    if (bt == 'd') servo1.detach(); //サーボのトルクを解除する
  }
}

ステッピングモーター

step

  • さらに高性能なモーター
     
  • 動きも制御もより複雑になる
最後により高性能なモーターであるステッピングモーターを紹介します。コントロール方法が複雑になりますが、その分できることも各段に多くなります。PWMと同じように任意の角度で止める事ができる一方で駆動範囲に制限はなく、DCモーターのようにどこまでも回し続けることができます。
更に適切に制御してあげることでとても静かに駆動させることもできます。

ステップを送る

  • DC/PWMモーターでは「速度」を指示していた
     
  • ステッピングモーターは1ステップごとにコイルの方向を制御する
従来のモーターとは制御構造が根本的に異なるため、このような動作ができるようになっています。DCモーターやPWMサーボは、指示した方向に基本的に動き続けるという動作をしますが、ステッピングモーターはマイコンArduinoから「1ステップ先に」動くという動作を指示します。時計の秒針をイメージすると分かりやすいと思います。そのため基本的には1ステップずつカクカクした動きで進んでいくのですが、この送り動作を高速に繰り返すことで滑らかな動きを実現することができます。

ステップ数と角度

  • 主な小型のステッピングモーターは200ステップで1週(1ステップ辺り1.8度)
     
  • より細かい動作はギヤで減速したり「マイクロステップ(後述)」という制御方法を使う
このステップという単位は、よく手に入る物だと1週200ステップ、1ステップ辺り1.8度の物が多いです。

ステッピングモーターの欠点

  • 速度やトルクが上がってくると「脱調」が起きる
     
  • 電源リセットや「脱調」で現在位置が分からなくなる
一見万能に思えるステッピングモーターですが、欠点もあります。最も大きな弱点が「脱調」という現象で、トルクが足りなかったりステップ速度が早すぎたりした時、軸が周り切らずステップを指示した数と実際の角度がズレてくる現象の事を言います。プログラムから指示したステップ数を数えておけば現在の角度は分かりますが、脱調が一度起きるとモーターが今どの位置を向いているのか分からなくなってしまいます。また電源を入れた時の位置が原点になるので、装置によっては初期位置を割り出すような仕組みを作ってあげる必要があります。

入手先

  • 42mm角の小型なら秋月やストロベリーリナックスで入手可能
     
  • もう少し大型の物が手に入る「シナノケンシ」
     
  • 有名&高品質な「オリエンタルモーター」
ステッピングモーターはDCモーターやPWMサーボがホビー~家庭用電子機器向けなのに比べて、
どちらかというと産業機器に使われる事が多いです。通常用途なら秋月やストロベリーリナックスのような電子部品屋でも購入することができますが、更に大型の物はシナノケンシやオリエンタルモーターのようなメーカーから購入することも可能です。

買う時の注意

  • 「バイポーラ」がおススメ。紹介するドライバもバイポーラ
     
  • 角タイプの方が取り付けやすい。NEMAという規格で定義されている
そしてステッピングモーターと一口に言っても色々な種類があります。もし購入を検討しているのであれば、6本線のユニポーラでは無く4本線のバイポーラをおススメします。ユニポーラは駆動回路が簡単の変わりにトルクが少ないというタイプなので、ドライバを買って使う分にはバイポーラタイプの物を使うのが配線も少なく、トルクも比較的強いので良いでしょう。
また円筒形のタイプが安いのでついそっちを買ってしまいがちですが、個人的には取付しやすい直方体タイプのものをおススメします。NEMAという規格に準拠したものが多いので、頻繁に使うようになった時には選択肢の幅が広がって便利です。

ステッピングモータードライバ

motorShield

  • ドライバが多い!(1000円~10万円台まで)

  • おススメは「L6470」モータードライバ

ステッピングモータードライバもいくつかあるので紹介します。ステッピングモーターについては、その制御の複雑さから、よりたくさんの種類のドライバが流通しています。先述のオリエンタルモーターでは自社のモーター用ドライバも生産しており、こちらはPCから直接制御もできて非常に高機能ですが2万~10万台までとかなり値が張ります。
Arduino用のシールドとライブラリ(Stepper.h)などもありますが、作品制作に用いるのであれば「L6470」を載せたドライバモジュールを強くお勧めします。

L6470ドライバモジュール

l6470

  • L6470というICを載せたモジュール(秋月やストロベリーリナックスなど)

  • 価格のわりに繊細なコントロールができる

L6470はSPI通信という通信方式で制御できるタイプのモータードライバなので、Arduinoから指示を送って動かす事ができます。1ステップ分のパルスを送ればその通りにステップ駆動をしてくれますし、「xxステップ動かして」という指示の仕方もできます。
また、作品やパフォーマンスに向いている機能として、加減速を指示した滑らかな動作や、アナログ出力を使って1ステップより細かく滑らかに動かす「マイクロステップ」の機能もついているため、繊細なコントロールが可能となります。これはArduinoのStepperライブラリなどでは難しい機能です。次週では、このステッピングモータードライバの使い方について実物を交えて解説していきます。

第十七項 モーター制御・DCモーター

第十七項 モーター制御・DCモーター

電子工作創作表現(2019/11/1)

スライドPDF

電気による駆動

  • 電気でものを動かす方法は基本的に「磁力」
     
  • コイルを複数組み合わせて動かすのがモーター
電気エネルギーを何かに変換して物を動かそうとするとき、一般的に普及しているものは基本的に磁力を使って動かします。コイル状の電線に電気を流すと磁界が生まれるので、その力を動力として応用しているのがモーターです。

モーターとドライバ

  • モーターは、得意な事に応じて制御方法が異なる
     
  • 使いたいモーターに応じてドライバを選定する
コイルに電気を流して回転動力を得る、というのがモーターの基本原理ですが、細かな構造は種類によっても異なります。構造の違いによって得意な事が変わってくるので、用途別に様々なモーターが開発されており、私達は使いたい要件に応じてそれらを適切に選ぶ必要があります。

同時に、モーターを動かすためのドライバにも種類があるので、モーターに合ったドライバも選ぶ必要があります。今回はいくつかのモーターとそのドライバの特性、使い方を解説していきます。

モーターの種類

  • DCモーター
     
  • PWMサーボモーター
     
  • ステッピングモーター
大きく分けて3つの種類を紹介します。

DCモーター

dc

  • 電気を流すだけで回るモーター
     
  • 流す方向で正転・逆転が変わる
     
  • 「ブラシ付きモーター」とも
まず最も構造がシンプルで制御も簡単なのがDCモーター。これは電気を流せば回るというものでブラシ(整流子)によって磁性が切り替わり回転動力を生み出します。電流を流す方向で正逆の向きを変えられるようになっています。

DCモーターの種類

  • 円筒型が基本形
     
  • ピンにギヤが付いて減速されるが力が強い「ギヤードモーター」も同じ
DCモーターは円筒型が多く、上面にネジ穴が付いているのでこれでパネルに取り付けることが多いです。ブラシの付いた軸がそのまま露出しているものに加えて、あらかじめギヤが取り付けられている「ギヤードモーター」というものがあるのですが、それらは回転速度が遅い分強い力でものを動かすことができます。

DCモーターの選定

  • 大きさ
     
  • 駆動電圧
     
  • 回転数
     
  • トルク
どんなモーターを選ぶかですが、主に上記4つのような要素があります。駆動電圧は6,9,12,24Vなどがあって、同じスペックで電圧の違う物などがありますので同時に使う他の機器と電源を共有できるように選定することができます。
回転数は駆動電圧をかけた時の回転する速さを表していて、rpmという単位が使われるのが一般的です。
ある程度重い物を動かしたい時にはトルクも見る必要があります。単位はN・mやkgf・mがよく使われます。

動作を切り替えるための「Hブリッジ」

h-bridge

プラスマイナスを繋ぎ変えれば回転方向を簡単に切り替えられますが、自動的に回路を繋ぎ変えるのは難しいです。そこで「Hブリッジ」と呼ばれる回路を使うと、スイッチをON/OFFすることでモーターに流れる電気の方向を切り替えることができるようになります。図ではスイッチを切り替えていますが、実際の駆動ではここにトランジスタやMOSFETが入ることで、マイコンから切り替えができるようになります。

画像:https://micro.rohm.com/jp/techweb_motor/knowledge/basics/basics-03/260

モータードライバ

  • 2つのINで正逆をコントロールする
     
  • 物によってはPWMによる速度コントロールも使える
デジタルで制御するとなると回路が複雑になりますが、それを簡単にできるようにしてくれているのがモータードライバです。
DCモーター用のドライバは、一般的に2つのインプットが付いており、片方をHIGH、片方をLOWにしてドライブし、逆転させたい時はHIGH・LOWの関係を逆にします。ドライバによっては、HIGH側のピンにPWMを載せることで速度コントロールのようなこともできます。

主なドライバの違い

  • 同時に動かせる数(1個か2個が多い)
     
  • 駆動電圧
     
  • 定格電流
     
  • 保護回路の有無
DCモーターだけでもたくさんのドライバがあって、値段もまちまちです。主に見ておくべきものとしては上記の要素があります。モーターや用途によって適切なものを選ぶ必要があります。

ドライバの例1

TB6643:比較的安く、制御もシンプル。1個だけ簡単に動かしたい時にはこれで十分

tb6643

http://akizukidenshi.com/catalog/g/gI-07688/
代表的なものをいくつか紹介します。まず1個目はTB6643という東芝のモータードライバICです。DCモーター1個を動かすことができ、保護回路もいくつかついています。値段も比較的安いので、とりあえず動かしたいというような時に便利なドライバです。

ドライバの例2

MC33926:Pololuなどが1個用、2個用のモジュール基板を出している。IC自体が小さいので小型化しやすい(発熱に注意)電流センサや逆起電力保護などが付いて高機能だが、やや高い(写真の物は4000円程)

mc

https://www.switch-science.com/catalog/581/
続いてもう一つが、freescale社のモータードライバMC33926です。これは単品では面実装ICなのですが、pololuなどがモジュール基板を作っています。薄いので小型化しやすく、電流センサが内蔵されていたり基板上に逆起電力保護機能などが付いているのでIC単体よりも高機能ですが、その分やや値段が高くなります。

モーターを動かす

  • サンプルはMC33926
     
  • ArduinoのOUTにドライバのINを接続
     
  • 駆動電源はVINに繋いで、必要な電圧をDCジャックに入れる
DCモーターを動かすためのサンプル基板&コードです。Arduinoの出力にモーター入力を入れて、電源ラインはArduinoのVINとつなぎます。ArduinoはDCジャックに18Vまで入れることで、VINを経由して5Vを超える電源を共有することができるようになっています。
#define MOTOR_A 9
#define MOTOR_B 10

void setup()
{
  pinMode(MOTOR_A, OUTPUT);
  pinMode(MOTOR_B, OUTPUT);

}

void loop()
{
  runMotor(true);
  delay(500);

  stopMotor();
  delay(500);

  runMotor(true);
  delay(500);

  stopMotor();
  delay(500);
}

void runMotor(bool dir)
{
  digitalWrite(MOTOR_A, dir);
  digitalWrite(MOTOR_B, !dir);
}

void stopMotor()
{
  digitalWrite(MOTOR_A, false);
  digitalWrite(MOTOR_B, false);
}

第十六項 エンベロープ

第十六項 エンベロープ

電子工作創作表現(2019/10/24)

スライドPDF

エンベロープを扱う

  • 前回はオシレータと、簡単な波形の合成
     
  • エンベロープを使ってもう少し楽器に近づける
先週に引きつづき、Mozziの解説をやっていきます。
今回もう少し楽器として実用可能な落とし込みを見せたいので、エンベロープについてやっていきます。

エンベロープの基本形・ADSR

  • 音の始まり方と終わり方を定義する
     
  • 音量のコントロールでニュアンスを変化させる
シンセサイザーを使っている人は良くご存じとは思いますが、エンベロープとは音が鳴り始めてからの音量の推移のことを表します。
例えばピアノは音が鳴ってから鍵盤を離すとすぐ音が途切れますが、ペダルを踏むと鍵盤を話しても音が持続したままになります。このような時間による音の変化を定量的に表すための「ADSR」というモデルがあります。

ADSRは何の略?

  • アタック(Attack)
     
  • ディケイ(Decay)
     
  • サステイン(Sustain)
     
  • リリース(Release)
ADSRはそれぞれの略で、上のような意味があります。

変化のグラフ

ADSR

グラフに表すとこのようになります。(出展:https://www.perfectcircuit.com/signal/learning-synthesis-envelopes-1)
アタックで最初に勢いよく音量が上がった後にディケイで少し音量が下がります。その後Sustainで延ばせる時間まで伸びた後リリースの時間かけて徐々に音が小さくなります。
情報としては、ADSR四項目それぞれの最大の長さと、Attack後、Decay後のボリュームが定義されています。

ADSRクラス

  • オシレータにボリュームの変化を与える
     
  • noteOnを呼べばその通りに鳴らしてくれる
概念としてはそんなに難しくないですが、これをプログラムで実装しようとするとやや煩雑です。MozziではADSRクラスが用意されているので、それを使うことで前回やったオシレーターに対して簡単にエンベロープをかけることができるようになっています。まずはこちらのスケッチを参考にしてみましょう。

オシレーターを用意するところまでは前回同様です。setup内のsetADLevelsでアタックとディケイの音量を定義して、setTimesでADSRそれぞれの時間を入れます。スイッチを11ピン~13ピンに繋ぎ、押された時にnoteOnを呼び出すことで簡単な鍵盤を実現することができます。
#include <MozziGuts.h>
#include <mozzi_midi.h>
#include <Oscil.h>
#include <EventDelay.h>
#include <ADSR.h>
#include <tables/saw8192_int8.h>

Oscil <8192, AUDIO_RATE> aOscil(SAW8192_DATA);//元となる音色
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数

void setup() {
  startMozzi(64);
  aOscil.setFreq(440);
  Atk = 10;
  Dec = 10;
  Sus = 100;
  Rel = 500;
  envelope.setADLevels(255, 128);
  envelope.setTimes(Atk, Dec, Sus, Rel);

  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);
}

void updateControl()
{
  //ボタンが押されたらエンベロープのnoteOnを呼ぶ
  if (digitalRead(11) == LOW)
  {
    aOscil.setFreq(mtof(60));
    envelope.noteOn();
  }
  if (digitalRead(12) == LOW)
  {
    aOscil.setFreq(mtof(62));
    envelope.noteOn();
  }
  if (digitalRead(13) == LOW)
  {
    aOscil.setFreq(mtof(64));
    envelope.noteOn();
  }
}

int updateAudio()
{
  envelope.update();
  return (int)(envelope.next() * aOscil.next())>>8;
  //エンベロープのボリュームは0~256なので、かけてからビットシフトをする
}

void loop() {
  audioHook();
}

mtof関数

  • MIDIノートを周波数に変更してくれる(Midi to Freqの略)
     
  • 64が国際式表記のA4で440Hz
     
  • 88鍵の音域は21~108だが、レンジ自体は0~127まで存在する
押されるボタンでオシレータの周波数を変えていますが、ここではmtofというmozziの関数を使っています。
MIDIのノート番号を周波数に変換する関数なので、音階を使いたい時にはこの関数を使うのが便利です。

mtofで音階を制御

  • ノートで指定できるので、スケールなども作りやすい
     
  • ランダムにペンタトニックを鳴らすスケッチ
配列変数としてノート番号を書いておけば、メロディやスケールを自由に定義して鳴らすことも可能です。
以下のコードは、あらかじめ変数pentaにペンタトニックのスケールを保存しておき、間を開けてランダムにnoteOnを呼び出すスケッチです。
#include <MozziGuts.h>
#include <mozzi_midi.h>
#include <Oscil.h>
#include <EventDelay.h>
#include <ADSR.h>
#include <tables/saw8192_int8.h>

Oscil <8192, AUDIO_RATE> aOscil(SAW8192_DATA);//元となる音色
ADSR <AUDIO_RATE, AUDIO_RATE> envelope;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数

EventDelay timer;
unsigned int penta[] = {0, 3, 5, 7, 10};

void setup() {
  startMozzi(64);
  aOscil.setFreq(440);
  envelope.setADLevels(255, 128);
  Atk = 10;
  Dec = 10;
  Sus = 100;
  Rel = 500;
  envelope.setTimes(Atk, Dec, Sus, Rel);

  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);

  timer.set(300);
  timer.start();
}

void updateControl()
{
  if (timer.ready())
  {
    //note変数に60(C4)から始まるペンタスケールをランダムに定義する
    int note = 60 + penta[int(random(100) % 5)];

    //オシレータの周波数を変更
    aOscil.setFreq(mtof(note));
    envelope.noteOn();

    //次に音を出す時間を決める(noteの音程で4分か8分の2パターン)
    if (note % 2 == 0) timer.set(300);
    else timer.set(150);
    timer.start();
  }
}

int updateAudio()
{
  envelope.update();
  return (int)(envelope.next() * aOscil.next())>>8;
  //エンベロープのボリュームは0~256なので、かけてからビットシフトをする
}

void loop() {
  audioHook();
}

EventDelay

  • Mozziのために用意されたディレイ関数
     
  • delay関数を使うと音が停まってしまう
ここで新しくEventDelayというクラスが出てきました。これもMozziを使う上でかなり便利なクラスです。ArduinoデフォルトのDelay関数では全ての処理が停まるため、オーディオの出力もストップします。それを回避しながら一定時間経過後に処理を走らせることができます。

EventDelayを使った繰り返し処理

  • setTimeを使って時間を設定し、startで計測開始
     
  • if (timer.ready())で経過したかどうか判定
setTimeで時間を設定し、startを呼び出すとタイマーが動き始めます。経過したかどうかはreadyメソッドで確認できるので、readyをif文に入れて、時間経過で呼び出したい処理を書くようにします。
readyになったらまたstartを呼び出すようにすれば繰り返し処理が行われますし、都度setTimeを呼ぶことでタイミングを変化させることもできます。

複数のEventDelay

  • アルゴリズミックな演奏をプログラムで表現することができる
     
  • ライヒの「Piano Phase」を再現してみる
これによってアルゴリズミックなメロディをArduino内にプログラムすることができるようになります。
複数のEventDelayを平行して走らせることもできるので、独立した演奏プログラムを走らせたりすることもできます。下のスケッチでは、スティーヴ・ライヒの「Piano Phase」の序盤を再現してみました。譜面のパターンを配列変数に格納して、EventDelayが発火したタイミングで順番に鳴らしていっています。1ミリ秒ごとにずれていく様子を聞くことができます。本家では途中から違う譜面になり終わり15分ほどで終了しますが、プログラムなので電源を供給する限り永遠に演奏が続きます。
#include <MozziGuts.h>
#include <mozzi_midi.h>
#include <Oscil.h>
#include <EventDelay.h>
#include <ADSR.h>
#include <tables/sin8192_int8.h>

Oscil <8192, AUDIO_RATE> aOscil(SIN8192_DATA);//元となる音色
Oscil <8192, AUDIO_RATE> aOscil2(SIN8192_DATA);//元となる音色
ADSR <AUDIO_RATE, AUDIO_RATE> envelope_A;//エンベロープをかけるためのクラス
ADSR <AUDIO_RATE, AUDIO_RATE> envelope_B;//エンベロープをかけるためのクラス
unsigned int Dur, Atk, Dec, Sus, Rel;//ADSRの長さを入れておく変数

//piano phaseのパターン
unsigned int pattern[] = {64, 66, 71, 73, 74, 66, 64, 73, 71, 66, 74, 73};

//二つのタイマーを用意
int phase_A = 0;
int phase_B = 0;
EventDelay timer_B;
EventDelay timer_A;

void setup() {
  startMozzi(1024);
  Atk = 10;
  Dec = 10;
  Sus = 50;
  Rel = 100;
  envelope_A.setTimes(Atk, Dec, Sus, Rel);
  envelope_A.setADLevels(255, 128);
  envelope_B.setTimes(Atk, Dec, Sus, Rel);
  envelope_B.setADLevels(255, 128);

  timer_A.set(150);
  timer_B.set(151);
  timer_B.start();
  timer_A.start();
}

void updateControl()
{  
  if (timer_A.ready())
  {
    int note = pattern[phase_A];
    phase_A = (phase_A + 1) % 12;
    aOscil.setFreq(mtof(note));
    envelope_A.noteOn();
    timer_A.start();
  }

  if (timer_B.ready())
  {
    int note = pattern[phase_B];
    phase_B = (phase_B + 1) % 12;
    aOscil2.setFreq(mtof(note));
    envelope_B.noteOn();
    timer_B.start();
  }
}

int updateAudio()
{
  envelope_A.update();
  envelope_B.update();
  int A = (envelope_A.next() * aOscil.next()) >> 8;
  int B = (envelope_B.next() * aOscil2.next()) >> 8;

  return (A + B) / 2;
}

void loop() {
  audioHook();
}