続々・Propeller日記(2)

長嶋 洋一


Propeller日記(1)

Propeller日記(2)

Propeller日記(3)

Propeller日記(4)

Propeller日記(5)

続・Propeller日記(1)

続・Propeller日記(2)

続・Propeller日記(3)

続・Propeller日記(4)

続・Propeller日記(5)

続々・Propeller日記(1)

続々・Propeller日記(3)

続々・Propeller日記(4)

2013年1月8日(火)

遂に「続々」のPropeller日記もpart2になってしまった。 SUAC基板が届いた12/21に part1 を始めたので、まさに part1 は年末年始をまたいだところである。 果たして「続々」でどこまで行けるのか、ちょっと楽しみである。

昨日はM2の伊熊さんの修了制作の検収が終わり、次は口頭試問である。 その日程調整や会場となる瞑想空間の予約、さらに2年目に入る学部長特別研究の申請と、 明日の講義再開を前にして、事務仕事も増えてきた。 アルスエレクトロニカ2013の作品募集案内も届いた。参加することに意義がある(応募すると後日にDVDが届く)、 と毎年応募しているからである。とりあえず今回は、MOM2012で発表した作品を応募しよう。 今日の午後には3ヶ月に一度のクスリを貰いに医者に行ったり、3回生の藤本さんの相談アポもある。 コマ切れの一日となりそうだが、コマ切れなりにPropellerも進めたい。 昨日の結果を受けて、次回版の基板に向けての申し送りメモは以下のようなっている。

■SUAC board ver.0.5からの改善点リスト■

●バグなので次バージョンで必ず改訂する項目
・XBeeのDinとDoutが逆(^_^;)なので入れ替える
・2個の138のG入力の2本にpull-up抵抗が必要
・↑そのpull-up先の電圧を+5Vとするか+3.3Vとするか、のジャンパが必要
・2個のAD7829の出力イネーブル(CS/RD)の2本はpull-down抵抗が必要

●必須ではないが改訂することになれば盛り込みたい項目
・電源コネクタ
・電源給電表示LED
・半固定抵抗の穴を2mmφにする
・半固定抵抗の中央穴の位置を0.1インチ離す

●現行SUAC board ver.0.5の増産時に対応すること
・XBeeのDinとDoutが逆なので基板配線面をカットして修正する
・CS1/CS2(2個のAD7829の出力イネーブル(CS/RD))を4.7kΩでpull-down
・2個の138のG入力の2本に4.7kΩのpull-up抵抗
	→そのpull-up先の電圧はホストに対応して+5Vまたは+3.3V

●その他メモ
治具が完成し動作が確認できたパラレル出力ポートで、 PropellerアセンブラによってPWM制御するライブラリの開発、という大ネタは、まだ先である。 しばらく寝かせておきながら、アルゴリズムを無意識下に検討していきたい。 となれば、次は「パラレル(ディジタル)入力」の拡張機能部分のチェックである。 これは最初から、以下のようにGNDとショートするワニグチクリップによって、 端子を次々と触っていったところをチェックする、という方針が決まっていた。 そのために、コネクタもオスにして、電極が露出するようにしたのである。

上の写真は作業の前に撮ったヤラセの写真であり(^_^;)、まだ何も起きない。 ちょうどLEDボード治具が出来たので、今回はMIDIもXBeeもビデオ出力もナシで、 入力の64ビットの状態をこのLEDの64ビットで表示する、というシンプルな動作にしてみよう。 プログラムとしては、既に作った「outport_check.spin」を改良して、 スタンドアロン動作の「input_port_check.spin」というカンジになる。 ただし、拡張バスは入力と出力とを刻々と切り換える、という、内部的には高度な動作である。

そして以下のように、たった「33ワード(longs)」という短いプログラムが出来て、 ポート入力の動作を確認した。 ディジタル入力ではお約束のpull-upによって、GNDとショートするスイッチ入力の論理は負論理となるので、 ビットを反転するのに「$FF」とXOR(排他的論理和)をとっているのが、定番のテクニックである。 1ビットだけ、例によってミニフラットパッケージのハンダ不良らしいビットを発見したので、 またまたフラックスとハンダにより、無事にこちらのチェックも終了した。

CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

PUB start | k, i
  dira[23..8]~~                 ' output
  outa[23..19] := %10011
  repeat k from 0 to 7
    port_out(k, 0)

  repeat
    repeat k from 0 to 7
      port_out(k, port_in(k))

PUB port_in(address)
  dira[15..8]~                  ' input
  outa[18..16] := address
  outa[20] := 0
  address := $FF^ina[15..8]
  outa[20] := 1
  return address

PUB port_out(address, data)
  dira[15..8]~~                 ' output
  outa[18..16] := address
  outa[15..8] := data
  outa[19] := 0
  outa[19] := 1

YouTube

入力もLED出力もビット単位であるが、実際には8ビットの塊としてまとめて処理するので、 例えばビット単位でのイベント(変化)を抽出する場合には、 内部的に8ビット幅のデータを保持しておいて、該当ビットについての処理の後で、 他のビットの保持値と組み合わせる、というテクニックも必要になる。

このYouTubeを上げたところで昼休みとなり、13時前には医者に出かけて、 採血してから1時間、検査結果待ちで待機して主治医の定期検診、その後にクスリを出してもらって研究室に帰ったのは15時であった。 今日はPropellerは区切り的にはここまで、というカンジである。 明日には、ぼちぼちA/D入力のチェックに進んでいきたい。 そこまでチェックすると、Propellerでの基板チェックは、ほぼ完了となり、次にはArduino、そしてGainer、最後にAKI-H8、 とホストマイコンを換えてチェック、という段取りである。

・・・そしてこの後、3回生の藤本さんが来て、総合演習Iで企画しているインスタレーション作品について相談した。 自宅でArduinoとかを本を頼りに勉強しているということで、1個のLEDを点灯させるまでは行ったとのことで、 いろいろと相談して、6個の白色LEDをPWM点灯させ、さらに6個の振動モータをArduinoで制御する、 という方針が決まった。 そして約3時間半、以下のように一気に完成したものを渡したところでオシマイとなった。 なかなか充実の一日である。(^_^)

2013年1月9日(水)

いよいよ講義再開の日である。 いつもであれば1・2・4・5限が埋まっているキツい日であるが、祝日の関係で「月曜講義日」ということで、 放課後のアカペラまで時間が十分にある。 ここは気合いを入れて、64ビットPWM出力のためのライブラリに挑戦してみよう。

64個のLEDを連続量点灯制御(PWM)、といえば、SUAC開学の直後に制作した、 「靄夜」(もや) のシステムである。 ホストのAKI-H8のプログラムは これ である。 MIDI入力に割り込みを使うのはいつもの事だが、このAKI-H8プログラムではいつものソフトタイマーでなく、 内蔵タイマーによるハードウェア割り込みを使用して正確なタイミング制御を行った。 僕にしては珍しく 回路図・MIDIプロトコルタイミング図 も描いて、普段よりも相当にチューニングを追求して、高速処理の極限を狙ったものであった。

この「靄夜」システムのAKI-H8プログラムをそのまま活用したのが、 去年の院生・見崎さんの修了制作のインスタレーション作品「OTOcakecco」である( プレゼンYouTube )。 ここでは3層に各7個、計21個の球体(ドレミの7つの音階に対応)があり、 それぞれの球体に3色LEDが内蔵されてフルカラー表示を行う。 つまり全ビット数は21*3=63個、とちょうど64個の「靄夜」システムとぴったりだったのである(^_^)。 そこで、24チャンネル(21個+予備)の衝撃センサ回路のためにさらに別にAKI-H8を搭載して、 と制作していったのだった。

「靄夜」システムでは、AKI-H8の豊富なポートをフルに使って、64ビットを全て、そのままLED出力として出している。 しかし今回のSUAC基板では、昨日までやったように、拡張データバスとラッチとで分割されているので、 AKI-H8を搭載しても、同じプログラムは作れない。 やや手間がかかるのは確実なので、「靄夜」システムよりも若干パフォーマンスは低下することになる。 しかしそれは先のことで、まずはPropellerで、これと同等の機能を実現してみたい。 当然、制御側からの情報は、以下のように「靄夜」のMIDIプロトコルと互換にしたい。

  • MIDIステータス = アフタータッチ (ポリフォニックプレッシャー)
  • MIDIチャンネル = 16 (status=0xAF)
  • ノートナンバ = 0 - 63
  • データ = 0 - 127
  • 各LEDはそれぞれ、128*Tの周期ごとに上記 data*T の値だけ点灯する。データ=1ならば 128*Tごとに1*Tだけ点灯することで、いいカンジにチラチラする。(^_^)
  • 実際にはデータが半分以上になると点灯が飽和傾向にあるので、データは必要に応じて 変換テーブルで補正する。これは定番テクニック
AKI-H8では割り込みを使ったが、割り込みの概念の無いPropellerでは、およそ以下のような作戦となるだろう。 これはここ2日ほど、寝かせている間に頭の中で整理されてきたものである。
  • LEDのPWMのために、専用のCogを確保して、アセンブラで並列処理を記述する
  • メインのCog(0)のメインループで、直接にLEDのビット出力の処理をしない。これをすると、ビデオ・グラフィック表示のように、処理が重なると遅くなる
  • そのために、MIDI/XBeeと同様に、PWMライブラリとのインターフェースにはFIFOバッファを用いて、LED表示処理から戻るのを待たないようにする
  • PWM処理のCogについては、ソフトタイマでの待ち合わせをせずに、FIFO処理以外は最高速度となるようにループを回す
  • そのために、PWMのパルス幅は絶対値でなく、ポーリング周期を分割する「比」の相対値とする

まず最初に、昨日のLED出力Propellerプログラムに、それ以前に開発したMIDIの送受信を加えて、 「MIDIから64個のLEDを個別にON/OFFする」というプログラム「board_008.spin」を作ってみた。 MIDIプロトコルだとデータが7ビット幅なので、OLEDモジュールの時と同様に、 1ポート8ビットのデータ形式には無駄が出て来るが、まぁLEDの点灯であれば、人間の視覚のレイテンシの方が大きいので、まったく問題ない。 これは過去に、メディア造形学科の学生インスタレーション作品でいくつも実現してきたものと同等であり、 今後、このSUAC基板を使えば、これもかなり簡単に実現できることになる。

MIDIプロトコルとしては、8ポートを上下4ビットずつに分割して、 チャンネルプレッシャー(Aftertouch : Dn dd)の1-16チャンネルの値として、1ポートあたりMIDIチャンネルを2つ、という使い方にした。 プログラムチェンジでもいいのだが、Propellerプログラムの冒頭のセットアップ時に、トータルCogs数をMIDIで返すのとの重複を避けた。 MIDIで送る方のMaxプログラム「SUACboard_test008.maxpat」は、 8ビット(0-255)の値を4ビットずつに分割(16で割った商と、16の剰余)して送るだけであるが、 Propellerで受ける方では、8ビットでLED表示するのに、MIDIで受けたデータの片割れ(残りの方の4ビット)を保持しておいて、 変化のあったデータだけ反映させる、という定番テクニックが必要となる。

このプログラミングは30分ほどで完成し、以下のようになった。 ここでお昼休みとなり、放課後のアカペラまで、いよいよ午後はプログラミング三昧である。(^_^)

CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  midiIn        : "MidiIn03"
  midiOut       : "MidiOut01"

PUB start | i, ch, dummy, buff[16]
  port_initial(0)
  midiOut.start(25)
  dummy := midiIn.start(24)
  midiOut.fifoset($C00000 + dummy & $00007F)  ' display total number of Cogs

  repeat i from 0 to 15
    buff[i] := 0

  repeat
    dummy := midiIn.event
    if dummy <> -1
      midiOut.fifoset(dummy)
      if (dummy & $F0FF00) == $D00000
        ch := (dummy & $0F0000) >> 16
        buff[ch] :=dummy & $00000F
        port_out(ch/2, buff[ch & %1110]<<4 + buff[(ch & %1110) + 1] )

PUB port_initial(data) | i
  dira[23..8]~~                 ' output
  outa[23..19] := %10011
  repeat i from 0 to 7
    port_out(i, data)

PUB port_out(address, data)
  outa[15..8] := data
  outa[18..16] := address
  outa[19] := 0
  outa[19] := 1

さて、とっっても久しぶりのPropellerアセンブラプログラミングである。 アセンブラについては、ほとんど段取りから忘却しているが(^_^;)、まずは環境作りである。 上の「board_008.spin」をコピー・リネームした「board_009.spin」がメイン、 さらにMIDI送信のライブラリ「MidiOut01.spin」をコピー・リネームした「PwmPortOut01.spin」を作った。 基本的には、インターフェースとしてなるべくMIDI処理のハンドリングを踏襲していこう。

まず第1ステップとして、ホスト側は「SUACboard_test008.maxpat」のままにして、 PWMライブラリ側に「port_initial(data)」と「port_out(address, data)」を移してみた。 アセンブラの部分はエントリーだけ残して、全てコメントアウトしていて、何もしない。 ホストに返る値「4」から、確かに新しくCogが定義されていることが確認できた。 「board_008.spin」は以下である。

CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  midiIn        : "MidiIn03"
  midiOut       : "MidiOut01"
  pwmOut       : "PwmPortOut01"

PUB start | i, ch, dummy, buff[16]
  midiOut.start(25)
  pwmOut.start
  pwmOut.port_initial(0)
  dummy := midiIn.start(24)
  midiOut.fifoset($C00000 + dummy & $00007F)  ' display total number of Cogs

  repeat i from 0 to 15
    buff[i] := 0

  repeat
    dummy := midiIn.event
    if dummy <> -1
      midiOut.fifoset(dummy)
      if (dummy & $F0FF00) == $D00000
        ch := (dummy & $0F0000) >> 16
        buff[ch] :=dummy & $00000F
        pwmOut.port_out(ch/2, buff[ch & %1110]<<4 + buff[(ch & %1110) + 1] )
「PwmPortOut01.spin」は以下である。 アセンブラの部分は省略している。
VAR
  long tx_Head, tx_Tail, tx_Buff[64]

PUB start : status
  tx_top := @tx_Head
  tx_end := @tx_Tail
  tx_fifo := @tx_Buff
  longfill(@tx_Head,66,0)
  status := cognew(@asm_entry, 0)

PUB fifoset(_tx_data)
  tx_Buff[tx_Head] := _tx_data
  tx_Head := (tx_Head + 1) & $3F

PUB port_initial(data) | i
  dira[23..8]~~                 ' output
  outa[23..19] := %10011
  repeat i from 0 to 7
    port_out(i, data)

PUB port_out(address, data)
  outa[15..8] := data
  outa[18..16] := address
  outa[19] := 0
  outa[19] := 1

DAT
                        org
asm_entry
{
・・・(略)・・・
}

t1                      long    0
t2                      long    0
tx_top                  long    0
tx_end                  long    0
tx_fifo                 long    0
tx_data                 long    0
event_data              long    0

                        fit
ホストのMaxの「SUACboard_test008.maxpat」に対して、上のプログラムは同等に動作するので、 これで、PWMライブラリと組み合わせても、「port_initial(data)」と「port_out(address, data)」でなくて、 「pwmOut.port_initial(data)」と「pwmOut.port_out(address, data)」と呼び出せる。 つまり上位互換であり、メインループからライブラリを経由して、直接にLEDのON/OFFも出来ることになる。

ただし、このプログラムは動作しているものの、さらに本質的な改訂が必要であると気付いた。 FIFOで送ることもあり、ポート出力はMIDIと同様にイベントドリブン(最後の書き込み状態が続く)となるが、 PWMとON/OFFとを共存させるには、このメソッドからON/OFFを書き込んだ時には、 単にポート出力を出すのでなく、ライブラリ内部のPWM値を最大かゼロに書き換える必要がある。

また、XBeeでやったように、MIDIで受けたメッセージをそのままFIFOに積むのは非効率である。 メインのMIDI受信部分でチェックしているので、「靄夜」互換のデータプロトコルはそこで確認しているので、 PWMライブラリには生のデータをFIFOで送ればよい。

自分が作ったとはいえ、Propellerアセンブラを眺めてみると、まったくピンと来ない。忘却の彼方である(^_^;)。 それでも眺めているうちに、「MidiOut01.spin」をベースにするだけでは駄目だ、と気付いた。 「MidiOut01.spin」の構造は、

  • 初期化
  • FIFOで受け取るデータをチェック
  • データがあれば処理ルーチンをコール
  • データが無ければFIFOチェックに戻る(無限ループ)
  • 処理ルーチン内でシリアル信号をソフト生成して送信
  • 送信が完了するとFIFOチェックに戻る(無限ループ)
というものである。 Cog(0)のメインから、MIDI受信に対応してパラメータを受け取るのは、この前半でOKであるが、 それではPWM処理が走らない(^_^;)。 調べてみると、MIDI受信ライブラリ「MidiIn03.spin」の構造は、
  • 初期化
  • シリアル受信ビットをモニタ
  • 変化が無ければシリアル受信ビットのモニタに戻る(無限ループ)
  • 変化があればスタートビットと解釈してデータ8ビットとストップビットまで待ち合わせて取得
  • MIDIプロトコルの解釈をして、メッセージが完成すればFIFOに積む
  • シリアル受信ビットのモニタに戻る(無限ループ)
という構造をしている。 今回のPWMライブラリでは、FIFO受信のチェックと、タイマ処理によるPWM書き込みと、 2つの無限ループが必要なので、こちらも参考になるのだ。

・・・などと眺めているうちに、次第にPropellerアセンブラの雰囲気が蘇ってきた。 CPUについては「覚えない」、頭をRAMにする、という主義の本領発揮で、このカンジは、なかなか気持ちいいものである。 ただし、実験的にコンパイルしてみると、おそらく怒濤のエラーが出て来て難儀・後悔するのである(^_^;)。 そこで、上のバージョンは今後の失敗退却に備えて(^_^;)残しておくことにして、 「board_009.spin」をコピー・リネームした「board_010.spin」がメイン、 そして「PwmPortOut01.spin」をコピー・リネームした「PwmPortOut02.spin」を作った。 また、ホストのMaxの「SUACboard_test008.maxpat」をコピー・リネームした「SUACboard_test009.maxpat」を作った。

・・・そして、ここから午後一杯、初代の「Propeller日記」を読み返す時間に費やされた(^_^;)。 もっとも難しい、 Propeller日記(3) の後半から Propeller日記(4) の前半あたりである。 ArduinoやAKI-H8のアセンブラとはまったく違う感覚に、一歩も進まないまま、復習とお勉強と試行錯誤の時間になった。

そして新たに気付いたのは、考えてみればメインのCog(0)からこのPWMライブラリにパラメータを渡すには、 別にFIFOでなくてもいい、という当たり前の事実だった。 MIDIやXBeeのシリアル通信であれば、刻々と与えられたデータに対応して処理する必要があるが、 LEDポートのPWMデータ(ONとOFFの比)というのは、最新の値があればいいだけなので、 別にFIFOに積まなくてもいいのである。

そして、これまで稼働していたMIDIライブラリに軽いバグも発見した。 MidiInとMidiOutの冒頭で、

longfill(@rx_Head,66,0)
としていたが、正しくはパラメータの順序が逆で、
longfill(@rx_Head,0,66)
であった。まぁ、正しく初期化されなくても動いていたので、 どたばたとライブラリを更新せずに、とりあえずここにメモするだけで放置することにした(^_^;)。 そして18時になり、アカペラ新年初日となった。 中途半端なところで止まったが、それはそれで、またどこか頭の奥底で熟成して、新たに取り組めることを期待しよう。

2013年1月10日(木)

この日は朝10時前にディーラーに車を持っていって翌日までの車検に預けて代車で大学に戻り、 4限には来週末のセンター試験の連絡会議、さらに5限には再び3回生の藤本さんのアポ、 とだんだん予定が充実してきた。 事務作業としては、学内の特別研究の申請期限が来週前半なので、こちらも片付けないといけないが、 どうにもこの中途半端な状態が嫌なので、覚悟の後回しで、引き続きのPropellerプログラミングである。

・・・と書いてから数時間が経過して夕方になった。 5年前の Propeller日記(3)Propeller日記(4) の頃には理解していた筈なのに忘却していた事を2点、ここでメモとして残しておこう。 まずは「異なるCog間の通信は、HubのRAMを介して行う」という事である。 これはPropellerアーキテクチャから当然なのだが、spinだけで書いていると、ふと忘れてしまうのだった。 FIFOで積むMIDIやXBeeだけでなくても、HubのRAMを経由して、別途にcognewした別のCogと通信できるが、 これはハードウェア並列処理のための待ち合わせとして、「7-22クロック」と幅のある待ち合わせになる。 Cog内のRAMアクセスが常に4クロックなのとは対照的である。

そしてもう1点は、入出力ポートのdir設定とデータの書き込みである。 spin言語で入出力ポートのdir設定を行う場合には、 例えば今回のSUACボードで、ポート8からポート23までを「出力」に設定するには、

dira[23..8]~~
あるいは
dira[23..8] := $%1111111111111111
などとする。ところがこれがPropellerのアセンブラだと、「%1111111111111111」が変数「t1」に入っているとして、
shl t1, #8
mov dir, t1
では駄目なのである。ここに引っかかって悩んでしまった(^_^;)。 Propellerのポートは、全32ポートがそのまま並んでいるので、設定したいビット以外を変更しないようにするので、 設定したいピットだけ1に立てた変数をマスクとして、以下のように処理する必要がある。 そのために「andn」という命令があり、spin言語では以下のように展開しているわけである。
shl t1, #8
andn dir, t1
or dir, t1
分って(思い出して)しまえば何でもないが、久しぶりのPropellerアセンブラにあれこれ苦闘して、 そして、ようやく光明が見えてきた(^_^)。 だいぶアセンブラの感覚も蘇ってきて、まだバグがあるものの、既に64個のLEDは個別にPWM制御できた。 ちょっと気になるバグが残っているが、規則性があるバグなので、ここまで来れば、明日には攻略できそうである。

4限の会議が早めに終わったので連絡して、藤本さんは速攻で1106にやってきた。 そして、彼女のインスタレーション作品のためのArduinoも、 6個の白色LEDのPWM点灯、6個の振動モータのON/OFFに加えて、 2個の照度センサを取り付けて、あとは作品としての構想とプログラミング、 そして何より重要な「造形」制作、というところまで進んだ。 以下は、そのもっともシンプルなArduinoサンプルプログラムである。 Propellerをやっていて言語が混乱して、何度もArduinoのサイトのReferenceを調べつつ思い出した(^_^;)。 敢えてこれだけ渡して、せっかくなので自力でプログラミングに挑戦してもらって、 ヘルプが来たら手伝うことにした。

int brightness = 0;
int counter = 0;
int sensorValue0, sensorValue1;

void setup()  {
  for (int i=2; i<14; i++){
    pinMode(i, OUTPUT);
  } 
}

void loop()  {
  sensorValue0 = analogRead(A0);
  sensorValue1 = analogRead(A1);
  if (sensorValue0 > 512){
  }
  else{
    LED_control();
  }
  if (sensorValue1 > 512){
    motor_control();
  }
  else{
  }
  delay(10);
}

void LED_control(){
  brightness++;
  if (brightness>255) {
    brightness = 0;
  }
  analogWrite(3, brightness);    
  analogWrite(5, brightness);    
  analogWrite(6, brightness);    
  analogWrite(9, brightness);    
  analogWrite(10, brightness);    
  analogWrite(11, brightness);    
}

void motor_control(){
  counter++;
  if (counter==90) {
    digitalWrite(2, HIGH);
    digitalWrite(4, HIGH);
    digitalWrite(7, HIGH);
    digitalWrite(8, HIGH);
    digitalWrite(12, HIGH);
    digitalWrite(13, HIGH);
  }
  else if (counter==100) {
    digitalWrite(2, LOW);
    digitalWrite(4, LOW);
    digitalWrite(7, LOW);
    digitalWrite(8, LOW);
    digitalWrite(12, LOW);
    digitalWrite(13, LOW);
    counter = 0;
  }
}
4限にあった、来週のセンター試験に向けた監督ミーティングでは、今年はインフルエンザに加えてノロウイルスも注意、 ということで、無事につつがなく進むことをひたすら祈りましょう、という感じであった(^_^;)。 センター試験があれば、次にはまたまたSUACの入試、院試である。 今年もまた、意欲ある学生がメディア造形学科に来る事を期待しよう。

2013年1月11日(金)

この日は学生からのちょっとしたアポが3件ほどあったが、まずまず研究室での作業が進んだ。 昼休みには事務局に行って、ちょっと嬉しい知らせが届いたが、これもお披露目は新学期である。 デザインフェスタのワークショップに関する、講師の小林さんとのやりとりなどもメイルで数往復、進んだ。

昨夜も夜中にフト目覚めて、問題点をメモして攻略指針は立っていたので、さっそくプログラミングに取りかかった。 ・・・そして遂に、昨日から持ち越していたバグも取れて、無事に「64ビット個別PWM制御」は完成した。 以下はその模様である。

出来てしまえば何でもないが、ここまで来るには色々と試行錯誤して、次第にPropellerアセンブラが蘇ってきた。 結論としては、ホストのMacから制御するMaxパッチもちょっと気合いを入れて頑張って、 AKI-H8で作ったシステムよりも、はるかに協力で柔軟な性能を実現してしまった。 以下のYouTubeでは、その機能をデモした様子を紹介している。

YouTube

まず、このPropellerプログラム「board_010.spin」とPWMライブラリ「PwmPortOut02.spin」では、 ホストのMaxパッチ「SUACboard_test009.maxpat」からMIDIにより、以下のパラメータで制御できる。

  • 全部のLEDを一斉に制御
    • MIDIステータス = チャンネルプレッシャー
    • MIDIチャンネル = 2 (status=0xD1)
    • データ = 0 - 127
    • 全LEDは、128*Tの周期ごとに上記 データ*T の値だけ点灯する。データ=1ならば 128*Tごとに1*Tだけ点灯する
  • PWM処理のスキャンスピードの制御
    • MIDIステータス = チャンネルプレッシャー
    • MIDIチャンネル = 3 (status=0xD2)
    • データ = 0 - 15
    • データは、スキャン周期のビットマスクの左シフト値。つまり0で最高速度、1でその1/2の速度、2で1/4の速度、・・・と遅くなり、データ最大値の15では1/2^15なので最高速度に対して1/32768と非常に遅い周期でPWM制御を行う
  • 個別のLEDのPWM制御
    • MIDIステータス = アフタータッチ (ポリフォニックプレッシャー)
    • MIDIチャンネル = 16 (status=0xAF)
    • ノートナンバ = 0 - 63 : 64個のLEDに対応 : [0-7]がポート0のビット0-7、[8-15]がポート1のビット0-7、・・・、[56-63]がポート7のビット0-7
    • データ = 0 - 127
    • 各LEDはそれぞれ、128*Tの周期ごとに上記 データ*T の値だけ点灯する。データ=1ならば 128*Tごとに1*Tだけ点灯する
    • このMIDI制御情報は、確認用にそのままPropellerからホストに同じデータとして送り返される
上のYouTube動画にあるように、今回のMaxパッチ「SUACboard_test009.maxpat」では、
  • 全部のLEDを一斉に制御
  • PWM処理のスキャンスピードの制御
  • ポートごとに全てのビットに同じ値を与えるデモ ★
  • ビットごとに全てのポートに同じ値を与えるデモ ★
  • 全てのLEDに対して個別のLEDのPWMをランダムに制御 ★
というようなモードでLEDのPWM制御を行っていて、★印については個別制御のプロトコルを使っているので、 画面に並べた64個のスライダーで、MIDIで戻ってきた制御データをグラフィックにモニタしている。 メインのPropellerプログラム「board_010.spin」は以下である。
CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  midiIn        : "MidiIn03"
  midiOut       : "MidiOut01"
  pwmOut       : "PwmPortOut02"

PUB start | dummy
  midiOut.start(25)
  pwmOut.start
  dummy := midiIn.start(24)
  midiOut.fifoset($C00000 + dummy & $00007F)  ' display total number of Cogs
  pwmOut.buffer_fill(0)

  repeat
    dummy := midiIn.event
    if dummy <> -1
      midiOut.fifoset(dummy)
      if (dummy & $FF0000) == $AF0000
        pwmOut.buffer_set(dummy & $3F7F)
      elseif (dummy & $FF0000) == $D10000
        pwmOut.buffer_fill(dummy & $7F)
      elseif (dummy & $FF0000) == $D20000
        pwmOut.speed_set(dummy & $F)
今回の目玉、アセンブラで新開発したPWMライブラリ「PwmPortOut02.spin」は以下である。 出来てしまえばスッキリしているが、ここまで来る道のりは遠かった(^_^;)。
VAR
  long speed_Data, pwm_Data[64]

PUB start : status | i
  buffer_address := @pwm_Data
  speed_address := @speed_Data
  speed_Data := %100000
  status := cognew(@asm_entry, 0)

PUB buffer_fill(data)
  longfill(@pwm_Data,data,64)

PUB speed_set(data)
  speed_Data := 1 << (data+6)

PUB buffer_set(data)
  pwm_Data[data>>8] := data & $7F

DAT
                        org
asm_entry
                        rdlong  counter1,speed_address
                        mov     counter2,#0
:start_entry
                        sub     counter1,#1             wz
              if_nz     jmp     #:start_entry
                        rdlong  counter1,speed_address
                        add     counter2,#1
                        and     counter2,#%01111111
                        or      dira,dir_setmask
                        andn    outa,enable_mask
                        or      outa,disable_mask
                        mov     port,#0
:main_loop
                        mov     work_bit,#0
                        mov     write_data,#0
:bit_loop
                        mov     t1,port
                        shl     t1,#3
                        add     t1,work_bit
                        shl     t1,#2
                        add     t1,buffer_address
                        rdlong  pwm_value,t1            wz
              if_z      jmp     #:led_off
                        cmp     pwm_value,counter2      wz
              if_z      jmp     #:led_on
                        cmp     pwm_value,counter2      wc
              if_c      jmp     #:led_off
:led_on
                        mov     bit_mask,#%00000001
                        shl     bit_mask,work_bit
                        or      write_data,bit_mask
:led_off
                        add     work_bit,#1
                        and     work_bit,#%111          wz
              if_nz     jmp     #:bit_loop
                        mov     t1,port
                        shl     t1,#16
                        andn    outa,port_mask
                        or      outa,t1
                        andn    outa,data_mask
                        shl     write_data,#8
                        or      outa,write_data
                        andn    outa,latch_mask
                        or      outa,latch_mask
                        add     port,#1
                        and     port,#%111              wz
              if_nz     jmp     #:main_loop
                        jmp     #:start_entry

dir_setmask             long    %00000000111111111111111100000000
disable_mask            long    %00000000100110000000000000000000
enable_mask             long    %00000000011000000000000000000000
latch_mask              long    %00000000000010000000000000000000
port_mask               long    %00000000000001110000000000000000
data_mask               long    %00000000000000001111111100000000
all_high                long    %11111111111111111111111111111111
buffer_address          long    0
counter1                long    0
counter2                long    0
port                    long    0
speed_address           long    0
work_bit                long    0
bit_mask                long    0
write_data              long    0
pwm_value               long    0
t1                      long    0

                        fit
外見的には、SUAC基板に治具の64個のLEDボードが付いた状態のままで、 今週の当初とあまり変わらないように見える。 しかし、まったく状況は違ってきた。 今回開発した、PropellerアセンブラのPWMライブラリによって、相当に高度な処理が、 もの凄く小さなプログラムで簡単に実現できる、とまた再確認できた。 現状では8ビット×8ポートで64個となっているが、Propellerの余りピンを考えると、 これを16ポート(128個)、32ポート(256個)、64ポート(512個)と増設するのに、 Propellerの処理能力としてはまったく何の問題も無い。これは素晴らしい事である。

・・・遅々として進んだが、今週は収穫も多かった。 これで明日は、某新年会マラソンカラオケ9時間を、心から楽しむことが出来そうである。 来週はいよいよ講義も始まるのでペースは落ちるが、あとは周辺機能としてはA/Dだけなので、先が見えて来た。

2013年1月13日(日)

かつて5年前、part1の「Propeller日記」では、主としてParallax社のサイトにあるサンプルプログラムを解析する、 ということを中心としてきた。 唯一、MIDIの入出力ライブラリだけは、アセンブラのライブラリを解析してオリジナル開発した。 しかし今回は、SUAC基板という新しいハードウェアが存在していて、そこで走るPropellerアセンブラ・ライブラリを、 白紙から作り上げたので、これは重要な進展なのだった。 また2-3年すれば忘れてしまうので、ここで上記のプログラムの一部を再録しつつ、 プログラミングのポイント(解読の補助)を解説・記録しておくことにした。
PUB start | dummy
  pwmOut.start
  pwmOut.buffer_fill(0)
  repeat
    dummy := midiIn.event
    if dummy <> -1
      if (dummy & $FF0000) == $AF0000
        pwmOut.buffer_set(dummy & $3F7F)
      elseif (dummy & $FF0000) == $D10000
        pwmOut.buffer_fill(dummy & $7F)
      elseif (dummy & $FF0000) == $D20000
        pwmOut.speed_set(dummy & $F)
上は、メインCogの処理のうち、重要な部分の抜粋であり、MIDIライブラリの部分を省略している。 「pwmOut.start」で、ライブラリの初期化ルーチンをコールする。 MIDIやXBeeと違って、入出力ピンの指定が無いので、引き数ナシで呼び出して、 ここでライブラリはアセンブラ処理の新しいCogを起動している。 初期化において、64個のLEDには直接、データを書かない。 代わりに「pwmOut.buffer_fill(0)」で、64個のLEDの全てのPWM値をゼロとしている。 MIDI入力イベントで定義に合致した場合に、「pwmOut.buffer_set()」によって、 PWMライブラリにパラメータをセットする。 「pwmOut.buffer_fill()」は全部のLEDのPWD値をセットするメソッド、 「pwmOut.speed_set()」はPWM処理のスキャンスピードのパラメータ(0-15)をセットするメソッド、 そして「pwmOut.buffer_set()」が、本命の「特定のLEDのPWM値」をセットするメソッドである。 データの下位7ビットがPWM値のデータ(0-127)、そしてデータの中位8ビット(bit 8-15)の下位6ビットが、 そのPWM値を書き込むLEDの番号(0-63)である。
VAR
  long speed_Data, pwm_Data[64]

PUB start : status | i
  buffer_address := @pwm_Data
  speed_address := @speed_Data
  speed_Data := %100000
  status := cognew(@asm_entry, 0)

PUB buffer_fill(data)
  longfill(@pwm_Data,data,64)

PUB speed_set(data)
  speed_Data := 1 << (data+6)

PUB buffer_set(data)
  pwm_Data[data>>8] := data & $7F
上は、サブCogのPWMライブラリの冒頭部分の定義である。 ほぼMIDIやXBeeのものと同じだが、ここではFIFO構造を持たず、 メインHub-RAMに64longsの配列を定義して、そこに各LEDのPWM値(0-127)を持つ。 メインからここに書き込まれた最新の値が、サブCogによって参照されてPWM制御に使用される。 「speed_Data」は、与えられた「0-15」の値に6を加えた値だけ、「1」を左ビットシフトしているので、 「2の15乗」(32768)倍、という相当に大きなダイナミックレンジとなっている。
dir_setmask             long    %00000000111111111111111100000000
disable_mask            long    %00000000100110000000000000000000
enable_mask             long    %00000000011000000000000000000000
latch_mask              long    %00000000000010000000000000000000
port_mask               long    %00000000000001110000000000000000
data_mask               long    %00000000000000001111111100000000
all_high                long    %11111111111111111111111111111111
buffer_address          long    0
counter1                long    0
counter2                long    0
port                    long    0
speed_address           long    0
work_bit                long    0
bit_mask                long    0
write_data              long    0
pwm_value               long    0
t1                      long    0
上は、順序は逆になるが、サブCogのPWMライブラリのアセンブラ部分の最後にある、 変数と定数の定義である。 Propellerでは、アセンブラプログラムで9ビットを越える定数は表現できないので、 このようにlong変数に定数を定義しておき、それをレジスタ変数のようなカンジで参照して使用する。 Hub-RAMの変数をアクセスするための参照アドレスを定義し格納するのもここである。
DAT
                        org
asm_entry
                        rdlong  counter1,speed_address
                        mov     counter2,#0
:start_entry
                        sub     counter1,#1             wz
              if_nz     jmp     #:start_entry
                        rdlong  counter1,speed_address
                        add     counter2,#1
                        and     counter2,#%01111111
                        or      dira,dir_setmask
                        andn    outa,enable_mask
                        or      outa,disable_mask
上は、サブCogのPWMライブラリのアセンブラ部分の冒頭である。 新しいCogが起動された最初にだけ、「asm_entry」にやってきて、 ここではPWM処理の間隔パラメータがHub-RAMから「speed_address」により参照され、 レジスタ「counter1」に格納される。 また、PWM周期の基準となるレジスタ「counter2」がゼロで初期化される。 そして、PWM処理は常に、次の「:start_entry」に戻る無限ループとなっている。 ここでは演算処理のスピードを決める「counter1」を減算して、 ゼロになった時だけ、それ以降のPWM値の設定処理に入り、あとは全て「:start_entry」に戻るので、 ほとんどの時間は、無駄に足踏みしていることになる。

ループで戻らず、PWM値の設定処理に入ると、まずは 「counter2」をインクリメントして7Fでマスクをかけている。 これで、64個のLEDのPWM値を処理するための基準時間が、 1カウントだけ進んだことになる。 そしてこの後に、該当する入出力ピンの方向をdiraで定義して、 制御ピンをセットしている。 これは毎回しなくてもよさそうだが、入出力拡張バスを入力に使う事があるのでは・・・とここに入れた。

                        mov     port,#0
:main_loop
                        mov     work_bit,#0
                        mov     write_data,#0
:bit_loop
                        mov     t1,port
                        shl     t1,#3
                        add     t1,work_bit
                        shl     t1,#2
                        add     t1,buffer_address
                        rdlong  pwm_value,t1            wz
              if_z      jmp     #:led_off
                        cmp     pwm_value,counter2      wz
              if_z      jmp     #:led_on
                        cmp     pwm_value,counter2      wc
              if_c      jmp     #:led_off
:led_on
                        mov     bit_mask,#%00000001
                        shl     bit_mask,work_bit
                        or      write_data,bit_mask
:led_off
                        add     work_bit,#1
                        and     work_bit,#%111          wz
              if_nz     jmp     #:bit_loop
                        mov     t1,port
                        shl     t1,#16
                        andn    outa,port_mask
                        or      outa,t1
                        andn    outa,data_mask
                        shl     write_data,#8
                        or      outa,write_data
                        andn    outa,latch_mask
                        or      outa,latch_mask
                        add     port,#1
                        and     port,#%111              wz
              if_nz     jmp     #:main_loop
                        jmp     #:start_entry
上は、サブCogのPWMライブラリのアセンブラのメイン処理部分である。 変数「port」は、LEDが8ビットずとまとまっているラッチ(ポート)の番号に対応する。 変数「work_bit」は、各ポートの8ビットのLEDの指定に使う。 これらを組み合わせて、まずHub-RAMに定義された各LEDのPWM値(0-127)を読み出し、 ゼロであれば「led_off」に飛ぶ。 このPWM値を現在のタイマ値である「counter2」と比較して、 等しいか負の場合も「led_off」に飛ぶ。 そうでなければ「led_on」ということで、該当ビットだけLED出力として立てたデータをポート出力する。 外部ラッチ(574)に指定するアドレスも出力し、138のGを下げて上げることでラッチパルスとする。 これが終われば、8ビットを全て回してそのポートが終了、 さらにポートが0から7まで回れば、そこで今回の処理が終了してメインループに戻る。

ここまで整理してみて、気付いたことがあった。 PWM処理ライブラリでは、拡張入出力ポートのディレクションを、 将来の「ポート入力」との混在に備えて、ループの内側で、毎回、設定している。 しかしこれでは、メインCogのspinブログラムでポート入力処理を記述した場合には、 サブCogのアセンブラでのdira処理とコンフリクトする可能性があるのだった。 PWM処理ライブラリ自身の中に、出力とポート入力の両方を完備しなければならないので、 現状のバージョンは「ポート入力しない場合」に限定の暫定バージョンである、という事である。

これはおいおい作るとしても、既に峠は越えているので問題ない、 明後日に期限が迫った「書類書き」仕事があり、それを棚上げしてアセンブラに取り組んだので、 ここでさらに深入りしては駄目なのである。 そこで、これは書類書きが終わったら、という宿題にした。

そして、もう1点、思い出した事があったので、ここで忘れずに記録しておこう。 続・Propeller日記(1) で去年の8月にPropeller日記を再開したところで、「その後のPropellerの活用事例」というのを並べていたが、 「電子十二影坊」 と「万変鏡」の間に、重要なものが抜けていた、 とまたまた夜中に思い出したのである(^_^;)。

それは、 SUACインスタレーション(2) の最後のあたり、「はやくスシになりたい」と「おはなしパネル」という、 現在でも動態保存していろいろな場で展示している作品に続く、 見崎さんの「OTOkakecco」である。以下にその記録を再録しておこう。

  • 展示発表 (メディア造形総合演習II最終合評会)
  • 作家 見崎央佳 メディア造形学科4回生
  • 概要 4人がそれぞれのマウスを同時に操作して行うFLASHゲーム。画面内にランダムに出現する音符をそれぞれのマウスに対応した自分のアイコンで競って捕獲する。卒業制作として人間自身が体感するゲーム(画像認識を利用)インスタレーション作品を制作するためのプログラミングの練習として、専用の「4マウスインターフェース」を利用して制作した
  • システム Propeller、GAINER、Flash
  • 写真 
  • プレゼン
  • YouTube YouTube

見崎さんのプレゼンFLASHでは、4つのマウスからPC(Flash)に接続するインターフェースが「Gainer」となっているが、 実はとても地味ながら、ここにPropellerが活躍しているのであった。 4つのマウスはUSBマウスではなくて、懐かしい「シリアルマウス」である。 4個のマウスとシリアル通信してそれぞれの操作情報を取得して、 それをホストに送る、という処理は、まさにPropellerにうってつけである。 マウスとのシリアル通信は、それぞれのCogsに任せればいいので、割り込みも不要である。

この4マウス・インターフェースのシステムも、ちゃんとSUACマルチメディア室のロッカーに動態保存しているので、 メディア造形学科の後輩などは、これをブラックボックスのインターフェースとして活用した作品の制作に、 簡単にチャレンジできるのである。 そのために、ここでこのシステムについてのドキュメントも整理しておくことにした。 いろいろ発掘した結果、以下の資料があった。

そして、Propellerプログラム「Mouse007.spin」は以下である。 共通の「E_mouseEngine03.spin」というメソッドを、4個のマウスに対応して呼び出している。 動作確認用にビデオ出力している部分は、過去のものを利用しているようである。
{{ Mouse007.spin }}

CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  Num : "E_Numbers02"
  TV : "E_TV_Terminal02"
  mouse1 : "E_mouseEngine03"
  mouse2 : "E_mouseEngine03"
  mouse3 : "E_mouseEngine03"
  mouse4 : "E_mouseEngine03"

PUB Main | M1[6], M2[6], M3[6], M4[6], dummy
  Num.Init                      { Num.Init MUST be called before first object use. }
  TV.Start1(12)
  TV.Start2
  TV.out(0)
  TV.Str(Num.ToStr( mouse1.Initialize(25,24), Num#DEC) ) 
  TV.Str(Num.ToStr( mouse2.Initialize(27,26), Num#DEC) )     
  TV.Str(Num.ToStr( mouse3.Initialize(21,20), Num#DEC) ) 
  TV.Str(Num.ToStr( mouse4.Initialize(23,22), Num#DEC) ) 
  DIRA[0..11]~~
  DIRA[15..19]~~
  OUTA[8..11]~~
  OUTA[15..19]~~
  M1[4] := M1[5] := M2[4] := M2[5] := M3[4] := M3[5] := M4[4] := M4[5] := 128
  OUTA[7..0] := 128
  repeat dummy from 8 to 19
    OUTA[dummy]~
    OUTA[dummy]~~

  repeat
    dummy := MoveX1( M1[2], M1[4], mouse1.getXDelta )
    M1[2] := dummy >> 8
    M1[4] := dummy // 256
    dummy := MoveY1( M1[3], M1[5], mouse1.getYDelta )
    M1[3] := dummy >> 8
    M1[5] := dummy // 256

    dummy := MoveX2( M2[2], M2[4], mouse2.getXDelta )
    M2[2] := dummy >> 8
    M2[4] := dummy // 256
    dummy := MoveY2( M2[3], M2[5], mouse2.getYDelta )
    M2[3] := dummy >> 8
    M2[5] := dummy // 256

    dummy := MoveX3( M3[2], M3[4], mouse3.getXDelta )
    M3[2] := dummy >> 8
    M3[4] := dummy // 256
    dummy := MoveY3( M3[3], M3[5], mouse3.getYDelta )
    M3[3] := dummy >> 8
    M3[5] := dummy // 256

    dummy := MoveX4( M4[2], M4[4], mouse4.getXDelta )
    M4[2] := dummy >> 8
    M4[4] := dummy // 256
    dummy := MoveY4( M4[3], M4[5], mouse4.getYDelta )
    M4[3] := dummy >> 8
    M4[5] := dummy // 256

    if( mouse1.getLeftButtonState <> 0)
      OUTA[0] := OUTA[1] := 1
    else
      OUTA[0] := OUTA[1] := 0      
    if( mouse2.getLeftButtonState <> 0)
      OUTA[2] := OUTA[3] := 1
    else
      OUTA[2] := OUTA[3] := 0
    if( mouse3.getLeftButtonState <> 0)
      OUTA[4] := OUTA[5] := 1
    else
      OUTA[4] := OUTA[5] := 0
    if( mouse4.getLeftButtonState <> 0)
      OUTA[6] := OUTA[7] := 1
    else
      OUTA[6] := OUTA[7] := 0
    OUTA[15]~
    OUTA[15]~~


PUB MoveX1(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" x1="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[8]~
    OUTA[8]~~
  return ( value1<<8 + value2 )

PUB MoveY1(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" y1="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[9]~
    OUTA[9]~~
  return ( value1<<8 + value2 )

PUB MoveX2(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" x2="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[10]~
    OUTA[10]~~
  return ( value1<<8 + value2 )

PUB MoveY2(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" y2="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[11]~
    OUTA[11]~~
  return ( value1<<8 + value2 )

PUB MoveX3(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" x3="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[16]~
    OUTA[16]~~
  return ( value1<<8 + value2 )

PUB MoveY3(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" y3="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[17]~
    OUTA[17]~~
  return ( value1<<8 + value2 )

PUB MoveX4(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" x4="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[18]~
    OUTA[18]~~
  return ( value1<<8 + value2 )

PUB MoveY4(value1,value2,dummy)
  if((value1 <> dummy) and (dummy <> 0))
    value1 := dummy
      if(dummy > 127)
        value2 := value2 + dummy - 256
        if(value2 < 0)
          value2 := 0
      elseif(dummy <> 0)
        value2 := value2 + dummy
        if(value2 > 255)
          value2 := 255
{
    TV.Str(string(" y4="))
    TV.Str(Num.ToStr(value2, Num#DEC)) 
}
    OUTA[7..0] := value2
    OUTA[19]~
    OUTA[19]~~
  return ( value1<<8 + value2 )
 
以下が、個々のマウスと通信しているライブラリ、「E_mouseEngine03.spin」である。 どうも、Parallax社のライブラリにあった、シリアルマウスと通信するライブラリを改造したもののようだが、 もはや詳細不明である。(^_^;)
VAR
  byte xDelta, yDelta, leftButton, rightButton

PUB getLeftButtonState
  return leftButton

PUB getRightButtonState
  return rightButton

PUB getXDelta
  return ~~xDelta

PUB getYDelta
  return ~~yDelta

PUB Initialize(clock_pin,data_pin)
  mouseClock          := 1 << clock_pin
  mouseData           := 1 << data_pin
  leftButtonAddress   := @leftButton
  rightButtonAddress  := @rightButton
  xDeltaAddress       := @xDelta
  yDeltaAddress       := @yDelta
  clockWait           := clkfreq / 66666 
  dataWait            := 33333
  return cognew(@initialization, 0)

DAT
                        org
initialization
                        mov     mouseTimeBuffer,      clockWait           ' Setup the sample wait.
                        add     mouseTimeBuffer,      cnt                 '
resetMouse              andn    dira,                 mouseData           ' Reset mouse data line state.
                        mov     mouseTimeCounter,     dataWait            ' Setup the timeout wait.                               
                        mov     mouseBuffer,          #0                  ' Reset mouse buttons states.
                        wrbyte  mouseBuffer,          leftButtonAddress   '
                        wrbyte  mouseBuffer,          rightButtonAddress  '
waitMouse               waitcnt mouseTimeBuffer,      clockWait           ' Wait for mouse to intialize.
                        djnz    mouseTimeCounter,     #waitMouse          '
                        mov     mousePacket,          #$FF                ' Reset the mouse.
                        call    #mouseTransmitPacket                      '
                        call    #mouseReceivePacket                       ' Check if the mouse passed its BAT.
                        jmp     #resetMouse                               '
setupMouse              mov     mousePacket,          #$F2                ' Read the mouse's ID.
                        call    #mouseTransmitPacket                      '
                        call    #mouseReceivePacket                       ' Check if a mouse is attached.
                        cmp     mousePacket,          #$00 wz             '
if_nz                   jmp     #setupMouse                               '
                        mov     mousePacket,          #$F3                ' Set sample rate.
                        call    #mouseTransmitPacket                      '
                        mov     mousePacket,          #200                ' To 200 samples per second.
                        call    #mouseTransmitPacket                      '  
                        mov     mousePacket,          #$F3                ' Set sample rate. 
                        call    #mouseTransmitPacket                      ' 
                        mov     mousePacket,          #100                ' To 100 samples per second. 
                        call    #mouseTransmitPacket                      '
                        mov     mousePacket,          #$F3                ' Set sample rate.
                        call    #mouseTransmitPacket                      '
                        mov     mousePacket,          #80                 ' To 80 samples per second.
                        call    #mouseTransmitPacket                      '
                        mov     mousePacket,          #$F2                ' Get device ID.
                        call    #mouseTransmitPacket                      '                                                 
                        mov     mousePacket,          #$F0                ' Set remote mode.
                        call    #mouseTransmitPacket                      ' 
updateMouse             mov     mousePacket,          #$EB                ' Read Data.
                        call    #mouseTransmitPacket                      '
                        call    #mouseReceivePacket                       ' Receive and copy mouse state data packet.
                        mov     mouseState,           mousePacket         '                                                 
                        test    mouseState,           #$1 wc              ' Update left button state.
                        muxc    mouseBuffer,          #$FF                '
                        wrbyte  mouseBuffer,          leftButtonAddress   ' 
                        test    mouseState,           #$2 wc              ' Update right button state.
                        muxc    mouseBuffer,          #$FF                '
                        wrbyte  mouseBuffer,          rightButtonAddress  '
                        call    #mouseReceivePacket                       ' Receive x axis movement data packet.                                                
                        mov     mouseCounter,         #0                  ' Reset x delta movement mouseCounter.
                        test    mouseState,           #$10 wz             ' Decode and add x axis movement to mouseCounter.  
                        muxnz   mousePacket,          #$100               ' 
                        shl     mousePacket,          #23                 '  
                        sar     mousePacket,          #23                 '
                        add     xAxisCounter,         mousePacket         '
                        add     mouseCounter,         mousePacket         '  
if_z                    mov     mouseBuffer,          #$FE                ' Setup x axis overflow value.
if_nz                   mov     mouseBuffer,          #$1                 '                                                                          
                        test    mouseState,           #$40 wc             ' Add x axis overflow movement to mouseCounter.
if_c                    muxnz   mouseBuffer,          #$100               ' 
if_c                    shl     mouseBuffer,          #23                 '
if_c                    sar     mouseBuffer,          #23                 '
if_c                    add     xAxisCounter,         mouseBuffer         '
if_c                    add     mouseCounter,         mouseBuffer         '                                                                          
                        wrbyte  mouseCounter,         xDeltaAddress       ' Update x delta movement mouseCounter.                                                                          
                        call    #mouseReceivePacket                       ' Receive y axis movement data packet.                                                                          
                        mov     mouseCounter,         #0                  ' Reset y delta movement mouseCounter.                                                                          
                        test    mouseState,           #$20 wz             ' Decode and add y axis movement to mouseCounter.   
                        muxnz   mousePacket,          #$100               ' 
                        shl     mousePacket,          #23                 ' 
                        sar     mousePacket,          #23                 '
                        add     yAxisCounter,         mousePacket         '
                        add     mouseCounter,         mousePacket         '                                                                          
if_z                    mov     mouseBuffer,          #$FE                ' Setup y axis overflow value.
if_nz                   mov     mouseBuffer,          #$1                 '                                                                          
                        test    mouseState,           #$80 wc             ' Add y axis overflow movement to mouseCounter.  
if_c                    muxnz   mouseBuffer,          #$100               ' 
if_c                    shl     mouseBuffer,          #23                 '
if_c                    sar     mouseBuffer,          #23                 '
if_c                    add     yAxisCounter,         mouseBuffer         '
if_c                    add     mouseCounter,         mouseBuffer         '                                                                          
                        wrbyte  mouseCounter,         yDeltaAddress       ' Update y delta movement mouseCounter.                                                                          
                        jmp     #updateMouse                              ' Loop

mouseTransmitPacket     mov     mouseCounter,         #20                 ' Setup to request to transmit.
                        or      dira,                 mouseClock          ' Request to transmit.                                                                          
mouseRequest            waitcnt mouseTimeBuffer,      clockWait           ' Wait for request to transmit.
                        djnz    mouseCounter,         #mouseRequest       '                                                                           
                        or      dira,                 mouseData           ' Transmit start bit.
                        xor     dira,                 mouseClock          '
                        movd    mousePacket,          #$3                 ' Add parity to the data packet.  
                        test    mousePacket,          #$FF wc             ' 
                        muxnc   mousePacket,          #$100               '                                                                             
                        mov     mouseCounter,         #11                 ' Setup to transmit the packet.  
mouseTransmitLoop       call    #mouseClockLow                            ' Wait until the clock is low.                                                                          
                        shr     mousePacket,          #1 wc               ' Output Bits
                        muxnc   dira,                 mouseData           '                                                                          
                        call    #mouseClockHigh                           ' Wait until the clock is high.                                                                          
                        djnz    mouseCounter,         #mouseTransmitLoop  ' Repeat 11 times.   
                        call    #mouseReceivePacket                       ' Recieve aknowledgement byte.
                        cmp     mousePacket,          #$FA wz             '
if_nz                   jmp     #resetMouse                               ' 
mouseTransmitPacket_ret ret                                               ' Return       
mouseReceivePacket      mov     mouseCounter,         #11                 ' Setup to recieve the packet.
mouseReceiveLoop        call    #mouseClockLow                            ' Wait until the clock is low.
                        test    mouseData,            ina wc              ' Input bits.
                        rcr     mousePacket,          #1                  '
                        call    #mouseClockHigh                           ' Wait until the clock is high.
                        djnz    mouseCounter,         #mouseReceiveLoop   ' Repeat 11 times.
                        shr     mousePacket,          #22                 ' Remove start bit.
                        test    mousePacket,          #$1FF wc            ' Remove parity bit and stop bit.
if_nc                   jmp     #resetMouse                               '
                        and     mousePacket,          #$FF                '
                        cmp     mousePacket,          #$AA wz             ' Check for BAT succesful.
if_z                    jmp     #setupMouse                               '       
mouseReceivePacket_ret  ret                                               ' Return
mouseClockLow           mov     mouseTimeCounter,     dataWait            ' Setup the timeout wait.
mouseLoopLow            waitcnt mouseTimeBuffer,      clockWait           ' Wait a little while.                    
                        test    mouseClock,           ina wc              ' Decode clock line state.                                                
if_c                    djnz    mouseTimeCounter,     #mouseLoopLow       ' Repeat until the clock goes low or timeout.                                                 
if_c                    jmp     #resetMouse                               ' Reset on timeout.
mouseClockLow_ret       ret                                               ' Return.
mouseClockHigh          mov     mouseTimeCounter,     dataWait            ' Setup the timeout wait.
mouseLoopHigh           waitcnt mouseTimeBuffer,      clockWait           ' Wait a little while.
                        testn   mouseClock,           ina wc              ' Decode clock line state.
if_c                    djnz    mouseTimeCounter,     #mouseLoopHigh      ' Repeat until the clock goes high or timeout.                                                
if_c                    jmp     #resetMouse                               ' Reset on timeout. 
mouseClockHigh_ret      ret                                               ' Return.

leftButtonAddress       long    0                                 
rightButtonAddress      long    0                               
xDeltaAddress           long    0
yDeltaAddress           long    0
clockWait               long    0                                         ' State machine update frquency.  
dataWait                long    0                                         ' State machine timeout. 
mouseClock              long    0
mouseData               long    0
xAxisCounter            long    0                                  
yAxisCounter            long    0                                  
mouseState              long    0                                   
mousePacket             long    0
mouseBuffer             long    0                                  
mouseCounter            long    0
mouseTimeCounter        long    0
mouseTimeBuffer         long    0                             

                        fit
これで、見崎作品のために制作した、Propellerを活用したインターフェースの資料は全てである。 こんなこともやっていたんだなぁ、と温故知新である。(^_^)

2013年1月14日(月)

日本全国が、雪と嵐に見舞われた成人の日、祝日である。 雨にも負ケズの筈の高校サッカーの決勝が中止になるほどの大雪だが、 研究室は、ぼちぼち暖かい。(^_^;)

しかしこの日は、終日マルマルかかって、学内の特別研究の申請書作成、という事務仕事で消えた。 私大時代からバリバリお役所のSUACが公立大となり研究費は貴重な税金なのだから、 と自分に言い聞かせて、苦手で苦手でたまらない書類作成に没頭した。 事務屋さんて、本当に偉いと思う。 ハンダ付けやプログラミングの方がよほど楽である。(^_^;)

2013年1月15日(火)

いよいよ本格的に全てが稼働する週になった。 昨日で書類書き仕事を終えたので、今日は明日から再開される講義の準備をしたり、 さらにSUAC基板のチェックを進めることになる。 今週は木曜日は終日、メディア造形学科の卒業制作の最終合評があり、金曜日は全学休講日、 そして週末の土日はセンター試験である。年度末の「濃い」日々が続く。

昨夜はまたまた夜中に、試作基板のチェックで残ったのはA/D部分である、というところで、過去のPropeller事例で、まだ抜けがある事に気付いた。 続・Propeller日記(1) の最初のところでチラッとだけ紹介していたが、2009年に開発した新楽器「Peller-Min」はまさに、 Propellerを使ってA/D入力拡張する好例だったのだ。 せっかくなので、ここで過去の資料を発掘して整理しておくことにした。 この 続・Propeller日記(1) で紹介していた関連情報は以下である。

また、「Peller-Min」を使った作品「controllable untouchableness」のパフォーマンスの記録(YouTube)は、 2009年のインカレ併催の「親父の背中」コンサートでの記録が これこれ であり、2010年のMOM2010のライブコンサートでの記録が これこれこれ である。 この2つのコンサート(リハ)の写真は、 ここここ にある。

製作の風景は、 ここ にあるだけであった。 回路図とかは公開していなかったようなので、かなりの時間をかけて発掘して(^_^;)、スキャンして以下に置いた。 技術資料はこれで全てである。

8個の赤外線距離センサが付いたリングが2個で計16個、 さらに左右8個ずつの反射光センサが計16個、 合計32チャンネルのアナログ入力のために、8チャンネルA/DコンバータのADC0809を4個並べて、 ちゃんとデータバスの電圧変換は2SC1815を8個、並べている。 教科書通りのような回路である(^_^)。 そして、Propellerのプログラムは以下であった。 現物合わせで、個々のセンサの感度調整をしているあたりが涙ぐましい。

{{ Inst010.spin }}  

CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  midiOut : "MidiOut01"

PUB main | dummy, i[3], j[6], mode, ad, addr, k[4], value1[32], value2[32], value3, value4[32], value5[32]
  midiOut.start(27)
  port_initial
  repeat ad from 0 to 3
    k[ad] := 0                                          ' k[ad] = A/D channel (0-7)
    ad_initial(ad,k[ad])
  repeat i from 0 to 32
    value4[i] := threshold_init(i)
    value5[i] := max_value_init(i)
  j[5] := 8

  repeat

    if mode <> 0
      repeat ad from 0 to 3
        outa[10..8] := 5                                ' EOC check port (3..0)
        dummy := ina[ad]                                ' EOC bit (0=EOC)
        outa[10..8]~~
        if dummy == 0
          addr := ad<<3 + k[ad]
          value2[addr] := value1[addr]
          outa[10..8] := ad
          value3 := (255 - ina[7..0]) >>1               ' value3 = A/D data
          outa[10..8]~~
          if value3 < value4[addr]
            value3 := value4[addr]
          dummy := ( (value3 - value4[addr])<<7 ) / ( value5[addr] - value4[addr] )
          value1[addr] := dummy
          if value2[addr] <> value1[addr]
            dummy += $B00000 + ad<<16 + k[ad]<<8
            midiOut.fifoset(dummy)  
          k[ad] := ((k[ad]+1) & %111)
          ad_initial(ad,k[ad])

    i[0] := cnt & $6000000
    if i[0] <> i[1]
      i[1] := i[0]
      i[2] := cnt>>25
      dummy := $B40000 + i[2]
      midiOut.fifoset(dummy)
      out_574( %001, seg7_conv( (i[2])//10 ) )

    j[0] := cnt & $7FC00000     ' Switch Scan
    if j[0] <> j[1]
      j[1] := j[0]
      outa[10..8] := 4
      j[2] := ina[5..0]         ' Non Lock Type SW 6 bits
      outa[10..8]~~
      if j[3] <> j[2]
        j[3] := j[2]
        dummy := $B60000 + j[2]
        midiOut.fifoset(dummy)
      outa[10..8] := 5
      j[4] := ina[6..4]         ' Toggle Type SW 3 bits
      outa[10..8]~~
      if j[5] <> j[4]
        j[5] := j[4]
        mode := j[4]
        dummy := $B50000 + mode
        midiOut.fifoset(dummy)  
        out_574( %111, seg7_conv(mode) )
        if mode > 3
          out_574(4,$FF)                                                       
          out_574(5,$FF)                                                       
        else
          out_574(4,$00)                                                       
          out_574(5,$00)                                                       

PUB threshold_init(id)
  case id
    0: return(13)
    1: return(13)
    2: return(13)
    3: return(13)
    4: return(13)
    5: return(13)
    6: return(12)
    7: return(12)
    8: return(11)
    9: return(25)
    10: return(20)
    11: return(14)
    12: return(10)
    13: return(11)
    14: return(11)
    15: return(16)
    16: return(104)
    17: return(79)
    18: return(58)
    19: return(53)
    20: return(72)
    21: return(58)
    22: return(37)
    23: return(75)
    24: return(45)
    25: return(56)
    26: return(74)
    27: return(62)
    28: return(87)
    29: return(65)
    30: return(56)
    31: return(78)

PUB max_value_init(id)
  case id
    0: return(79)
    1: return(79)
    2: return(79)
    3: return(79)
    4: return(79)
    5: return(79)
    6: return(79)
    7: return(79)
    8: return(79)
    9: return(79)
    10: return(79)
    11: return(79)
    12: return(79)
    13: return(79)
    14: return(79)
    15: return(79)
    16: return(121)
    17: return(121)
    18: return(121)
    19: return(121)
    20: return(121)
    21: return(115)
    22: return(100)
    23: return(121)
    24: return(115)
    25: return(115)
    26: return(121)
    27: return(116)
    28: return(121)
    29: return(121)
    30: return(121)
    31: return(121)

PUB out_574(sel,data)
  outa[18..11] := data
  outa[21..19] := sel
  outa[22]~
  outa[22]~~

PUB seg7_conv(data)
  case data
    0: return(%00000011)
    1: return(%10011111)                    
    2: return(%00100101)                    
    3: return(%00001101)                    
    4: return(%10011001)                    
    5: return(%01001001)                    
    6: return(%01000001)                    
    7: return(%00011011)                    
    8: return(%00000001)                    
    9: return(%00001001)                    

PUB port_initial
  dira[7..0]~                   ' Input Bus <-- 245                                         
  dira[10..8]~~                 ' Input Select : %110 - %000 (disable = %111)
  outa[10..8]~~                 '  --> normal disable %111
  dira[18..11]~~                ' Output Bus --> 574
  dira[21..19]~~                ' Output Select : %111 - %000
  dira[22]~~                    ' Output Select Enable (active low)                                         
  outa[22]~~                    '   --> normal High
  out_574(%001,$FF)             ' 7seg Red LED off                              
  out_574(%111,$FF)             ' 7seg Green LED off
  out_574(4,0)                  ' Blue LED(1) off                              
  out_574(5,0)                  ' Blue LED(2) off                              

PUB ad_initial(ad,addr)
  case ad
    0:
      out_574(0,addr+%00000000)
      out_574(0,addr+%00001000)
      out_574(0,addr+%00011000) 
      out_574(0,addr+%00010000)
      out_574(0,addr+%00000000)
      return
    1:
      out_574(0,addr+%00000000)
      out_574(0,addr+%00001000)
      out_574(0,addr+%00101000) 
      out_574(0,addr+%00100000)
      out_574(0,addr+%00000000)
      return
    2:
      out_574(0,addr+%00000000)
      out_574(0,addr+%00001000)
      out_574(0,addr+%01001000) 
      out_574(0,addr+%01000000)
      out_574(0,addr+%00000000)
      return
    3:
      out_574(0,addr+%00000000)
      out_574(0,addr+%00001000)
      out_574(0,addr+%10001000) 
      out_574(0,addr+%10000000)
      out_574(0,addr+%00000000)
      return
 
「Propeller日記」としては意味のある内容が加わったが、基板のチェックはまだまだ先である。 明日の水曜日は講義満タン、そして木曜日は終日、卒業制作の最終合評。その後の打上げの回復のために、 金曜日の午前までは潰れる予定である。(^_^;)

2013年1月22日(火)

前回の日記からもう1週間である。サボッていた訳ではないが、Propellerはまったく進展していない。 頑張ったメディア造形4回生の卒業制作最終合評があり、毎年恒例の打ち上げが今年はちょっと延びて(^_^;)、 帰宅したのはたぶん金曜日の午前3時過ぎだった程度である。 そして土日はセンター試験の監督を今年は2日間とも担当して立ち続け、ヘトヘトになった。

月曜日には瞑想空間では、M2の伊熊さんが こんなふうに 口頭試問のための記録などを頑張っているのを見届けた。 さらに半日かけて、以下のように、後期いよいよ終盤となった芸文専門科目「メディアアーツ論」の最終レポートと、 メディア造形専門科目「サウンドデザイン演習」の最終課題を作ってWebに置いた。

実は、水曜日の「メディアアーツ論」では、ロシアで初演し、オスロと東京で再演したライブComputer Music作品を、 セッティングの解説デモとともにパフォーマンスする、という計画だったのだが、この日に調べていて、出来ないことが判明した。 この作品では、2001年に開発した新楽器 "MiniBioMuse-III" を使うのであるが、その動作確認をしていたところ、電極ベルトである伸縮性の介護ベルトの、 マジックテープになっている部分が破断してしまったのである。

これまで、フランス・ドイツ・カナダ・オランダ・台湾・ロシア・ノルウェーでの海外公演などで活躍してきたこのセンサも、 遂に退役となった。 今後、筋電センサを使ったパフォーマンスをする機会が登場したら、これを修理するのでなく、 新しく次世代を製作するとして、今回のデモは無しという事にした。

そして今日は、3回生の藤本さんが再びのアポで来訪して、以下のようにインスタレーション作品のシステムとして仕込む、 6個の白色LEDと6個の振動モーターと2個の照度センサ(CdS)を接続したArduinoのプログラミングを行った。

アポの入った午前中だけではちょっと終わらなかったが、昼休みにはバグも取れて、無事に照度センサの3段階のステージごとに、 LEDとモータの数量をランダムに絞って、さらにそれぞれランダムに駆動する、というArduinoプログラムが完成した。 実際に作品に仕込んで、さらに展示場所の環境光に対応して動作条件を設定できるようにパラメータも切り出した。 これで、来週末の最終合評では、どんな作品となるか、藤本さんの追い込みに期待しよう。 ちなみにArduinoプログラムは以下である。
const int room_dark = 300;
const int point_dark = 300;

int LED[6], brightness[6], LED_count[6], LED_direction[6];
int motor[6], interval[6], motor_count[6], motor_phase[6];
int active_LED[6], active_motor[6], mode, mode_old, event;

void setup()  {
  LED[0] = 3;
  LED[1] = 5;
  LED[2] = 6;
  LED[3] = 9;
  LED[4] = 10;
  LED[5] = 11;
  motor[0] = 2;
  motor[1] = 4;
  motor[2] = 7;
  motor[3] = 8;
  motor[4] = 12;
  motor[5] = 13;
  for (int i=0; i<6; i++){
    pinMode(LED[i], OUTPUT);  
    pinMode(motor[i], OUTPUT);  
  }
  mode_old = 0;
  random_set();
}

void loop()  {
  mode = 0;
  event = 0;
  if (analogRead(A0) < room_dark){  // Room is bright
    mode = 2;
  }
  if (analogRead(A1) < point_dark){ // Point is bright
    mode = mode + 1;
  }
  if ( mode != mode_old){
    random_set();
    mode_old = mode;
    event = 1;
  }
  switch (mode) {
    case 3:  // Room bright, Point bright
      if (event == 1){
        some_active_set(2,2);
      }
      LED_control();
      motor_control();
      break;
    case 2:  // Room bright, Point dark
      if (event == 1){
        some_active_set(3,3);
      }
      LED_control();
      motor_control();
      break;
    case 0: // Dark
      if (event == 1){
        all_active_set();
      }
      LED_control();
      motor_control();
  }
  delay(10);
}

void LED_control(){
  for (int i=0; i<6; i++){
    if (active_LED[i] ==1){
      if (LED_direction[i]>0){
        LED_count[i]++;
        analogWrite(LED[i], LED_count[i]);
        if (LED_count[i] == brightness[i]){
          LED_direction[i] = 0;
        }
      }
      else{
        LED_count[i]--;
        analogWrite(LED[i], LED_count[i]);
        if (LED_count[i] == 0){
          LED_direction[i] = 1;
        }
      }
    }
  }
}

void motor_control(){
  for (int i=0; i<6; i++){
    if (active_motor[i] ==1){
      if (motor_phase[i] == 0){
        motor_count[i]++;
        digitalWrite(motor[i], LOW);
        if (motor_count[i] > interval[i]){
          motor_phase[i] = 1;
        }
      }
      else{
        motor_count[i]++;
        digitalWrite(motor[i], HIGH);
        if (motor_count[i] > (interval[i]+10)){
          motor_phase[i] = 0;
          motor_count[i] = 0;
        }
      }
    }
  }
}

void some_active_set(int max_LED, int max_motor){
  int dummy;
  int count = 0;
  for (int i=0; i<6; i++){
    active_motor[i] = 0;
    active_LED[i] = 0;
  }
  do {
    while(1){
      dummy = random(0,6);
      if(active_motor[dummy] != 1){
        break;
      }
    }
    active_motor[dummy] = 1;
    count++;
  } while (count < max_motor);
  count = 0;
  do {
    while(1){
      dummy = random(0,6);
      if(active_LED[dummy] != 1){
        break;
      }
    }
    active_LED[dummy] = 1;
    count++;
  } while (count < max_LED);
}

void all_active_set(){
  for (int i=0; i<6; i++){
    active_motor[i] = 1;
    active_LED[i] = 1;
  }
}

void random_set(){
  for (int i=0; i<6; i++){
    digitalWrite(motor[i], LOW);
    analogWrite(LED[i], 0);
  }
  for (int i=0; i<6; i++){
    brightness[i] = random(80,256);
    LED_count[i] = 0;
    LED_direction[i] = 1;
    interval[i] = random(250,500);
    motor_count[i] = 0;
    motor_phase[i] = 0;
  }
}
さらに今日の3限には、またまた金重さんと、今回は助っ人の竹下さんも研究室に来て、 「ドラえもん・空気エンジンカー」 を制作して、いつものようにYouTubeに動画を上げた。 今回のは、ちょっと子供ではエアーを押し込むのが大変だった(^_^;)。

藤本さんのインスタにおよその光明が見えてきたところで、今度は同じ佐藤ゼミの望月さんからアポ打診のメイルが届いた。 インスタレーション作品で、豆電球を15個ほど点灯させたいらしい。 もう最終合評は来週末だから、あと10日ほどしかないのだが(^_^;)、間に合うだろうか。 とりあえず、明日は1・2・4・5限に講義があるので3限に、 そして明後日は2・4限と放課後に予定が埋まっているので3限に、と予定を組んだ。 まだ使用する豆電球の現物が無いらしいのが不安だが(^_^;)、 インスタレーション作品であれば、ぜひとも応援しなければ。

ちなみに、予定満載で追い込まれているかというと、実はそうでもない。 明日は怒濤の講義の後で、某新年会カラオケである(*^o^*)。 そして週末には、先々週に続いて、またまた某新年会マラソンカラオケである(*^o^*)。 張り合いがあれば、いくらでも頑張れるし、風邪など無縁である。

2013年1月24日(木)

3回生の望月さんのインスタレーション作品について、昨日に続いて今日もアポがあって一緒に相談した。 昨日の段階で、豆電球でなく「電球色(橙色)」のLED、と方針が決まってさっそく業者に発注した。 こちらも藤本さん作品と同様に、パソコン無しのスタンドアロンにしたい、との事なので、 まずはコンピュータはArduino、という方針となった。

そして、作品を囲む体験者のセンシングには、過去に以下の1期生の作品その他で活躍した、 赤外線近接センサを並べることにした。

YouTube

ところがマルチメディア室のロッカーの在庫を調べてみると、他の作品にも使ったりしたためか、 8個でなく6個しかなかった。 Arduinoのアナログ入力は6チャンネルなので好都合だが、センサのある台座は8角形なのが、ちょっと気になる。 そして、LEDは24個、ということで、Arduinoは12ビットしかないので拡張・・・という話になった。

しかし、ここでフト気付いたのが、 この8チャンネルの距離センサ であった。 これはそのまま、8角形の台座に取り付けられる。 ただし、ArduinoはMIDIを受け落とすので駄目、となると、Propellerで新しく作ればいい。 Propellerなら、MIDIのポート以外に24ビットまで、直接に使える。 でも、最終合評まであと1週間しかないのが、なかなか厳しい。(^_^;)

・・・と考えてみて気付いたのが、今回のSUACボードである。 MIDIも受けられる、そして64ビットまでのLEDを個別に調光制御できるように、PWMライブラリが完成したところだった(^_^)。 これはもう、ソレしかない、という事で、急転直下、SUAC基板の作品への応用の初事例が、この望月作品という事に決定した。 そして、とりあえずシャープの距離センサを段ボール箱に並べて、以下のように作業場が出来た。

そして、8個のセンサからの距離データで、チェック治具として繋いである64個のLEDを、 8個ずつの1列ごとにセンサと対応させて、それぞれの距離に対応して輝度を変化させる、 という以下のPropellerプログラムまで走らせることが出来た。 PWMライブラリはそのまま活用である。
CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  midiIn        : "MidiIn03"
  midiOut       : "MidiOut01"
  pwmOut       : "PwmPortOut02"

PUB start | dummy, i
  midiOut.start(25)
  pwmOut.start
  dummy := midiIn.start(24)
  midiOut.fifoset($C00000 + dummy & $00007F)  ' display total number of Cogs
  pwmOut.buffer_fill(0)

  repeat
    dummy := midiIn.event
    if dummy <> -1
      midiOut.fifoset(dummy)
      if (dummy & $FF0000) == $D00000
        repeat i from 0 to 7
          pwmOut.buffer_set(i<<8 + (dummy & $7F))
      elseif (dummy & $FF0000) == $D10000
        repeat i from 0 to 7
          pwmOut.buffer_set((i+8)<<8 + (dummy & $7F))
      elseif (dummy & $FF0000) == $D20000
        repeat i from 0 to 7
          pwmOut.buffer_set((i+16)<<8 + (dummy & $7F))
      elseif (dummy & $FF0000) == $D30000
        repeat i from 0 to 7
          pwmOut.buffer_set((i+24)<<8 + (dummy & $7F))
      elseif (dummy & $FF0000) == $D40000
        repeat i from 0 to 7
          pwmOut.buffer_set((i+32)<<8 + (dummy & $7F))
      elseif (dummy & $FF0000) == $D50000
        repeat i from 0 to 7
          pwmOut.buffer_set((i+40)<<8 + (dummy & $7F))
      elseif (dummy & $FF0000) == $D60000
        repeat i from 0 to 7
          pwmOut.buffer_set((i+48)<<8 + (dummy & $7F))
      elseif (dummy & $FF0000) == $D70000
        repeat i from 0 to 7
          pwmOut.buffer_set((i+56)<<8 + (dummy & $7F))
これで、順調に行けば、明日にLEDが納品されて、その部分のハンダ付けをやっつければ、 あとは週末も望月さんが木工や造形を頑張って、来週の月・火でなんとかなりそうである。 研究室のドリル一式も貸し出した。 明日には、和田先生のゼミの学生の人工筋肉の制御の相談もあるが、 なんとかそれぞれ、やっつけられそうである。 この、期限が迫ったきた時の緊張感が、何よりイイのである。(^_^)

2013年1月25日(金)

昨日は上記の望月さん関係の作業の後で、放課後に、M2の伊熊さんの修了制作の最後の関門、 口頭試問があった。 結果は、いろいろな意見をいただきつつも「合格」ということで、とりあえずホッとした。 これから最終報告会と卒展に向けて、さらに色々と改良を加えていって欲しい。

今日、例によって目覚めた瞬間に気付いたのは、今回のSUACボードは、2/2(土)のメディアデザインウイーク2013の 「フィジカル・コンピューティング」ワークショップ でお披露目する予定であるが、考えてみれば、 そのイベントで望月さんのインスタレーション作品を展示している、という事実であった(^_^;)。 つまり、この実験基板を作品の内部に組み込んだのでは、ワークショップで「これ(^_^)」と紹介できない。 そこで、午後の3限に望月さんのアポがあるので、午前中に、新たに組み込み用の基板を作ることにした。

ゼロから回路を手配線するにはちょっと時間が苦しいが、さすがのオリジナル汎用実験試作基板である。 以下のように、必要な部分だけに限定して部品を搭載して、無事に、昨日のプログラム「mamiko01.spin」が走る、 MIDI入力とLEDを24個、というPropellerシステムが2時間ほどで完成した。(^_^)

そして午後になって、いろいろ設計ノートを書き込んできた望月さんが研究室に来た。 まだ午後イチの段階でLEDの納品が無かったので、24本のLEDまで行くシールド線をそれぞれの長さまで切る、 という作業をしてもらった。

その後、秋月電子からの正午の便で着いた、ということで業者がLEDを納品してきたが、 造形に没頭しているらしくメイルに返信が無かった(^_^;)。 そして、基板コネクタ側を配線していて発覚したのは、24本の筈のシールドの21番だけ無い、ということで、 この日の作業はここまでとなった。 次は日曜日か月曜日である。果たして完成するのかどうか。

2013年1月28日(月)

いよいよ、メディア造形3回生の「総合演習I」の最終合評が週末の金曜日に、そして僕の水曜日の2つの講義も最終日となる。 しかし、去年の年末に遭遇したのと似た、「お腹に入る風邪」っぽくなり、急遽、放課後のアカペラの中止をメンバーに連絡して、 午後には早々に帰宅して明日まで寝て治すことにした。 午前中にはモーショングラフィクス作品の3回生・大山クンのアポがあったが、作曲した音楽はともかく、まだ映像が完成していなかった。(^_^;)

そして午後には望月さんが来て、以下のように24個のLED(橙色・黄色・白色)を繋ぐ、という作業だけやっつけた。 明日の午後までには全ての造形を完成させて持ってきて、いよいよ肝となるPropellerのプログラミングである。 僕もそれまでに、ひたすら寝て回復して、プログラミングできる体調に復活している必要がある。

これをアップロードしたら、速攻で帰宅である。

2013年1月30日(水)

30分おきにトイレに駆け込むという下痢風邪(ノロにあらず)に悩まされて3日目である。体重が2キロほど減って、これだけはラッキーである(^_^;)。 昨日は前日の午後から18時間ほど寝てそこそこ回復し、午後には以下のように望月さんのインスタレーション作品の仕上げに向かった。 基板取り付けのドリル穴開けなどもあり、ウルトラマンのカラータイマーにように次第に体力が低下していくのがよく分った。 5限には講義がある、と退出する望月さんからセンサとLED番号の対応メモを受け取って、 とりあえずのProcessingプログラムを完成させて夕方に帰宅した。

そして12時間ほど睡眠して、今日は普段より1時間ほど遅い8時前に研究室に出て来た。 後期毎週水曜1限にあった、院生の「メディアデザイン特論」は先週までで終わり、ということで、つかの間の時間である。 今日は2限の「メディアアーツ論」ではNIME04の岩井俊雄さんの基調講演のビデオを紹介、そして3限には望月さんが来て最後のプログラム制作、 4-5限には「サウンドデザイン演習」で、最終課題の合評である。終日、消耗する体力と戦いつつ、ときどきトイレに駆け込むことになる。

2013年1月31日(木)

昨夜もまた、12時間睡眠をとった。月曜日から、18時間・12時間・12時間と寝て、 遂にようやく、治った(^_^)。 今日は午後3限に学科会議、4限に学部教授会と大学院教授会、そして5限に佐藤先生が研究室に来て、 望月さん作品の検収を行う予定であり、午前中に成績処理とか色々な事務を行う予定である。

昨日の3限に望月さんが研究室に来て、上記の写真のような状況で、一応のPropellerプログラムを確認した。 8つの距離センサに対して24個のLEDを反応させる、というところまではOKとなったが、あと一つ、 「センサからLEDの反応までに約1秒の遅れが欲しい」という条件だけが残った。 そこからしばし、ベストでない体調でプログラミングに挑んだが、結論として、これはPropellerで簡単に30分ほどで出来るものでは無い、 と結論付けて、時間的制約から今回の作品については、MacノートのMaxが登場することになった。 Maxを使えば、センサからの入力を「pipe 1000」というオブジェクト1個で簡単に遅延させることができて、 実際にこれで希望する動作は完璧に実現できた。

Propellerのスタンドアロンでは、MIDI入力をFIFOに積んで、そのFIFOデータをメインループで頻繁にモニタして、 イベントがあれば(書き込みと読み出しのポインタが異なっていれば)即刻、読み出して必要な処理をする、 ということで、FIFOが64ワードという浅さで成立している。 これが、1秒という深さにイベントバッファを組むとなると、MIDIでランニングステータスを使わないとしても、 最大でおよそ1msecごと、つまりFIFOバッファは少なくとも1000ワード以上は欲しくなるが、 これは内部RAMの制限から、Propellerにはかなり致命的なキツさである。

IRIXのMIDI処理のように、MIDIドライバから本格的に書き換えて、MIDIイベントにシステムのタイムスタンプまで添えた形にすれば、 おそらくPropellerでも「1秒の遅延」は実現できる可能性があるが、とても今回は間に合わない、という事である。 いずれの課題として、ここは作品展示の際には、台の下にノートパソコンを折り畳んで内蔵する形としてもらう事にした。 以下が記念すべきそのPropellerプログラム、つまり今回のSUACボードが作品に使用される第一号のプログラムである。

CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  midiIn        : "MidiIn03"
  pwmOut       : "PwmPortOut02"

PUB start | dummy, i, led[20], sensor[8]
  pwmOut.start
  dummy := midiIn.start(24)
  pwmOut.buffer_fill(0)
  led[0] := 0
  led[1] := 0
  led[2] := 1

  repeat
    led[0]++
    if led[0] == 1000
      led[0] := 0
      if led[2] == 1
        led[1]++
        if sensor[0] == 0
          pwmOut.buffer_set(led[1])
        if led[1] == 100
          led[2] := 0
      else
        led[1]--
        if sensor[0] == 0
          pwmOut.buffer_set(led[1])
        if led[1] == 0
          led[2] := 1
    dummy := midiIn.event
    if dummy <> -1
      if (dummy & $FF0000) == $D00000
        sensor[0] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[0] < 0
          sensor[0] := 0
        pwmOut.buffer_set(0<<8 + sensor[0])
        pwmOut.buffer_set(23<<8 + sensor[0])
      elseif (dummy & $FF0000) == $D10000
        sensor[1] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[1] < 0
          sensor[1] := 0
        repeat i from 1 to 4
          pwmOut.buffer_set(i<<8 + sensor[1])
      elseif (dummy & $FF0000) == $D20000
        sensor[2] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[2] < 0
          sensor[2] := 0
        repeat i from 5 to 6
          pwmOut.buffer_set(i<<8 + sensor[2])
      elseif (dummy & $FF0000) == $D30000
        sensor[3] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[3] < 0
          sensor[3] := 0
        repeat i from 7 to 9
          pwmOut.buffer_set(i<<8 + sensor[3])
      elseif (dummy & $FF0000) == $D40000
        sensor[4] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[4] < 0
          sensor[4] := 0
        repeat i from 10 to 12
          pwmOut.buffer_set(i<<8 + sensor[4])
      elseif (dummy & $FF0000) == $D50000
        sensor[5] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[5] < 0
          sensor[5] := 0
        repeat i from 13 to 16
          pwmOut.buffer_set(i<<8 + sensor[5])
      elseif (dummy & $FF0000) == $D60000
        sensor[6] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[6] < 0
          sensor[6] := 0
        repeat i from 17 to 19
          pwmOut.buffer_set(i<<8 + sensor[6])
      elseif (dummy & $FF0000) == $D70000
        sensor[7] := ( (dummy & $7F) - 20 ) * 5 / 2
        if sensor[7] < 0
          sensor[7] := 0
        repeat i from 20 to 22
          pwmOut.buffer_set(i<<8 + sensor[7])
最終合評では、ここにMaxの「1秒の遅延」を挟んだバージョンで展示発表し、 その翌日からのメディアデザインウイーク2013では、展示スタッフの手間もあるので、 Maxを省略して、センサからPropellerに直結して、遅延ナシのバージョンで展示、という作戦である。

そして午後になり、学科会議、デザイン学部教授会、大学院教授会、と会議を経て、 5限には以下のように、研究室に佐藤先生に来てもらって検収も完了した。 その後、他ゼミの機材準備を手伝ったりして、明日の最終合評のプレゼン会場での設置を受けて、 MacとMaxのセッティングは明日の朝に行うこととした。やれやれである。

YouTube

・・・しかし実は、こう淡々と書いた裏で、とんでもない数のメイルが行き来して、またまた色々と新しい状況に突入した。 これだから、楽しいのである。風邪が治ってよかった。

明後日に迫った、メディアデザインウイーク・ 「フィジカル・コンピューティング」ワークショップ には、駆け込みで参加者が計10人となった。 内訳は、SUAC教員2人、学部学生1人、院生3人、そして某大学の先生1人、某Y社から2人、某R社から1人、 と、とても「濃い」陣容である。これは楽しみである。

今年の4月から、SUACのマルチメディア室に入っているMax5/MSP/jitterが、ようやくMax6+Genにアップグレードされることになり、 そのやりとりをCycling'74社とやっていた関係で、国内代理店の某社と繋がり、なんとその某社の社長が、 かつて音情研の黎明期に色々とお世話になった友人だ、と判明した。 これからMaxの開発者のDavidともいろいろあるかもしれない、という展開である。

そしてこれらと同時刻に、CQ出版社の編集者から突然のメイルが飛び込んできた。 中身はまだ書けないが、原稿執筆の打診である。 「こんな面白いお話、なんとしても、お受けいたします。ぜひ、やらせて下さい。(^_^)」 と速攻で返信した、とだけ書いておこう。 そして、返信を書いているその瞬間に、その相手から電話がかかってきて、 急転直下、来週末に東京に行く合間に時間を作って、えらく久しぶりに巣鴨のCQ出版社に打合せに行く、と決まった。 まだしばらく先になるが、これはまたまた、素晴らしい話である。 そして何よりこの話、もしかすると、Propellerと、SUAC基板と、繋がるかもしれないのである。 これだから、楽しいのである。(^_^)

2013年2月3日(日)

上記の日付けからもう3日目である。中身の濃い2日間であった。 2月1日の 3回生・「メディア造形総合演習I」最終合評 では、この日記に書いたように、藤本さんと望月さんのインスタレーション作品も無事に発表された。 今回のSUAC基板でPropellerが活躍した望月さんのインスタ作品の様子は、 これこれこれ であり、Arduinoが活躍した藤本さんのインスタ作品の様子は、 これ である。

これまででおそらく初めてであるが、体調を考慮して、何より大好きな最終合評の後の打ち上げに参加せず帰宅して、 2月2日の メディアデザインウイーク・「スケッチング」ワークショップ に臨んだ。 前日のシンガポールでの講演から帰国して浜松に直行してくれた小林さん、 京都から来てくれた落さんとともに、無事に半日のワークショップも有意義な交流とともに成功した。 当日の朝に3時間かけて作った僕のプレゼン資料は これ である。 終了後、メディアデザインウイークの展示を参加者とともに楽しみ、 さらにマインシュロスでの懇親会も、濃い4人で満喫した。 ヤマハとローランドとの新しいネットワークも出来て、今後の展開も楽しみである。

ちなみに、この2月2日の朝には、嬉しいメイルが届いた。 応募していたpaper(structured abstract)が採択されて、8月末にウイーンで開催される国際会議に行ける、と確定した。 忙しい合間に、頑張って応募した甲斐があった。 そして今日、2月3日の午前は、NHK杯の将棋で、事実上の決勝戦となった「羽生vs森内」を堪能した。 まったく森内名人に将棋をさせずに圧勝した羽生さん、流石である(^_^)。

今週の予定としては、明日月曜日の放課後には、メディアデザインウイークのレクチャーとして、 錯視の北岡先生(立命館大)がSUACに登場する。これは必見である。 そして水曜日は終日、院生の修了制作最終発表会があり、週末の金曜日から東京に出張である。 合間を見て、SUAC基板の残ったチェックとして、以下を進めていくことになる。

  • 2つのA/Dコンバータの動作確認
  • マイコンをGainerにしての確認
  • マイコンをArduinoにしての各に
  • マイコンをAKI-H8にしての確認
ただし、次期バージョンの基板については、ちょっと構想として、これまでの延長から変更したい、 というアイデアがいくつか出ている。この日記と前回の日記で進めてきたように、思った以上にPropellerで仕事が出来るので、 「AKI-H8を外す」というのがその第一である。 また、Gainerと互換のようで実は互換でないGainer-Miniをやめて、正規のGainerに戻す、というアイデアもある。 このあたりも含めて、現状バージョンの見直しと改良を検討していきたい。 このあたりが区切りになりそうなので、次からはPart3に進むことにしよう。

続々・Propeller日記(3) へ