続・Propeller日記(1)

長嶋 洋一


Propeller日記(1)

Propeller日記(2)

Propeller日記(3)

Propeller日記(4)

Propeller日記(5)

続・Propeller日記(2)

続・Propeller日記(3)

続・Propeller日記(4)

続・Propeller日記(5)

続々・Propeller日記(1)

続々・Propeller日記(2)

続々・Propeller日記(3)

続々・Propeller日記(4)

2012年8月27日(月)

以前に執筆してWebに上げていた Propeller日記(5) の最後の日付けは「2008年5月21日(水)」であり、それからもう、4年半近く経過してしまった。 この間、まぁそれなりに色々とやってきた。 「日記」モノとしては、「Propeller日記」と並行してチラッとやって見切ってしまった 「Arduino日記」 があったが、さらに終了宣言しないまま宙に浮いているものとして(^_^;)、 「Processing日記」「SuperCollider日記」 がある。いずれも主に海外出張中(乗り継ぎ待ちの空港など、かなり時間があるため)など、 暇がある時に進めてきたものである。

Propellerについては、上記の最後の日記の中で

  • 「トラ技」に原稿執筆
  • 日本音楽知覚認知学会での発表で紹介(5/24)★
  • 音楽情報科学研究会での発表で紹介(5/28)★
  • Sketch03で紹介(6/25-27、米国RISD)★
  • 音情研・夏シンポで詳細紹介(8/6-8)★
  • FIT2008で紹介(9/2-4)★
  • RFIDのリーダ製作予定(ゼミの学生作品)
  • オープンキャンパス/MAF2008でのインスタレーション作品に応用(12画面ディスプレイ)
などと書いていたが、「トランジスタ技術」誌に2月連続で寄稿したのが2008年10月号/11月号、 そして上記のリストの★印については、 ここ を探すと、それぞれ発表したPDFが発掘できる。 RFIDリーダについては楽をしてAKI-H8に譲ったが、 Propellerを13個使った、12画面ディスプレイ利用のインスタ作品 「電子十二影坊」 は盛大に展示発表した。


電子十二影坊

さらにComputer Musicの方では、Propellerを使って2009年に開発した新楽器 「Peller-Min」 とその発展形に関して、

  • 「シーズ指向による新楽器のスケッチング」2009年5月21日『音楽情報科学研究会』(筑波大)
  • 「Parallel Processing System Design with "Propeller" Processor」2009年6月5日『International Conference on New Interfaces for Musical Expression』(カーネギーメロン大学)
  • 「Some interactive works ('08-'09) - case study of my sketching」2009年7月18日『Sketching in Hardware 2009』(University College of London)
  • 「Parallel Processing Platform for Interactive Systems Design」2009年9月2日『International Conference on Entertainment Computing』(Conservatoire National des Arts et Metiers, Paris)
  • 「並列処理プロセッサ"Propeller"によるプラットフォームの検討」2009年12月5日『音楽情報科学研究会』(国立音大)
  • 「Untouchable Instrument “Peller-Min”」2010年6月17日『International Conference on New Interfaces for Musical Expression』(シドニー工科大学)
  • 「Technology for Computer Music / Interactive Multi-Media Performance with New Interfaces」2010年12月6日『Intertnational Festival/Competition SYNC.2010 Tutorial(1)』(The Ural State Conservatory, Yekaterinburg, Russia)
  • 「Untouchable Instruments and Performances」2011年8月2日『International Computer Music Conference』(University of Huddersfield, UK)
  • 「Untouchable Performance and Technology」2011年12月10日『Asia Computer Music Project 2011』(東京電機大)
などの報告をしていて、 ここ を探すと、それぞれ発表したPDFが発掘できる。 この延長から ロシアツアー(2010) も実現できたので、Propellerとの出会いが大きく貢献している。


Peller-Min

2011-2012年には、PropellerでなくArduinoを使った ジャミネータと遊ぼう というプロジェクトが盛り上がり、 メイキング動画 はデザインスタジオIDEOで実際にJaminatorをデザインした本人からも オレゴン で好評を得た(写真右)。 そして今回の「続・Propeller日記」は、この「ジャミーズ娘+」から繋がっているのである。

東京では年に2回開催されている「Make Meeting」を、三輪さん赤松さん小林さんなどお友達の多いIAMAS(大垣)で開催する、 というので2010に「Make Ogaki Meeting 2010」に参加した。 その模様は ここ とか ここ で判ると思う。 そして沈黙の2011年を経て、2012年にまた「MOM2012」がある、というので、 ここに「ジャミーズ娘+」の再演と僕の新作公演、そしてインスタ展示を計画した。

インスタ展示は2作品で、その一つがPropellerを活用した大塚さんの卒制作品「万変鏡」である。 解説は この中 にある (YouTube) 。 そしてもう1作品は、SUACの国際交流提携校である韓国・ホソ大学からの交換留学生であるリュジュンヒー君が、 2012年前期に僕のゼミの「メディア造形演習II」の作品として制作したインスタレーション作品「カラーオーケストラ」である。 解説は この中 にある (YouTube)


万変鏡

「Make Ogaki Meeting 2012」に参加したのが、2012年8月25-26日であった。 この日記を書いている8月27日は、学生は8/26にMOM終了後に浜松に帰ったが、 僕だけ機材を積んだSUAC公用車で朝帰りした日である。 ちなみにMOM2012のライブでの僕の新作「Joyful Boxes」は、 このリュ君の制作したサウンドインスタレーションを「楽器」として使い、 ライブグラフィクスを伴うComputer Music作品として作曲した (YouTube) 。 このMOM2012への参加の模様は ここ(リンク先を含めて) で判ると思う。

そして、 MOM2012出展者情報 の中に、たまたま発見したのが プロペラブ というものであった。 なんとPropellerにハマるという奇特な人達がいたのであった(^_^;)。 この「プロペラブ」にあった プロペラブ Propeller Wiki を見ていると、 先人の方々が記された文書などへのリンク というのがあり、なんと僕の「Propeller日記」がリンクされていた(^_^;)。 光栄である。 MOM2012の場では、展示ブースの「島」がちょうどお隣だったので、挨拶に行った。

そして プロペラブ の中に発見したのが、 「Windows/Linux/Macで動作する開発環境 - 標準のツールだとWindows用のしかないですが、BSTならLinuxやMacでもプロペラを楽しめます!ヤッタネ☆」 という BST - The multi-platform Propeller Tool Suite である。 ちょっと油断するとPropellerとご無沙汰になる最大の理由は、開発環境がWindowsだけ、というものであった。 いつも使うMacで開発できるというのは画期的な朗報である。 このbstとの出会いが、この「続・Propeller日記」のきっかけなのである。 全てはPropellerを軸として繋がっているのであった。

2012年8月28日(火)

(本当のところは上の「8/27」というのも、流れをスムースにするために形として分けただけで、実際には8/28の朝から書いたのであるが(^_^;)) さて、8月28日である。MOM2012が無事に終わって、 次には今週木曜日8/30の午後から9/12まで、 今年2度目の海外(リンツ・ウイーン・スロベニア)に出かける、という慌ただしい期間である。 あまり時間のかかる仕事も出来ないので、隣りのマシンで以下のようにデータの整理/バックアップをしているぐらいなので、 チラッとPropellerと遊んでみるには格好のチャンスである。

とりあえずPropellerはもう忘却の彼方なので、bstの動作実験から、まずは「思い出し」である。 まずはさっそく、 BST - The multi-platform Propeller Tool Suite に行ってみる。

bstには以下の3種類のツールがあるというが、ここでは迷い無く「bst」を選ぶ。

  • bstl - The command line loader
  • bstc - The command line compiler
  • bst - The GUI IDE

bstのダウンロードページ に行くと、LinuxとMacだけでなく、Windows版もあった(^_^;)。 OSX版の最新バージョンは「0.19.3」とあるので、 これ をダウンロードして解凍すると、ドキュメントも何もなくて「bst.app」(たった2.2MB)が出来た。 これでオシマイである。

manualのダウンロードページ からは、最新の0.04バージョンの これ をダウンロードした。 3種類のツール全てをまとめているが、チラッと見てみると、 なんと以下のように「spinをバイトコードで」などという、ソソラレル記述があった。 これはいずれ、きちんとチェックしてみたい。

さて、ツールを実験するにはサンプルソースが必要である。 そこでまず、1106研究室でPropeller開発に使っていた3台のWindowsXPマシン内にあった、 Propeller関連のディレクトリを全部まとめて、重複を消してデータ整理することにした。 かつてはSGI Indyワークステーション(IRIX)が4台とLinuxに入れ替えたノートPCが1台あったが既に廃棄していて、 現在、1106研究室にあるパソコンを数えてみると、Windowsが5台(XPが3台、98と95が各1台)で、 Macは全てOSXで14台(G4PowerBookが2台、iBookが4台、MacBookが2台、miniが5台、MacBookAirが1台)であった。 この写真 は技術造形学科3期生の僕のゼミの卒業アルバム写真(2005年)であるが、ここに写っているパソコン18台(大部分がMac)は全て寿命で廃棄して、 総どっかえになっている。7年もすればパソコンは消える運命にあるようだ。 整理したPropeller関係のディレクトリは以下のようなものである。

何はともあれ、まずはダウンロードしたbstを起動してみると、以下のようになった。 ここでようやく、bstとは「Brad's Spin Tool」、つまりたぶんブラッドさんが作ったspin用(Propeller開発用)ツールの事なんだ、 と判った(^_^;)。 試しのソースは、もっとも古いアーカイブ、つまり最初の「Propeller日記」のあたりのものだろう。 ちょっとMacの画面だと見にくい気もするが、これはスグに馴れるのかな。

開発ツールが立ち上がればさっそくPropellerを繋いでみよう。 しばらく眠っていた、最初のPropeller実験ボード(トラ技に書いた記事で紹介)を引っ張り出してきて、以下のように繋いでみた。 これまで「Propellerの開発ツールはWindows限定」と淋しい気持ちでいたので、この風景は画期的である。

このMacはGainerやXBeeの実験にも使っていて、シリアルのためのFTDIドライバはインストールされているためか、 メニューの「Compile」の中にあった「Detect Propeller」を選ぶと、何もしないでも以下のようにアッサリとPropellerを認識した(^_^)。

Propellerのソースコードのコンパイルも、Propeller上のRAMへの転送もあっさりと成功してしまったので、 次に、トラ技の記事の「テレビ(NTSC)出力」のソースコード(proto004.spin)に切り換えてみた。 こちらもコンパイルはOK、ところが「Compile and Load Ram」のところでヘンな現象が起きた。 まず、秒殺で完了する筈のPropellerへのロードで、以下のようにしばしストップする。これは既に異常である。

その後、10秒ほどすると、そのまま何もしないのに以下のように表示される。Crashとは嫌な情報である(^_^;)。

ここには「OK」ボタンしかないので、仕方なくクリックするとこのメッセージウインドウが消える。 ところが、この状態で、bstはハングアップして、ウインドウ内のクリックも、Quitのためのショートカットも、全て反応しなくなる(^_^;)。 「虹色グルグル」が出たままである。 ところが、他のアプリとかOSXは普通に生きている(Unixなのでこれは当然)。 ただし、OSXユーティリティの「アクティビティモニタ」からbstのプロセスをkillしようとしても、頑として無視する。 まさにハングアップである。(^_^;)

そして、Propellerと繋いでいたUSBケーブルを抜くと、以下のような謎のウインドウが出て、bstは生き返る。 この状態で「OK」をクリックすると、何事も無かったように復帰する。 この現象は何度となく試した結果、確実に同じことが起きることを確認した。 要するにUSBインターフェースの部分で、ハングアップというよりはタイムアウトエラーを起こしていた。

この現象は確実に起きるので、原因も明確である可能性が高い。 簡単なプログラムではエラーが起きないので、オブジェクトのサイズが関係するのか、 あるいは外部モジュールを参照すると問題なのか(ただしソースのコンパイルではエラーが起きない)。 ここまでが異常に順調だったが、ここで、楽しい(^_^;)試行錯誤タイムに突入した。

・・・ここでしばし、日常のお仕事の中断があったが、遂に原因が判明して解決した。 試しにMacBookAirで同じことをやったら、問題なくロード出来たのでピンと来たのが、 この「お仕事用Mac mini」は、Sketching in Hardwareなどに持参する「出張用MacBookAir」よりも、 開発環境としては「古い」、という点である。 そこで調べてみると、インストールしていたFTDIシリアルドライバのバージョンは「2.0.9」だった。 ここ に久しぶりに行ってみると、なんと現状は「2.0.18」と、マイナーバージョンが9段階も改訂されていた。 これをインストールしたところ、以下のように何事もなかったようにロードに成功して、ビデオ出力が表示できた。 ようやく、これで解決である。(^_^)

ちょっと思い出してきたので、ここでいよいよ、何か新しく作ってみることにした。 過去のプログラムなんてのは忘れていて思い出すのが大変なので、いっそ新しいものを作った方が早いのである。 考えてみると、MOM2012で展示した、OGの大塚さんの卒制作品「万変鏡」は、あまりドキュメントが残っていなかったので、 ここに深入りすることにした。 大塚さんの「万変鏡」のプレゼン(Flash)は これ である。

この「OLED」というのは、正式には「uOLED-96-PROP」というもので、 4D System社 が開発したフルカラーOLEDモジュールで、なんとPropellerがディスプレイ制御を行っている。 96*64ドット(アスペクト比3:2)に対して、「RGBあわせて8ビット」という疑似フルカラーを実現する小型モジュールであるが、 残念ながら製造中止となって、現在では入手できない。僕の手元にあるのは、実験機に組み込んだ2個を入れて、計4個だけである。 既にネットからは消えているが マニュアル もあるので、これをディスプレイとして使うことにしよう。

そしてもう一つ、ジャミーズ娘+では5台の改造ジャミネータからのMIDI出力をマージしていたが、 ちょうど最近、XBeeをやったところだった(XBeeはこれまで「エックスビー」と内心、呼んでいたのだが、MOMでは「ジグビー」と呼ぶ人がいた。それは規格のZigBeeじゃないのかなぁ)。 SUACの国際交流提携校である韓国・ホソ大学からの交換留学生であるイーギョンフン君も、 2012年前期に僕のゼミで「メディア造形演習II」に取り組み、パフォーマンス作品「日本の音風景」を制作した。 解説は この中 にある (YouTube) 。 ここで、中古の三味線を改造してセンサ情報をMax/MSP/jitterに伝えていたのがXBeeである。 もともと、M2の伊熊さんの修了制作に使うということで、 このように 実験していたものを、さっそくイー君の三味線に使ったわけである。 せっかくなので、このXBeeも、Propellerで料理してみることにしよう。

これでようやく、ここからハンダ付けである。 大体はここ でやった作業(電池ケースを単3から単4に小型化)なので詳細は省略するが、 XBee用のゲタ基板を作り、電池ケースに両面テープ(プロ仕様の硬化するやつ)で取り付け、今回は38400bpsで設定したXBeeを載せた、ということで、 以下のような流れである。

ホストのMax側のパッチはほとんど変わらないが、今回はPropellerにディスプレイデータを送る、という作業がメインとなる。 一方、Propellerモジュールの方は、前回はシリアルポートを内蔵しているArduinoだったところを、 PropellerのCogのソフトによって、1ビットごとに監視してXBeeからの38400シリアルを確実に受け取る必要がある。 MIDI受信モジュールをアセンブラで完全に自作しているので、31250から38400にちょっとだけ変換すればいい、 というのが基本だが、途中のデバッグはかなり困難なので、「作って試す」というループはとても長大である。 さてどうなるか、ここで18時になったので、続きは明日にやってみよう。

2012年8月29日(水)

さて翌日、渡欧前日となったので、研究室にある海外コンセントアダプターとかトランスとかを準備した。 午後には1回生の金重さんと「ドラえもん」制作の予定があるので、時間との勝負である。 どこまで進めるか。 スタートラインは以下のような風景である。 Macと2台のXbeeモジュール(片方にはPropellerのOLEDディスプレイモジュール)、という美しい風景である。

いきなりXBeeを使ってMaxとPropellerが通信する、という確認に入るには、「Propellerの思い出し」というステップが障害となるので、まず最初に以下のように、「XBeeから何かデータが飛んで来たら表示する」というMaxパッチを作って走らせておくことにした。

そしていよいよPropellerである。 過去のアーカイブを調べて、ディレクトリ「project_OLED」とディレクトリ「project_Peller_Min」から、以下のモジュールをコピー/リネームして、今回の実験用に作ったディレクトリ「project_XBee」に置いた。

  • OLED-Driver1.spin - OLED付属のディスプレイドライパを改造したもの
  • OLED-Driver2.spin - OLED付属のディスプレイドライパを改造したもの
  • MIdi_In.spin - オリジナル開発したMIDI入力ドライバ
  • MIdi_Out.spin - オリジナル開発したMIDI出力ドライバ
  • Demo_OLED.spin - 加速度センサで円表示を移動させ座標をMIDI出力したシステム
  • PellerMin.spin - 新楽器「Peller-Min」のPropellerソフト。計32チャンネルのテルミン情報をMIDI出力

「OLED-Driver1.spin」は以下である。

CON
  _OUTPUT       = 1             'Sets pin to output in DIRA register
  _INPUT        = 0             'Sets pin to input in DIRA register  
  _HIGH         = 1             'High=ON=1=3.3v DC
  _ON           = 1
  _LOW          = 0             'Low=OFF=0=0v DC
  _OFF          = 0
  _ENABLE       = 1             'Enable (turn on) function/mode
  _DISABLE      = 0             'Disable (turn off) function/mode
' _OLED_Data    = 7..0          'OLED Data lines (This line is here for reference, not for code)
  _OLED_CS      = 8             'OLED chip select (low = select)
  _OLED_RESET   = 9             'OLED reset (low = reset)
  _OLED_DorC    = 10            'OLED data or command select (low = command, high = data)
  _OLED_WR      = 11            'OLED write             
  _OLED_RD      = 12            'OLED read
  _OLED_VCCE    = 13            'OLED VCC enable
  _SET_COLUMN_ADDRESS   =       $15
  _SET_ROW_ADDRESS      =       $75
  _SET_CONTRAST_RED     =       $81
  _SET_CONTRAST_GREEN   =       $82
  _SET_CONTRAST_BLUE    =       $83
  _SET_CONTRAST_MASTER  =       $87
  _CONTRAST_RED_2ND     =       $8A
  _CONTRAST_GREEN_2ND   =       $8B
  _CONTRAST_BLUE_2ND    =       $8C
  _REMAP_COLOUR_SETTINGS=       $A0
  _DISPLAY_START_LINE   =       $A1
  _DISPLAY_OFFSET       =       $A2
  _DISPLAY_NORMAL       =       $A4
  _DISPLAY_ALL_ON       =       $A5
  _DISPLAY_ALL_OFF      =       $A6
  _DISPLAY_INVERSE      =       $A7
  _MUX_RATIO            =       $A8
  _MASTER_CONFIGURE     =       $AD
  _DISPLAY_OFF          =       $AE
  _DISPLAY_ON           =       $AF
  _POWERSAVE_MODE       =       $B0
  _PHASE_PRECHARGE      =       $B1
  _CLOCK_FREQUENCY      =       $B3
  _SET_GRAYSCALE        =       $B8
  _RESET_GRAYSCALE      =       $B9
  _PRECHARGE_RGB        =       $BB
  _SET_VCOMH            =       $BE
  _NOP                  =       $E3
  _LOCK_COMMAND         =       $FD
  _DRAW_LINE            =       $21
  _DRAW_RECTANGLE       =       $22
  _COPY_AREA            =       $23
  _DIM_WINDOW           =       $24
  _CLEAR_WINDOW         =       $25
  _FILL_ENABLE_DISABLE  =       $26
  _SCROLL_SETUP         =       $27
  _STOP_SCROLL          =       $2E
  _START_SCROLL         =       $2F
  _256_COLOURS          =       $32                     '%0100_0000
  _65K_COLOURS          =       $72                     '%0100_1000

VAR
  long OLED_cog                 'Cog flag/ID
  long MemDispPtr               'A pointer to the beginning of the bitmap screen in HUB RAM
  long CSpin                    'Pin number - so pin can be used in new cog
  long DorCpin                  'Pin number - so pin can be used in new cog
  long WRpin                    'Pin number - so pin can be used in new cog
  long RESpin                   'Pin number - so pin can be used in new cog
  long VCCEpin                  'Pin number - so pin can be used in new cog
  long RDpin                    'Pin number - so pin can be used in new cog
  long FrameTime                'Variable holding time of last frame update

PUB start(_MemDisp) : okay
'' Start the ASM display driver after doing a Spin initialize
'' Setup I/O pins, initiate variables, starts a cog
'' returns cog ID (1-8) if good or 0 if no good
  stop                                                  'Keeps two cogs from running at the same time
  MemDispPtr := _MemDisp                                'Copy pointer passed from main program
  CSpin := _OLED_CS                                     'Store I/Os in variables for ASM to access
  DorCpin := _OLED_DorC
  WRpin := _OLED_WR
  RESpin := _OLED_RESET
  VCCEpin := _OLED_VCCE
  RDpin := _OLED_RD
  okay:= OLED_cog:= cognew(@ENTRY, @MemDispPtr) + 1     'Returns 0-8 depending on success/failure

PUB stop
'' Stops ASM display driver - frees a cog
  if OLED_cog                                           'Is cog non-zero?
    dira[7..0] := $FF                                   'Set data as outputs, 8-bits wide
    dira[_OLED_CS] := _OUTPUT                           'Set cs as output
    dira[_OLED_RESET] := _OUTPUT                        'Set reset as output
    dira[_OLED_DorC] := _OUTPUT                         'Set DorC as output
    dira[_OLED_WR] := _OUTPUT                           'Set write as output
    dira[_OLED_VCCE] := _OUTPUT                         'Set VCCE as output
    outa[_OLED_DorC] := _HIGH                           'Set the pins high (keeps ASM from being able to control the OLED
    outa[_OLED_RD] := _HIGH
    outa[_OLED_WR] := _HIGH
    outa[_OLED_CS] := _HIGH
    outa[_OLED_VCCE] := _HIGH
    cogstop(OLED_cog~ - 1)                              'Stop the cog and then make value of flag zero
    powerDown                                           'Perform a power down sequence in Spin
    dira[7..0] := $00                                   'Set data as inputs
    dira[_OLED_CS] := _INPUT                            'Set cs as input
    dira[_OLED_RESET] := _INPUT                         'Set reset as input
    dira[_OLED_DorC] := _INPUT                          'Set DorC as input
    dira[_OLED_WR] := _INPUT                            'Set write as input
    dira[_OLED_VCCE] := _INPUT                          'Set VCCE as input
    outa[_OLED_DorC] := _LOW                            'Set the pins low
    outa[_OLED_RD] := _LOW
    outa[_OLED_WR] := _LOW
    outa[_OLED_CS] := _LOW
    outa[_OLED_VCCE] := _LOW

PUB frameStat : value
'' Returns the time (ms) that it took to paint the last screen to the OLED memory
  value := FrameTime/(clkfreq/1_000)

PRI powerDown
'' Power down of OLED display based on datasheet
  writeCMD(_DISPLAY_OFF)                                'Send the display off command
  outa[_OLED_VCCE] := _OFF                              'Turn off the OLED VCC voltage
  pauseMSec(100)                                        'Observe a waiting period before next action

PRI writeCMD (cmd)
'' Send a one byte command to OLED Display
  outa[_OLED_DorC] := _LOW                              'Set line for command
  outa[_OLED_CS] := _LOW                                'Select the OLED display
  outa[_OLED_WR] := _LOW                                'Prepare to write to the register
  outa[7..0] := cmd.byte[0]                             'Send the 8-bit data
  outa[_OLED_WR] := _HIGH                               'Latch in the command
  outa[_OLED_CS] := _HIGH                               'Deselect OLED display  
  outa[_OLED_DorC] := _HIGH                             'Restore state - data

PRI pauseMSec(Duration)
  waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)

DAT
' Assembly Language display driver for uOLED-96-PROP
' copy HUB RAM bit-mapped memory to OLED Graphics RAM
'
                        org
ENTRY                   mov t0, par                     'Load address of parameter list into t1 (par contains address)

                        rdlong memptr, t0               'Read value of dispMemptr from main memory

                        add t0, #4                      'Increament address pointer by four bytes      
                        rdlong CSp, t0                  'Read value of CSpin from main memory
                        mov CSmask, #1                  'Load mask with a 1          
                        shl CSmask, CSp                 'Create mask for the proper I/O pin by shifting

                        add t0, #4                      'Increament address pointer by four bytes
                        rdlong DorCp, t0                'Read value of DorCpin from main memory
                        mov DorCmask, #1                'Load mask with a 1          
                        shl DorCmask, DorCp             'Create mask for the proper I/O pin by shifting
                        
                        add t0, #4                      'Increament address pointer by four bytes
                        rdlong WRp, t0                  'Read value of WRpin from main memory
                        mov WRmask, #1                  'Load mask with a 1          
                        shl WRmask, WRp                 'Create mask for the proper I/O pin by shifting

                        add t0, #4                      'Increament address pointer by four bytes
                        rdlong RESp, t0                 'Read value of RESpin from main memory
                        mov RESmask, #1                 'Load mask with a 1          
                        shl RESmask, RESp               'Create mask for the proper I/O pin by shifting

                        add t0, #4                      'Increament address pointer by four bytes
                        rdlong VCEp, t0                 'Read value of VCCEpin from main memory
                        mov VCEmask, #1                 'Load mask with a 1          
                        shl VCEmask, VCEp               'Create mask for the proper I/O pin by shifting

                        add t0, #4                      'Increament address pointer by four bytes
                        rdlong RDp, t0                  'Read value of RDpin from main memory
                        mov RDmask, #1                  'Load mask with a 1          
                        shl RDmask, RDp                 'Create mask for the proper I/O pin by shifting

                        add t0, #4                      'Increament address pointer by four bytes
                        mov FrmTm, t0                   'Move pointer value for the Frame Per Second measurement

                        mov t1, #0                      'Initialize t1 (ensure it is zero)
                        or t1, CSmask                   'Create a composite mask for all pins (or them into t1)
                        or t1, DorCmask                 '
                        or t1, WRmask                   '
                        or t1, RESmask                  '
                        or t1, VCEmask                  '
                        or t1, RDmask                   '
                        or t1, #$ff                     'Data lines on pins 0..7
                        mov dira, t1                    'Set CS, DorC, WR, RES, VCCE and data as outputs

                        or outa, CSmask                 'Set the CSpin high
                        or outa, DorCmask               'Set the DorCpin high
                        or outa, WRmask                 'Set the WRpin high
                        or outa, RDmask                 'Set the WRpin high
                        andn outa, RESmask              'Set the RESpin low
                        andn outa, VCEmask              'Set the VCEpin low

                        call #INITIALIZE                'Initialize the display

                                                        'Data sent left to right, top to bottom
ScrnStart               mov s0, cnt                     'Get the current count for statistics
                        CALL #STATCALC                  'Go calculate the statistics
                        mov rowcnt, #0                  'Reset the row counter
:row                    mov colcnt, #0                  'Reset the column counter
:col                    mov offset, colcnt              'Load the current column in offset
                        shr offset, #2                  'Divide by 4 (4 pixels wide is a tile)
                        shl offset, #6                  'Multiply by 64 (lines on the display)
                        add offset, rowcnt              'Add in the row we are working with, offset is in long
                        shl offset, #2                  'Multiply by 4, offset is now in bytes (for HUB RAM)
                        add offset, memptr              'Offset is now the entire HUB RAM address

                        rdlong t3, offset               'Get a long from HUB RAM, 4 pixels
                        mov t2, #4                      'Load the counter for four bytes per long to clock out
:bytechck               mov cdata, t3                   'Move the data over for sending
                        call #SENDDATA                  'Send the data
                        shr t3, #8                      'Shift the long from memory right by eight to get next byte
                        djnz t2, #:bytechck             'Check if there is a next byte, if so repeat
                        
                        cmp colcnt, colpixel    wz      'Test if all the columns have been written (0 to 95), but colpixel is 92 because of long clocking
              if_nz     add colcnt, #4                  'No, add another four columns (four pixels in a long
              if_nz     jmp #:col                       'No, clock out another column
                        
                                                        'Run the row test if colcnt = colpixel (all columns clocked out)
                        cmp rowcnt, rowpixel    wz      'Test if all the rows have been written (0 to 63)
              if_nz     add rowcnt, #1                  'No, add another row
              if_nz     jmp #:row                       'No, clock out another row
                        jmp #ScrnStart                  'Yes - do it all over again                                                                                      

SENDDATA                or outa, DorCmask               'Ensure the data/command line is high
                        andn outa, CSmask               'Ensure the OLED display is selected by putting line low
                        andn outa, WRmask               'set the write line low
                        andn outa, #$FF                 'Clear the data on the output lines
                        and cdata, #$FF
                        or outa, cdata                  'Set the data on the output lines
                        or outa, WRmask                 'Latch in the write                        
SENDDATA_ret            ret                             'Return to calling program

SENDCMD                 andn outa, DorCmask             'Ensure the data/command line is low
                        andn outa, CSmask               'Ensure the OLED display is selected by putting line low
                        andn outa, WRmask               'set the write line low
                        andn outa, #$FF                 'Clear the command on the output lines
                        or outa, cdata                  'Set the command on the output lines
                        or outa, WRmask                 'Latch in the write                        
SENDCMD_ret             ret                             'Return to calling program

INITIALIZE              'Perform line reset
                        andn outa, RESmask              'Set the reset line low (resets the display)
                        mov r0, #$1FF                   'move clock ticks into R0
                        add r0, cnt                     'add in the current clock state
                        waitcnt r0, #0                  'pause
                        or outa, RESmask                'Set the reset line high (displays starts to init)

                        'Power on VCCE
                        andn outa, VCEmask              'Set the VCCE pin low (no voltage)
                        mov cdata, DISPLAY_OFF          'Ensure the display is off via software
                        call #SENDCMD                   '
                        or outa, VCEmask                'Set the VCCE pin high (voltage)
                        mov r0, #$1FF                   'move clock ticks into R0
                        add r0, cnt                     'add in the current clock state
                        waitcnt r0, #0                  'pause

                        'Send a barrage of commands for initializing
                        mov cdata, DISPLAY_NORMAL       'Normal display
                        call #SENDCMD                   '
                        
                        mov cdata, CLOCK_FREQUENCY      'Clock & frequency
                        call #SENDCMD                   '
                        mov cdata, #$F0                 'Data for above command
                        call #SENDCMD                   '
                        
                        mov cdata, DISPLAY_OFFSET       'Set display offset
                        call #SENDCMD                   '
                        mov cdata, #$00                 'Data for above command
                        call #SENDCMD                   '
                        
                        mov cdata, MUX_RATIO            'Duty
                        call #SENDCMD                   '
                        mov cdata, #63                  'Data for above command (Yes, this is decimal)
                        call #SENDCMD                   '

                        mov cdata, MASTER_CONFIGURE     'Master Configure
                        call #SENDCMD                   '
                        mov cdata, #$8E                 'Data for above command
                        call #SENDCMD                   '
                        
                        mov cdata, DISPLAY_START_LINE   'Master Configure
                        call #SENDCMD                   '
                        mov cdata, #$00                 'Data for above command
                        call #SENDCMD                   '
   
                        mov cdata, REMAP_COLOUR_SETTINGS'Set re-map color/depth
                        call #SENDCMD                   '
                        mov cdata, COLORS256            'Data for above command
                        call #SENDCMD                   '

                        mov cdata, SET_CONTRAST_MASTER  'Set master contrast
                        call #SENDCMD                   '
                        mov cdata, #$0F                 'Data for above command
                        call #SENDCMD                   '

                        mov cdata, SET_CONTRAST_RED     'Set contrast current for A
                        call #SENDCMD                   '
                        mov cdata, #$FF                 'Data for above command
                        call #SENDCMD                   '

                        mov cdata, SET_CONTRAST_GREEN   'Set contrast current for B
                        call #SENDCMD                   '
                        mov cdata, #$FF                 'Data for above command
                        call #SENDCMD 

                        mov cdata, SET_CONTRAST_BLUE    'Set contrast current for C
                        call #SENDCMD                   '
                        mov cdata, #$FF                 'Data for above command
                        call #SENDCMD
              
                        mov cdata, PRECHARGE_RGB        'Set pre-charge voltage of color A B C
                        call #SENDCMD                   '
                        mov cdata, #$3E                 'Data for above command
                        call #SENDCMD

                        mov cdata, SET_VCOMH            'Set VcomH
                        call #SENDCMD                   '
                        mov cdata, #$3E                 'Data for above command
                        call #SENDCMD

                        mov cdata, POWERSAVE_MODE       'Set power saving mode
                        call #SENDCMD                   '
                        mov cdata, #$00                 'Data for above command
                        call #SENDCMD

                        mov cdata, PHASE_PRECHARGE      'Set pre & dis charge
                        call #SENDCMD                   '
                        mov cdata, #$11                 'Data for above command
                        call #SENDCMD

                        mov cdata, DISPLAY_ON           'Set display on
                        call #SENDCMD                   '
                                                        'The following commands set parameters, but also ensure starting point on display
                        mov cdata, SET_COLUMN_ADDRESS   'set column address command (and moves cursor)
                        call #SENDCMD
                        mov cdata, #$00
                        call #SENDCMD
                        mov cdata, #$5F
                        call #SENDCMD
                        mov cdata, SET_ROW_ADDRESS      'set row address command (and moves cursor)
                        call #SENDCMD
                        mov cdata, #$00
                        call #SENDCMD
                        mov cdata, #$3F
                        call #SENDCMD

INITIALIZE_ret         ret                              'Return to calling program

STATCALC                mov r1, s0                      'Move the start time for a frame
                        sub r1, s1                      'Subtract the start time of the previous frame (includes stat calc)
                        wrlong r1, FrmTm                'Write the value to the HUB RAM

                        mov s1, s0                      'Store the latest frame start time to the previous start time

STATCALC_ret            ret                             'Return to calling program
                        
colpixel                long 92                 'zero based, OLED is 96 x 64, but because of column count by four, this is 92
rowpixel                long 63                 'zero based
SET_COLUMN_ADDRESS      long $15
SET_ROW_ADDRESS         long $75
SET_CONTRAST_RED        long $81
SET_CONTRAST_GREEN      long $82
SET_CONTRAST_BLUE       long $83
SET_CONTRAST_MASTER     long $87
CONTRAST_RED_2ND        long $8A
CONTRAST_GREEN_2ND      long $8B
CONTRAST_BLUE_2ND       long $8C
REMAP_COLOUR_SETTINGS   long $A0
DISPLAY_START_LINE      long $A1
DISPLAY_OFFSET          long $A2
DISPLAY_NORMAL          long $A4
DISPLAY_ALL_ON          long $A5
DISPLAY_ALL_OFF         long $A6
DISPLAY_INVERSE         long $A7
MUX_RATIO               long $A8
MASTER_CONFIGURE        long $AD
DISPLAY_OFF             long $AE
DISPLAY_ON              long $AF
POWERSAVE_MODE          long $B0
PHASE_PRECHARGE         long $B1
CLOCK_FREQUENCY         long $B3
SET_GRAYSCALE           long $B8
RESET_GRAYSCALE         long $B9
PRECHARGE_RGB           long $BB
SET_VCOMH               long $BE
NNOP                    long $E3
LOCK_COMMAND            long $FD
COLORS256               long $32                '256 colors, 8bit (%RRR_GGG_BB)
COLORS65K               long $72

t0            res 1                             'temporary variable 0
t1            res 1                             'temporary variable 1
t2            res 1                             'temporary variable 2
t3            res 1                             'temporary variable 3
r0            res 1                             'temporary variable for use in subroutines
r1            res 1                             'temporary variable for use in subroutines
s0            res 1                             'temporary for statistics
s1            res 1                             'temporary for statistics                             
CSp           res 1                             'Value of pin assignment
CSmask        res 1                             'Mask of pin assignment
DorCp         res 1                             'Value of pin assignment
DorCmask      res 1                             'Mask of pin assignmenr          
WRp           res 1                             'Value of pin assignement
WRmask        res 1                             'Mask of pin assignment
RESp          res 1                             'Value of pin assignment
RESmask       res 1                             'Mask of pin assignment
VCEp          res 1                             'Value of pin assignment
VCEmask       res 1                             'Mask of pin assignment
RDp           res 1                             'Value of pin assignment
RDmask        res 1                             'Mask of pin assignment
rowcnt        res 1                             'Counter of rows for processing
colcnt        res 1                             'Counter of columns for processing
pxmask        res 1                             'Mask of pixel to process
offset        res 1                             'Offset for use with memptr to read HUB RAM
memptr        res 1                             'Pointer to HUB RAM of display memory
cdata         res 1                             'byte variable to hold color data to be sent to OLED
FrmTm         res 1                             'Pointer to FPS measurement
「OLED-Driver2.spin」は以下である。
CON
  _OUTPUT       = 1             'Sets pin to output in DIRA register
  _INPUT        = 0             'Sets pin to input in DIRA register  
  _HIGH         = 1             'High=ON=1=3.3v DC
  _ON           = 1
  _LOW          = 0             'Low=OFF=0=0v DC
  _OFF          = 0
  _ENABLE       = 1             'Enable (turn on) function/mode
  _DISABLE      = 0             'Disable (turn off) function/mode
  
VAR 
  long  GRAPHICS_cog            'Cog flag/ID
  long  Xtiles                  'Number of x tiles, each tile is 4 pixels by 4 pixels
  long  Ytiles                  'Number of y tiles, each tile is 4 pixels by 4 pixels
  long  BitmapBase              'Address of the start of the bitmap video memory
  long  BitmapLongs             'Number of longs in the bitmap video memory
  
PUB setup(_xtiles, _ytiles, _baseptr)
'' Setup the bitmap parameters for the graphics driver, must be run
''  _xtiles    - number of x tiles (tiles are 4 x 4 pixels each because 8-bits x 4 pixes = long)
''  _ytiles    - number of y tilesl
''  _baseptr   - base address of bitmap
  Xtiles := _xtiles
  Ytiles := _ytiles
  BitmapBase := _baseptr                                'Calculate the values to be used by other routines
  BitmapLongs := _xtiles * (_ytiles << 2)

PUB clear
'' Clear bitmap (write zeros to all pixels)
  longfill(BitmapBase, 0, BitmapLongs)                  'Fill the bitmap with zeros

PUB copy(_destptr)
'' Copy bitmap to new location for use as double-buffered display (flicker-free)
''  _destptr   - base address of destination bitmap
  longmove(_destptr, BitmapBase, BitmapLongs)           'Copy bitmap to new destination

PUB plotPixel(_x0, _y0, _color) | videoOffset, pixelValue
''' Plot at pixel at x, y, with the appropriate 8-bit color
''  _x         - coordinate of the pixel
''  _y         - coordinate of the pixel
''  _color     - 8-bit color value (RRRGGGBB)
  'This byte version work
  videoOffset := BitmapBase + (_x0 >> 2) * (Ytiles << 4) + (_x0 & %11) + (_y0 << 2)
  pixelValue := byte[videoOffset]
  pixelValue := (_color & $FF)
  byte[videoOffset] := pixelValue
{
  'This long version works
  videoOffset := (BitmapBase >> 2) + (_x >> 2) * (Ytiles << 2) + _y
  pixelValue := long[0][videoOffset]
  pixelValue := pixelValue & !($ff << ((_x & %11) << 3))
  pixelValue := pixelValue | ((_color &$FF) << ((_x & %11) << 3))
  long[0][videoOffset] := pixelValue
}

PUB plotLine(_x0, _y0, _x1, _y1, _color) | dx, dy, difx, dify, sx, sy, ds
'' Plot a line from _x0,_y0 to _x1,_y1 with the appropriate 8-bit color
''  _x0, _y0   - coordinate of the start pixel
''  _x1, _y1   - coordinate of the end pixel
''  _color     - 8-bit color value (RRRGGGBB)
''  Based on routine from Phil on Parallax Forum
  difx := ||(_x0 - _x1)         'Number of pixels in X direciton.
  dify := ||(_y0 - _y1)         'Number of pixels in Y direction.
  ds := difx <# dify            'State variable change: smaller of difx and dify.
  sx := dify >> 1               'State variables: >>1 to split remainders between line ends.
  sy := difx >> 1
  dx := (_x1 < _x0) | 1         'X direction: -1 or 1
  dy := (_y1 < _y0) | 1         'Y direction: -1 or 1
  repeat (difx #> dify) + 1     'Number of pixels to draw is greater of difx and dify, plus one.
    plotPixel(_x0, _y0, _color) 'Draw the current point.
    if ((sx -= ds) =< 0)        'Subtract ds from x state. =< 0 ?
      sx += dify                '  Yes: Increment state by dify.
      _x0 += dx                 '       Move X one pixel in X direciton.
    if ((sy -= ds) =< 0)        'Subtract ds from y state. =< 0 ?
      sy += difx                '  Yes: Increment state by difx.
      _y0 += dy                 '       Move Y one pixel in Y direction.
      
PUB plotCircle(_x0, _y0, _radius, _color) | sum, x, y
'' Plot a circle with center _x0,_y0 and radius _radius with the appropriate 8-bit color
''  _x0, _y0   - coordinate of the center of the circle
''  _radius    - radius, in pixels, of the circle
''  _color     - 8-bit color value (RRRGGGBB)
''  Based on routines from Paul Sr. on Parallax Forum
  x := 0
  y := _radius
  sum := (5-_radius*4)/4
  circleHelper(_x0, _y0, x, y, _color)
  repeat while (x < y) 
    x++
    if (sum < 0) 
      sum += 2*x+1
    else 
       y--
       sum += 2*(x-y)+1
    circleHelper(_x0, _y0, x, y, _color)
  circleHelper(_x0, _y0, x, y, _color)
    
PUB plotSprite(_x0, _y0, _spritePTR) | xpix, ypix, x, y
'' Plot a pixel sprite into the video memory.
''  _x0, _y0   - coordinate of the center of the sprite
''  _spritePTR - pointer to pixel sprite memory location
''  long
''  byte xpixels, ypixels, xorigin, yorigin
''  long %RRRGGGBB, %RRRGGGBB, %RRRGGGBB, %RRRGGGBB
''  long %RRRGGGBB, %RRRGGGBB, %RRRGGGBB, %RRRGGGBB
''  long %RRRGGGBB, %RRRGGGBB, %RRRGGGBB, %RRRGGGBB
''  long %RRRGGGBB, %RRRGGGBB, %RRRGGGBB, %RRRGGGBB
''  .... 
  xpix := byte[_spritePTR][0]
  ypix := byte[_spritePTR][1]
  repeat y from 0 to ypix-1
    repeat x from 0 to xpix-1
      plotPixel(_x0+x, _y0+y, byte[_spritePTR][4+x+(xpix*y)])

PUB plotChar(_char, _xC, _yC, _font, _color) | row, col
'' Plot a single character into the video memory.
''  _char      - The character
''  _xC        - Text column (0-11 for 8x8 font, 0-15 for 5x7 font)
''  _yC        - Text row (0-7 for 8x8 and 5x7 font)
''  _font      - The font, if 1 then 8x8, else 5x7
''  _color     - 8-bit color value (RRRGGGBB)
''  Based on routines from 4D System uOLED driver    
   _char := (_char - " ") << 3
   if _font                                             ' font 1 8x8 
      _xC <<= 3                                         ' x 8
      _yC <<= 3                                         ' x 8
      repeat row from 0 to 7
         repeat col from 0 to 7
            if font_8x8[_char+row] & $80 >> col
               plotPixel(_xC+col,_yC+row, _color)
   else                                                 ' font 0 5x7
      _xC *= 6                                          ' x 6
      _yC *= 8                                          ' x 7
      repeat row from 0 to 7
         repeat col from 1 to 6
            if font_5x7[_char+row] & $01 << col
               plotPixel(_xC+col,_yC+row, _color)
               
PUB plotText (_xC, _yC, _font, _color, _str) | t
'' Plot a string of characters into the video memory.
''  _xC        - Text column (0-11 for 8x8 font, 0-15 for 5x7 font)
''  _yC        - Text row (0-7 for 8x8 and 5x7 font)
''  _font      - The font, if 1 then 8x8, else 5x7
''  _color     - 8-bit color value (RRRGGGBB)
''  _str       - String of characters
''  Based on routines from 4D System uOLED driver
  repeat strsize(_str)
    plotChar((byte[_str++]), _xC++, _yC, _font, _color)
     if _font
        if _xC > 95 / 8
          _xC := 0
          _yC += 1
     elseif _xC > 95 / 6
       _xC := 0
       _yC += 1

PRI circleHelper(_cx, _cy, _x, _y, _color)
'' helps to draw a circle on the screen, used with plotcircle
'' Based on routiness from Paul Sr. on Parallax Forum
  if (_x == 0) 
    plotPixel(_cx, _cy + _y, _color)
    plotPixel(_cx, _cy - _y, _color)
    plotPixel(_cx + _y, _cy, _color)
    plotPixel(_cx - _y, _cy, _color)
  else 
  if (_x == _y) 
    plotPixel(_cx + _x, _cy + _y, _color)
    plotPixel(_cx - _x, _cy + _y, _color)
    plotPixel(_cx + _x, _cy - _y, _color)
    plotPixel(_cx - _x, _cy - _y, _color)
  else 
  if (_x < _y) 
    plotPixel(_cx + _x, _cy + _y, _color)
    plotPixel(_cx - _x, _cy + _y, _color)
    plotPixel(_cx + _x, _cy - _y, _color)
    plotPixel(_cx - _x, _cy - _y, _color)
    plotPixel(_cx + _y, _cy + _x, _color)
    plotPixel(_cx - _y, _cy + _x, _color)
    plotPixel(_cx + _y, _cy - _x, _color)
    plotPixel(_cx - _y, _cy - _x, _color)

DAT
font_8x8      byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000
              byte %00110000,%00110000,%00110000,%00110000,%00110000,%00000000,%00110000,%00000000
              byte %01101100,%01101100,%01101100,%00000000,%00000000,%00000000,%00000000,%00000000
              byte %01101100,%01101100,%11111110,%01101100,%11111110,%01101100,%01101100,%00000000
              byte %00110000,%01111100,%11000000,%01111000,%00001100,%11111000,%00110000,%00000000
              byte %00000000,%11000110,%11001100,%00011000,%00110000,%01100110,%11000110,%00000000
              byte %00111000,%01101100,%00111000,%01110110,%11011100,%11001100,%01110110,%00000000
              byte %01100000,%01100000,%11000000,%00000000,%00000000,%00000000,%00000000,%00000000
              byte %00011000,%00110000,%01100000,%01100000,%01100000,%00110000,%00011000,%00000000
              byte %01100000,%00110000,%00011000,%00011000,%00011000,%00110000,%01100000,%00000000
              byte %00000000,%01100110,%00111100,%11111111,%00111100,%01100110,%00000000,%00000000
              byte %00000000,%00110000,%00110000,%11111100,%00110000,%00110000,%00000000,%00000000
              byte %00000000,%00000000,%00000000,%00000000,%00000000,%00110000,%00110000,%01100000
              byte %00000000,%00000000,%00000000,%11111100,%00000000,%00000000,%00000000,%00000000
              byte %00000000,%00000000,%00000000,%00000000,%00000000,%00110000,%00110000,%00000000
              byte %00000100,%00001100,%00011000,%00110000,%01100000,%11000000,%10000000,%00000000
              byte %01111100,%11000110,%11001110,%11011110,%11110110,%11100110,%01111100,%00000000
              byte %00110000,%01110000,%00110000,%00110000,%00110000,%00110000,%11111100,%00000000
              byte %01111000,%11001100,%00001100,%00111000,%01100000,%11001100,%11111100,%00000000
              byte %01111000,%11001100,%00001100,%00111000,%00001100,%11001100,%01111000,%00000000
              byte %00011100,%00111100,%01101100,%11001100,%11111110,%00001100,%00011110,%00000000
              byte %11111100,%11000000,%11111000,%00001100,%00001100,%11001100,%01111000,%00000000
              byte %00111000,%01100000,%11000000,%11111000,%11001100,%11001100,%01111000,%00000000
              byte %11111100,%11001100,%00001100,%00011000,%00110000,%00110000,%00110000,%00000000
              byte %01111000,%11001100,%11001100,%01111000,%11001100,%11001100,%01111000,%00000000
              byte %01111000,%11001100,%11001100,%01111100,%00001100,%00011000,%01110000,%00000000
              byte %00000000,%00110000,%00110000,%00000000,%00000000,%00110000,%00110000,%00000000
              byte %00000000,%00110000,%00110000,%00000000,%00000000,%00110000,%00110000,%01100000
              byte %00011000,%00110000,%01100000,%11000000,%01100000,%00110000,%00011000,%00000000
              byte %00000000,%00000000,%11111100,%00000000,%00000000,%11111100,%00000000,%00000000
              byte %01100000,%00110000,%00011000,%00001100,%00011000,%00110000,%01100000,%00000000
              byte %01111000,%11001100,%00001100,%00011000,%00110000,%00000000,%00110000,%00000000
              byte %01111100,%11000110,%11011110,%11011110,%11011110,%11000000,%01111000,%00000000
              byte %00110000,%01111000,%11001100,%11001100,%11111100,%11001100,%11001100,%00000000
              byte %11111100,%01100110,%01100110,%01111100,%01100110,%01100110,%11111100,%00000000
              byte %00111100,%01100110,%11000000,%11000000,%11000000,%01100110,%00111100,%00000000
              byte %11111000,%01101100,%01100110,%01100110,%01100110,%01101100,%11111000,%00000000
              byte %01111110,%01100000,%01100000,%01111000,%01100000,%01100000,%01111110,%00000000
              byte %01111110,%01100000,%01100000,%01111000,%01100000,%01100000,%01100000,%00000000
              byte %00111100,%01100110,%11000000,%11000000,%11001110,%01100110,%00111110,%00000000
              byte %11001100,%11001100,%11001100,%11111100,%11001100,%11001100,%11001100,%00000000
              byte %01111000,%00110000,%00110000,%00110000,%00110000,%00110000,%01111000,%00000000
              byte %00011110,%00001100,%00001100,%00001100,%11001100,%11001100,%01111000,%00000000
              byte %11100110,%01100110,%01101100,%01111000,%01101100,%01100110,%11100110,%00000000
              byte %01100000,%01100000,%01100000,%01100000,%01100000,%01100000,%01111110,%00000000
              byte %11000110,%11101110,%11111110,%11111110,%11010110,%11000110,%11000110,%00000000
              byte %11000110,%11100110,%11110110,%11011110,%11001110,%11000110,%11000110,%00000000
              byte %00111000,%01101100,%11000110,%11000110,%11000110,%01101100,%00111000,%00000000
              byte %11111100,%01100110,%01100110,%01111100,%01100000,%01100000,%11110000,%00000000
              byte %01111000,%11001100,%11001100,%11001100,%11011100,%01111000,%00011100,%00000000
              byte %11111100,%01100110,%01100110,%01111100,%01101100,%01100110,%11100110,%00000000
              byte %01111000,%11001100,%11100000,%01111000,%00011100,%11001100,%01111000,%00000000
              byte %11111100,%00110000,%00110000,%00110000,%00110000,%00110000,%00110000,%00000000
              byte %11001100,%11001100,%11001100,%11001100,%11001100,%11001100,%11111100,%00000000
              byte %11001100,%11001100,%11001100,%11001100,%11001100,%01111000,%00110000,%00000000
              byte %11000110,%11000110,%11000110,%11010110,%11111110,%11101110,%11000110,%00000000
              byte %11000110,%11000110,%01101100,%00111000,%00111000,%01101100,%11000110,%00000000
              byte %11001100,%11001100,%11001100,%01111000,%00110000,%00110000,%01111000,%00000000
              byte %11111110,%00000110,%00001100,%00011000,%00110000,%01100000,%11111110,%00000000
              byte %01111000,%01100000,%01100000,%01100000,%01100000,%01100000,%01111000,%00000000
              byte %11000000,%01100000,%00110000,%00011000,%00001100,%00000110,%00000010,%00000000
              byte %01111000,%00011000,%00011000,%00011000,%00011000,%00011000,%01111000,%00000000
              byte %00010000,%00111000,%01101100,%11000110,%00000000,%00000000,%00000000,%00000000
              byte %00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000,%11111111
              byte %00110000,%00110000,%00011000,%00000000,%00000000,%00000000,%00000000,%00000000
              byte %00000000,%00000000,%01111000,%00001100,%01111100,%11001100,%01110110,%00000000
              byte %11100000,%01100000,%01100000,%01111100,%01100110,%01100110,%11011100,%00000000
              byte %00000000,%00000000,%01111000,%11001100,%11000000,%11001100,%01111000,%00000000
              byte %00011100,%00001100,%00001100,%01111100,%11001100,%11001100,%01110110,%00000000
              byte %00000000,%00000000,%01111000,%11001100,%11111100,%11000000,%01111000,%00000000
              byte %00111000,%01101100,%01100000,%11110000,%01100000,%01100000,%11110000,%00000000
              byte %00000000,%00000000,%01110110,%11001100,%11001100,%01111100,%00001100,%11111000
              byte %11100000,%01100000,%01101100,%01110110,%01100110,%01100110,%11100110,%00000000
              byte %00110000,%00000000,%01110000,%00110000,%00110000,%00110000,%01111000,%00000000
              byte %00001100,%00000000,%00001100,%00001100,%00001100,%11001100,%11001100,%01111000
              byte %11100000,%01100000,%01100110,%01101100,%01111000,%01101100,%11100110,%00000000
              byte %01110000,%00110000,%00110000,%00110000,%00110000,%00110000,%01111000,%00000000
              byte %00000000,%00000000,%11001100,%11111110,%11111110,%11010110,%11000110,%00000000
              byte %00000000,%00000000,%11111000,%11001100,%11001100,%11001100,%11001100,%00000000
              byte %00000000,%00000000,%01111000,%11001100,%11001100,%11001100,%01111000,%00000000
              byte %00000000,%00000000,%11011100,%01100110,%01100110,%01111100,%01100000,%11110000
              byte %00000000,%00000000,%01110110,%11001100,%11001100,%01111100,%00001100,%00011110
              byte %00000000,%00000000,%11011100,%01110110,%01100110,%01100000,%11110000,%00000000
              byte %00000000,%00000000,%01111100,%11000000,%01111000,%00001100,%11111000,%00000000
              byte %00010000,%00110000,%01111100,%00110000,%00110000,%00110100,%00011000,%00000000
              byte %00000000,%00000000,%11001100,%11001100,%11001100,%11001100,%01110110,%00000000
              byte %00000000,%00000000,%11001100,%11001100,%11001100,%01111000,%00110000,%00000000
              byte %00000000,%00000000,%11000110,%11010110,%11111110,%11111110,%01101100,%00000000
              byte %00000000,%00000000,%11000110,%01101100,%00111000,%01101100,%11000110,%00000000
              byte %00000000,%00000000,%11001100,%11001100,%11001100,%01111100,%00001100,%11111000
              byte %00000000,%00000000,%11111100,%10011000,%00110000,%01100100,%11111100,%00000000
              byte %00011100,%00110000,%00110000,%11100000,%00110000,%00110000,%00011100,%00000000
              byte %00011000,%00011000,%00011000,%00000000,%00011000,%00011000,%00011000,%00000000
              byte %11100000,%00110000,%00110000,%00011100,%00110000,%00110000,%11100000,%00000000
              byte %01110110,%11011100,%00000000,%00000000,%00000000,%00000000,%00000000,%00000000
              byte %00000000,%01100110,%01100110,%01100110,%01100110,%01100110,%01011100,%10000000
font_5x7      byte $00,$00,$00,$00,$00,$00,$00,$00  ' space
              byte $02,$02,$02,$02,$02,$00,$02,$00  '  "!"
              byte $36,$12,$24,$00,$00,$00,$00,$00  '  """
              byte $00,$14,$3E,$14,$3E,$14,$00,$00  '  "#"
              byte $08,$3C,$0A,$1C,$28,$1E,$08,$00  '  "$"
              byte $22,$22,$10,$08,$04,$22,$22,$00  '  "%"
              byte $04,$0A,$0A,$04,$2A,$12,$2C,$00  '  "&"
              byte $18,$10,$08,$00,$00,$00,$00,$00  '  "'"
              byte $20,$10,$08,$08,$08,$10,$20,$00  '  "("
              byte $02,$04,$08,$08,$08,$04,$02,$00  '  ")"
              byte $00,$08,$2A,$1C,$1C,$2A,$08,$00  '  "*"
              byte $00,$08,$08,$3E,$08,$08,$00,$00  '  "+"
              byte $00,$00,$00,$00,$00,$06,$04,$02  '  ","
              byte $00,$00,$00,$3E,$00,$00,$00,$00  '  "-"
              byte $00,$00,$00,$00,$00,$06,$06,$00  '  "."
              byte $20,$20,$10,$08,$04,$02,$02,$00  '  "/"
              byte $1C,$22,$32,$2A,$26,$22,$1C,$00  '  "0"
              byte $08,$0C,$08,$08,$08,$08,$1C,$00  '  "1"
              byte $1C,$22,$20,$10,$0C,$02,$3E,$00  '  "2"
              byte $1C,$22,$20,$1C,$20,$22,$1C,$00  '  "3"
              byte $10,$18,$14,$12,$3E,$10,$10,$00  '  "4"
              byte $3E,$02,$1E,$20,$20,$22,$1C,$00  '  "5"
              byte $18,$04,$02,$1E,$22,$22,$1C,$00  '  "6"
              byte $3E,$20,$10,$08,$04,$04,$04,$00  '  "7"
              byte $1C,$22,$22,$1C,$22,$22,$1C,$00  '  "8"
              byte $1C,$22,$22,$3C,$20,$10,$0C,$00  '  "9"
              byte $00,$06,$06,$00,$06,$06,$00,$00  '  ":"
              byte $00,$06,$06,$00,$06,$06,$04,$02  '  ";"
              byte $20,$10,$08,$04,$08,$10,$20,$00  '  "<"
              byte $00,$00,$3E,$00,$3E,$00,$00,$00  '  "="
              byte $02,$04,$08,$10,$08,$04,$02,$00  '  ">"
              byte $1C,$22,$20,$10,$08,$00,$08,$00  '  "?"
              byte $1C,$22,$2A,$2A,$1A,$02,$3C,$00  '  "@"
              byte $08,$14,$22,$22,$3E,$22,$22,$00  '  "A"
              byte $1E,$22,$22,$1E,$22,$22,$1E,$00  '  "B"
              byte $18,$24,$02,$02,$02,$24,$18,$00  '  "C"
              byte $0E,$12,$22,$22,$22,$12,$0E,$00  '  "D"
              byte $3E,$02,$02,$1E,$02,$02,$3E,$00  '  "E"
              byte $3E,$02,$02,$1E,$02,$02,$02,$00  '  "F"
              byte $1C,$22,$02,$02,$32,$22,$1C,$00  '  "G"
              byte $22,$22,$22,$3E,$22,$22,$22,$00  '  "H"
              byte $3E,$08,$08,$08,$08,$08,$3E,$00  '  "I"
              byte $20,$20,$20,$20,$20,$22,$1C,$00  '  "J"
              byte $22,$12,$0A,$06,$0A,$12,$22,$00  '  "K"
              byte $02,$02,$02,$02,$02,$02,$3E,$00  '  "L"
              byte $22,$36,$2A,$2A,$22,$22,$22,$00  '  "M"
              byte $22,$22,$26,$2A,$32,$22,$22,$00  '  "N"
              byte $1C,$22,$22,$22,$22,$22,$1C,$00  '  "O"
              byte $1E,$22,$22,$1E,$02,$02,$02,$00  '  "P"
              byte $1C,$22,$22,$22,$2A,$12,$2C,$00  '  "Q"
              byte $1E,$22,$22,$1E,$0A,$12,$22,$00  '  "R"
              byte $1C,$22,$02,$1C,$20,$22,$1C,$00  '  "S"
              byte $3E,$08,$08,$08,$08,$08,$08,$00  '  "T"
              byte $22,$22,$22,$22,$22,$22,$1C,$00  '  "U"
              byte $22,$22,$22,$14,$14,$08,$08,$00  '  "V"
              byte $22,$22,$22,$2A,$2A,$2A,$14,$00  '  "W"
              byte $22,$22,$14,$08,$14,$22,$22,$00  '  "X"
              byte $22,$22,$14,$08,$08,$08,$08,$00  '  "Y"
              byte $3E,$20,$10,$08,$04,$02,$3E,$00  '  "Z"
              byte $3E,$06,$06,$06,$06,$06,$3E,$00  '  "["
              byte $02,$02,$04,$08,$10,$20,$20,$00  '  "\"
              byte $3E,$30,$30,$30,$30,$30,$3E,$00  '  "]"
              byte $00,$00,$08,$14,$22,$00,$00,$00  '  "^"
              byte $00,$00,$00,$00,$00,$00,$00,$7F  '  "_"
              byte $10,$08,$18,$00,$00,$00,$00,$00  '  "`"
              byte $00,$00,$1C,$20,$3C,$22,$3C,$00  '  "a"
              byte $02,$02,$1E,$22,$22,$22,$1E,$00  '  "b"
              byte $00,$00,$3C,$02,$02,$02,$3C,$00  '  "c"
              byte $20,$20,$3C,$22,$22,$22,$3C,$00  '  "d"
              byte $00,$00,$1C,$22,$3E,$02,$3C,$00  '  "e"
              byte $18,$24,$04,$1E,$04,$04,$04,$00  '  "f"
              byte $00,$00,$1C,$22,$22,$3C,$20,$1C  '  "g"
              byte $02,$02,$1E,$22,$22,$22,$22,$00  '  "h"
              byte $08,$00,$0C,$08,$08,$08,$1C,$00  '  "i"
              byte $10,$00,$18,$10,$10,$10,$12,$0C  '  "j"
              byte $02,$02,$22,$12,$0C,$12,$22,$00  '  "k"
              byte $0C,$08,$08,$08,$08,$08,$1C,$00  '  "l"
              byte $00,$00,$36,$2A,$2A,$2A,$22,$00  '  "m"
              byte $00,$00,$1E,$22,$22,$22,$22,$00  '  "n"
              byte $00,$00,$1C,$22,$22,$22,$1C,$00  '  "o"
              byte $00,$00,$1E,$22,$22,$1E,$02,$02  '  "p"
              byte $00,$00,$3C,$22,$22,$3C,$20,$20  '  "q"
              byte $00,$00,$3A,$06,$02,$02,$02,$00  '  "r"
              byte $00,$00,$3C,$02,$1C,$20,$1E,$00  '  "s"
              byte $04,$04,$1E,$04,$04,$24,$18,$00  '  "t"
              byte $00,$00,$22,$22,$22,$32,$2C,$00  '  "u"
              byte $00,$00,$22,$22,$22,$14,$08,$00  '  "v"
              byte $00,$00,$22,$22,$2A,$2A,$36,$00  '  "w"
              byte $00,$00,$22,$14,$08,$14,$22,$00  '  "x"
              byte $00,$00,$22,$22,$22,$3C,$20,$1C  '  "y"
              byte $00,$00,$3E,$10,$08,$04,$3E,$00  '  "z"
              byte $38,$0C,$0C,$06,$0C,$0C,$38,$00  '  "{"
              byte $08,$08,$08,$08,$08,$08,$08,$08  '  "|"
              byte $0E,$18,$18,$30,$18,$18,$0E,$00  '  "}"
              byte $00,$2C,$1A,$00,$00,$00,$00,$00  '  "~"
              byte $7F,$7F,$7F,$7F,$7F,$7F,$7F,$7F   '  --
「MIdi_In.spin」は以下である。
VAR
  long rx_Head, rx_Tail, rx_Buff[64]

PUB start(_midiPin) : status
  midiPin := _midiPin
  rx_top := @rx_Head
  rx_end := @rx_Tail
  rx_fifo := @rx_Buff
  bitticks := clkfreq / 31_250 * 80 / 64 '##### special setting !! #####
  halfticks := bitticks / 2  
  longfill(@rx_Head,66,0)
  status := cognew(@asm_entry, 0)

PUB event : status
  status := -1
  if rx_Tail <> rx_Head
    status := rx_Buff[rx_Tail]
    rx_Tail := (rx_Tail + 1) & $3F
    
DAT
                        org
asm_entry
                        mov     midiMask,#1
                        shl     midiMask,midiPin
getMidiByte
                        waitpeq midiMask,midiMask
                        mov     bitClk,cnt
                        add     bitClk,halfticks       
                        add     bitClk,bitticks
                        mov     testBits,#9                         
:check_loop
                        waitcnt bitClk,bitticks
                        test    midiMask,ina            wc
                        rcr     rx_data,#1
                        djnz    testBits,#:check_loop
                        shr     rx_data,#32-9
                        xor     rx_data,#$FF
                        and     rx_data,#$FF
                        test    rx_data,#%10000000      wz
              if_z      jmp     #:running
                        mov     t1,rx_data
                        and     t1,#%11110000
                        cmp     t1,#%11110000           wz
              if_z      jmp     #getMidiByte
                        mov     rsb,rx_data
                        mov     dcb,#0
                        jmp     #getMidiByte
:running
                        mov     t1,rsb
                        and     t1,#%11100000
                        cmp     t1,#%11000000           wz
              if_z      jmp     #:byte_2
                        tjnz    dcb,#:byte_3
                        add     dcb,#1
                        mov     keyno,rx_data
                        jmp     #getMidiByte
:byte_2 
                        mov     event_data,rsb
                        shl     event_data,#16                                      
                        or      event_data,rx_data
                        jmp     #:write_event
:byte_3 
                        mov     dcb,#0
                        mov     event_data,rsb
                        shl     event_data,#16                                      
                        mov     t1,keyno
                        shl     t1,#8
                        or      event_data,t1
                        or      event_data,rx_data
:write_event
                        rdlong  t1,rx_top
                        mov     rx_pointer,t1
                        shl     rx_pointer,#2
                        add     rx_pointer,rx_fifo
                        wrlong  event_data,rx_pointer
                        add     t1,#1
                        and     t1,#$3F
                        wrlong  t1,rx_top
                        jmp     #getMidiByte

t1                      long    0
midiMask                long    0
testBits                long    0
bitClk                  long    0
bitticks                long    0
halfticks               long    0
midiPin                 long    0
rx_top                  long    0
rx_end                  long    0
rx_fifo                 long    0
rx_data                 long    0
rx_pointer              long    0
event_data              long    0
rsb                     long    0
dcb                     long    0
keyno                   long    0

                        fit
「MIdi_Out.spin」は以下である。
VAR
  long tx_Head, tx_Tail, tx_Buff[64]

PUB start(_midiPin) : status
  midiPin := _midiPin
  tx_top := @tx_Head
  tx_end := @tx_Tail
  tx_fifo := @tx_Buff
  bitticks := clkfreq / 31_250
  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
    
DAT
                        org
asm_entry
                        mov     midiMask,#1
                        shl     midiMask,midiPin
                        or      dira,midiMask
:fifo_check
                        rdlong  t1,tx_end
                        rdlong  t2,tx_top
                        cmp     t1,t2                   wz
              if_z      jmp     #:fifo_check  
                        mov     t2,t1
                        shl     t1,#2
                        add     t1,tx_fifo
                        rdlong  event_data,t1
                        mov     t1,t2
                        add     t1,#1
                        and     t1,#$3F
                        wrlong  t1,tx_end
                        mov     tx_data,event_data
                        shr     tx_data,#16
                        call    #send_event
                        and     tx_data,#%11100000
                        cmp     tx_data,#%11000000      wz
              if_z      jmp     #:byte_2
                        mov     tx_data,event_data
                        shr     tx_data,#8
                        call    #send_event
:byte_2
                        mov     tx_data,event_data
                        call    #send_event
                        jmp     #:fifo_check

send_event
                        xor     tx_data,#$FF
                        and     tx_data,#$FF
                        shl     tx_data,#1
                        or      tx_data,#1
                        mov     testBits,#10                        
                        mov     bitClk,cnt
                        add     bitClk,bitticks
:bit_send
                        shr     tx_data,#1              wc
                        muxc    outa,midiMask
                        waitcnt bitClk,bitticks
                        djnz    testBits,#:bit_send
send_event_ret          ret

t1                      long    0
t2                      long    0
midiMask                long    0
testBits                long    0
bitClk                  long    0
bitticks                long    0
midiPin                 long    0
tx_top                  long    0
tx_end                  long    0
tx_fifo                 long    0
tx_data                 long    0
event_data              long    0

                        fit
「Demo_OLED.spin」は以下である。
CON
  _clkmode = xtal1 + pll8x      'Use the PLL to multiple the external clock by 8
  _xinfreq = 8_000_000          'An external clock of 8MHz. is used (64MHz. operation)
  _OUTPUT       = 1             'Sets pin to output in DIRA register
  _INPUT        = 0             'Sets pin to input in DIRA register  
  _HIGH         = 1             'High=ON=1=3.3v DC
  _ON           = 1
  _LOW          = 0             'Low=OFF=0=0v DC
  _OFF          = 0
  _ENABLE       = 1             'Enable (turn on) function/mode
  _DISABLE      = 0             'Disable (turn off) function/mode
  _xpixels      = 96                                    'Screen width
  _ypixels      = 64                                    'Screen height          
  _pixelperlong = 4                                     'Each tile requires 4 longs
  _xtiles       = _xpixels/_pixelperlong                'Each tile is 4 pixels x 4 pixels
  _ytiles       = _ypixels/_pixelperlong                                        
  _screensize   = (_xtiles * _ytiles * _pixelperlong)   'Size needed for arrays
  _red          = %11100000
  _green        = %00011100
  _blue         = %00000011
  _yellow       = %11111100
  _purple       = %11100011
  _turq         = %00011111
  _white        = %11111111
  _black        = %00000000

VAR
  long MemDisp[_screensize]     'OLED display driver variables 
  long MemWork[_screensize]     'graphics driver variables  

OBJ
  OLED          : "OLED-Driver1"
  Graphics      : "OLED-Driver2"
  midiIn        : "Midi_In"

PUB main | mode, x, d, p, dummy, i, color, eraser_count
  OLED.start(@MemDisp)
  Graphics.setup(_xtiles, _ytiles, @MemWork)
  midiIn.start(21)
  dira[18..20]~
  mode := 1
  p := 0
  repeat
    case mode
      0:
        color := 0
        repeat while mode == 0
          if color > 127
            color := 0
          i := 1
          repeat while mode == 0  
            if i > 45
              i := 1
            Graphics.clear
            Graphics.plotCircle(48,32,i, color)
            color++
            Graphics.plotCircle(48,32,i+5, color)
            color++
            Graphics.plotCircle(48,32,i-5, color)
            Graphics.copy(@MemDisp)
            i++
            dummy := midiIn.event
            if INA[19] == 0
              mode := 1
              Graphics.clear
              Graphics.copy(@MemDisp)
            elseif INA[20] == 0
              mode := 2
              p := 0
              Graphics.clear
              Graphics.copy(@MemDisp)
      1:
        plines[0] := 10
        plines[1] := 3
        plines[2] := 50
        plines[3] := 60
        plines[4] := 10
        plines[5] := 3
        plines[6] := 50
        plines[7] := 60
        vlines[0] := 1
        vlines[1] := 2
        vlines[2] := -3
        vlines[3] := 5
        vlines[4] := 1
        vlines[5] := 2
        vlines[6] := -3
        vlines[7] := 5
        Graphics.clear
        eraser_count := 0
        repeat while mode == 1
          color++
          if (++eraser_count > 7)
            Graphics.plotLine(plines[4], plines[5], plines[6], plines[7], _black)
            plines[4] += vlines[4]
            plines[5] += vlines[5]
            plines[6] += vlines[6]
            plines[7] += vlines[7]
            if (plines[4] > 95)
              vlines[4] := -vlines[4]
              plines[4] += vlines[4]
            if (plines[6] > 95)
              vlines[6] := -vlines[6]
              plines[6] += vlines[6]
            if (plines[5] > 63)
              vlines[5] := -vlines[5]
              plines[5] += vlines[5]
            if (plines[7] > 63)
              vlines[7] := -vlines[7]
              plines[7] += vlines[7]
          Graphics.plotLine(plines[0], plines[1], plines[2], plines[3], color)
          plines[0] += vlines[0]
          plines[1] += vlines[1]
          plines[2] += vlines[2]
          plines[3] += vlines[3]
          if (plines[0] > 95)
            vlines[0] := -vlines[0]
            plines[0] += vlines[0]
          if (plines[2] > 95)
            vlines[2] := -vlines[2]
            plines[2] += vlines[2]
          if (plines[1] > 63)
            vlines[1] := -vlines[1]
            plines[1] += vlines[1]
          if (plines[3] > 63)
            vlines[3] := -vlines[3]
            plines[3] += vlines[3]     
          Graphics.copy(@MemDisp)
          dummy := midiIn.event
          if INA[18] == 0
            mode := 0
            Graphics.clear
            Graphics.copy(@MemDisp)
          elseif INA[20] == 0
            mode := 2
            p := 0
            Graphics.clear
            Graphics.copy(@MemDisp)
      2:
        dummy := midiIn.event
        if dummy <> -1
          d := (dummy & $FF0000) >> 16
          Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _green)
          Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _green)
          Graphics.copy(@MemDisp)
          p := place(p)
          if (d > $DF) or (d < $C0)
            d := (dummy & $00FF00) >> 8
            Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _white)
            Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _white)
            Graphics.copy(@MemDisp)
            p := place(p)
          d := dummy & $0000FF
          Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _white)
          Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _white)
          Graphics.copy(@MemDisp)
          p := place(p)
        if INA[18] == 0
          mode := 0
          Graphics.clear
          Graphics.copy(@MemDisp)
        elseif INA[19] == 0
          mode := 1
          Graphics.clear
          Graphics.copy(@MemDisp)

PUB place(p)
  p := ++p//40
  if p == 0
    Graphics.clear
    Graphics.copy(@MemDisp)
  return(p)         
            
PUB HexConv(d)
  if d<10
    return("0"+d)
  else
    return("A"+d-10)

PRI pauseMSec(Duration)
  waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)

DAT
' data structure is array of points, each point is in x,y form, each line needs two points
plines                  word 10,3 ' line 1, endpoint 1
                        word 50,60' line 1, endpoint 2
                        word 10,3 ' line 2, endpoint 1
                        word 50,60' line 2, endpoint 2

'data structure is an array of velocity vectors in vx, vy form, each pair of numbers used to translate a point
vlines                  word    1,2   ' line 1, endpoint 1 velocity
                        word    -3,5  ' line 1, endpoint 2 velocity
                        word    1,2   ' line 2, endpoint 1 velocity
                        word    -3,5  ' line 2, endpoint 2 velocity
「PellerMin.spin」は以下である。
{{ Inst010.spin }}  

CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

OBJ
  midiOut : "Midi_Out"

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
これで準備が出来たので、まずは「Demo_OLED.spin」をコピーして「XBee_001.spin」として、 ここに外部オブジェクトモジュールとして「Midi_Out.spin」を加えて、 mainではMIDI入力処理をいったんコメントアウトして、 「PellerMin.spin」からMIDI出力の呼び出しを加えつつXBeeから出力して、 同時にOLEDに何か描画する、という最初のプログラムに挑戦した。 XBeeからのデータをMaxでモニタしているので、まずはXBeeで送信側のPropellerの「生存証明」を送ろう、 という作戦である。

・・・それから数時間、午後には途中で金重さんが研究室に来て 飛ぶドラえもん を作ったりしたが、 あれこれPropellerの思い出しに苦闘しつつも、なんとか以下のようにシステムが動いてきた。 過去のPropeller日記で作ったオリジナルのMIDI受信/MIDI送信モジュールは、 外部回路の関係でビットが反転していることを思い出すのに2時間かかったが(^_^;)、 これを解決しつつ、さらにMIDI送信ドライパのバグを発見して解決してしまった。

ここで稼働しているシステムの仕様は以下である。

  • OLED-PROPモジュールのPropellerからは、38400bpsで「MIDIプログラムチェンジ(C0 nn)」を、n=0, n=2, ..., n=127、と繰り返しXBeeから送信している。これは受信側のMaxから生存確認するためのものである
  • OLED-PROPモジュールのPropellerは、XBeeから38400bpsでMIDIメッセージをMIDI規約に従ったものとして(速度だけが違う)解釈して受信する。MIDI規約違反の場合には無視する。この情報をOLEDでHEX(16進)表示する
  • Max側のパッチ(XBee_001.maxpat)は38400bpsでシリアルポートと通信し、Propellerの生存証明の情報(およそ1秒ごとにインクリメント)を表示し、またとりあえずMIDIコントロールチェンジ(B0 nn dd)として0≦nn≦127、0≦dd≦127、のデータを送る
  • OLED-PROPモジュールのPropellerプログラムは「XBee_001.spin」で、あわせてXBee用ドライバとして「XBee_Out.spin」「XBee_In.spin」を開発した

「XBee_001.spin」は以下である。

CON
  _clkmode = xtal1 + pll8x      'Use the PLL to multiple the external clock by 8
  _xinfreq = 8_000_000          'An external clock of 8MHz. is used (64MHz. operation)
  _OUTPUT       = 1             'Sets pin to output in DIRA register
  _INPUT        = 0             'Sets pin to input in DIRA register  
  _HIGH         = 1             'High=ON=1=3.3v DC
  _ON           = 1
  _LOW          = 0             'Low=OFF=0=0v DC
  _OFF          = 0
  _ENABLE       = 1             'Enable (turn on) function/mode
  _DISABLE      = 0             'Disable (turn off) function/mode
  _xpixels      = 96                                    'Screen width
  _ypixels      = 64                                    'Screen height          
  _pixelperlong = 4                                     'Each tile requires 4 longs
  _xtiles       = _xpixels/_pixelperlong                'Each tile is 4 pixels x 4 pixels
  _ytiles       = _ypixels/_pixelperlong                                        
  _screensize   = (_xtiles * _ytiles * _pixelperlong)   'Size needed for arrays
  _red          = %11100000
  _green        = %00011100
  _blue         = %00000011
  _yellow       = %11111100
  _purple       = %11100011
  _turq         = %00011111
  _white        = %11111111
  _black        = %00000000

VAR
  long MemDisp[_screensize]     'OLED display driver variables 
  long MemWork[_screensize]     'graphics driver variables  

OBJ
  OLED          : "OLED-Driver1"
  Graphics      : "OLED-Driver2"
  midiIn        : "XBee_In"
  midiOut       : "XBee_Out"

PUB main | dummy, i[3], p, num
  OLED.start(@MemDisp)
  Graphics.setup(_xtiles, _ytiles, @MemWork)
  midiIn.start(21)
  midiOut.start(20)
  num := 0
  repeat
    i[0] := cnt & $8000000
    if i[0] <> i[1]
      i[1] := i[0]
      num := ++num & 127
      dummy := $C00000 + num
      midiOut.fifoset(dummy)
    dummy := midiIn.event
    if dummy <> -1
      p := Hex_Display(dummy, p)

PUB Hex_Display(dummy, p) | d
  if dummy <> -1
    d := (dummy & $FF0000) >> 16
    Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _green)
    Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _green)
    Graphics.copy(@MemDisp)
    p := place(p)
      if (d > $DF) or (d < $C0)
        d := (dummy & $00FF00) >> 8
        Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _white)
        Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _white)
        Graphics.copy(@MemDisp)
        p := place(p)
      d := dummy & $0000FF
      Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _white)
      Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _white)
      Graphics.copy(@MemDisp)
      p := place(p)
  return(p)

PUB place(p)
  p := ++p//40
  if p == 0
    Graphics.clear
    Graphics.copy(@MemDisp)
  return(p)         
            
PUB HexConv(d)
  if d<10
    return("0"+d)
  else
    return("A"+d-10)

PRI pauseMSec(Duration)
  waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)

DAT
plines                  word 10,3 ' line 1, endpoint 1
                        word 50,60' line 1, endpoint 2
                        word 10,3 ' line 2, endpoint 1
                        word 50,60' line 2, endpoint 2

vlines                  word    1,2   ' line 1, endpoint 1 velocity
                        word    -3,5  ' line 1, endpoint 2 velocity
                        word    1,2   ' line 2, endpoint 1 velocity
                        word    -3,5  ' line 2, endpoint 2 velocity
「XBee_In.spin」は以下である。
VAR
  long rx_Head, rx_Tail, rx_Buff[64]

PUB start(_midiPin) : status
  midiPin := _midiPin
  rx_top := @rx_Head
  rx_end := @rx_Tail
  rx_fifo := @rx_Buff
  bitticks := clkfreq / 38_400 * 80 / 64
  halfticks := bitticks / 2  
  longfill(@rx_Head,66,0)
  status := cognew(@asm_entry, 0)

PUB event : status
  status := -1
  if rx_Tail <> rx_Head
    status := rx_Buff[rx_Tail]
    rx_Tail := (rx_Tail + 1) & $3F
    
DAT
                        org
asm_entry
                        mov     midiMask,#1
                        shl     midiMask,midiPin
getMidiByte
                        waitpne midiMask,midiMask
                        mov     bitClk,cnt
                        add     bitClk,halfticks       
                        add     bitClk,bitticks
                        mov     testBits,#9                         
:check_loop
                        waitcnt bitClk,bitticks
                        test    midiMask,ina            wc
                        rcr     rx_data,#1
                        djnz    testBits,#:check_loop
                        shr     rx_data,#32-9
'                        xor     rx_data,#$FF
                        and     rx_data,#$FF
                        test    rx_data,#%10000000      wz
              if_z      jmp     #:running
                        mov     t1,rx_data
                        and     t1,#%11110000
                        cmp     t1,#%11110000           wz
              if_z      jmp     #getMidiByte
                        mov     rsb,rx_data
                        mov     dcb,#0
                        jmp     #getMidiByte
:running
                        mov     t1,rsb
                        and     t1,#%11100000
                        cmp     t1,#%11000000           wz
              if_z      jmp     #:byte_2
                        tjnz    dcb,#:byte_3
                        add     dcb,#1
                        mov     keyno,rx_data
                        jmp     #getMidiByte
:byte_2 
                        mov     event_data,rsb
                        shl     event_data,#16                                      
                        or      event_data,rx_data
                        jmp     #:write_event
:byte_3 
                        mov     dcb,#0
                        mov     event_data,rsb
                        shl     event_data,#16                                      
                        mov     t1,keyno
                        shl     t1,#8
                        or      event_data,t1
                        or      event_data,rx_data
:write_event
                        rdlong  t1,rx_top
                        mov     rx_pointer,t1
                        shl     rx_pointer,#2
                        add     rx_pointer,rx_fifo
                        wrlong  event_data,rx_pointer
                        add     t1,#1
                        and     t1,#$3F
                        wrlong  t1,rx_top
                        jmp     #getMidiByte

t1                      long    0
midiMask                long    0
testBits                long    0
bitClk                  long    0
bitticks                long    0
halfticks               long    0
midiPin                 long    0
rx_top                  long    0
rx_end                  long    0
rx_fifo                 long    0
rx_data                 long    0
rx_pointer              long    0
event_data              long    0
rsb                     long    0
dcb                     long    0
keyno                   long    0

                        fit
「XBee_Out.spin」は以下である。
VAR
  long tx_Head, tx_Tail, tx_Buff[64]

PUB start(_midiPin) : status
  midiPin := _midiPin
  tx_top := @tx_Head
  tx_end := @tx_Tail
  tx_fifo := @tx_Buff
  bitticks := clkfreq / 38_400 * 80 / 64
  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
    
DAT
                        org
asm_entry
                        mov     midiMask,#1
                        shl     midiMask,midiPin
                        or      dira,midiMask
:fifo_check
                        rdlong  t1,tx_end
                        rdlong  t2,tx_top
                        cmp     t1,t2                   wz
              if_z      jmp     #:fifo_check  
                        mov     t2,t1
                        shl     t1,#2
                        add     t1,tx_fifo
                        rdlong  event_data,t1
                        mov     t1,t2
                        add     t1,#1
                        and     t1,#$3F
                        wrlong  t1,tx_end
                        mov     tx_data,event_data
                        shr     tx_data,#16
                        mov     status_d,tx_data
                        call    #send_event
                        and     status_d,#%11100000
                        cmp     status_d,#%11000000       wz
              if_z      jmp     #:byte_2
                        mov     tx_data,event_data
                        shr     tx_data,#8
                        call    #send_event
:byte_2
                        mov     tx_data,event_data
                        call    #send_event
                        jmp     #:fifo_check

send_event
                        and     tx_data,#$FF
                        or      tx_data,#$100
                        shl     tx_data,#1
                        mov     testBits,#10                        
                        mov     bitClk,cnt
                        add     bitClk,bitticks
:bit_send
                        shr     tx_data,#1              wc
                        muxc    outa,midiMask
                        waitcnt bitClk,bitticks
                        djnz    testBits,#:bit_send
send_event_ret          ret

t1                      long    0
t2                      long    0
midiMask                long    0
testBits                long    0
bitClk                  long    0
bitticks                long    0
midiPin                 long    0
tx_top                  long    0
tx_end                  long    0
tx_fifo                 long    0
tx_data                 long    0
status_d                  long    0
event_data              long    0

                        fit
だいぶ、Propellerの勘が蘇ってきた。 バグが無ければ、XBeeドライバはほぼこのままなので、あとはメインのspinで色々と遊べる筈である。 うまく行けば、これを持参して渡欧して、暇なときにやってみよう・・・というのが構想である。 明日はセントレアに前泊ということで、SUACを出発するのは午後なので、もう少しだけ進めてみることにしよう。

2012年8月30日(木)

渡欧の出発日となった。 いつもは浜松駅からe-wingに乗るところを、先月の Sketching2012 で試したように、今回も浜松西インターにクルマを置いてe-wingである。 これは、帰りの成田→セントレアのフライトに乗り継ぐe-wingが浜松駅行きでなく、 西インターから掛川に行ってしまうためである。 次の浜松駅行きのe-wingを1時間半も待つのがカッタルイ、という事である。 ウイーンまで同行する院生3人のうち伊熊さんとSUACを出発するのは15:00、 それまで朝から一仕事、出来そうである。

世間はお盆が終わって動き出したのか(小学校も今日から2学期スタートの模様)、 いろいろなメイルが届いてきた。 10月には水戸一高同窓会の「歩く会」があるという。例年は時期的に無理なところ、 今年は行けそうなので「参加」をメイルした。 さらにビッグニュースとして、ICMAから

Fwd: ICMC online proceedings - complete.

Thanks to the hard work of Sandra Neal and the University of 
Michigan Library.
We now have all of the ICMC conference proceedings from 
1975 to 2011 online and searchable.

Check it out!
というメイルが届いた。 URLは http://quod.lib.umich.edu/i/icmc/ である。 なんと、ICMCの1975年から去年までの全てのProceedingsが検索対応でWeb公開である。 これは素晴らしい。さすがミシガン大学である。

そしてANAからは、いつもように以下の搭乗前日メイルが届いた。 今日はe-wingでセントレアに行って前泊、明日の朝イチのフライトで成田へ、 そしてフランクフルト経由でリンツに行くのである。

いつもANAをご利用いただきありがとうございます。

いよいよご出発は明日となりました。準備はお済みですか?
お客様の予約内容をご案内します。ご搭乗を心よりお待ち申し上げております。

[1]  8月31日(金)  NH338
  名古屋(中部) - 東京(成田)
  08:20発09:25着   飛行時間:01:05
  座席番号: 8H

[2]  8月31日(金)  NH209
  東京(成田) - フランクフルト
  11:25発16:35着   飛行時間:12:10
  座席番号: 42G

[3]  8月31日(金)  NH6113
  フランクフルト - リンツ
  21:05発22:10着   飛行時間:01:05
  ルフトハンザ ドイツ航空(ルフトハンザ・シティライン)運航のコードシェア便です。

[4]  別の交通手段により移動

[5]  9月11日(火)  JP108
  リュブリャナ - ミュンヘン
  18:30発19:30着   飛行時間:01:00

[6]  9月11日(火)  NH208
  ミュンヘン - 東京(成田)
  21:00発15:25着(翌日)   飛行時間:11:25
  座席番号: 36C

[7]  9月12日(水)  NH337
  東京(成田) - 名古屋(中部)
  16:55発18:05着   飛行時間:01:10
  座席番号: 6C

ANAマイレージクラブ・サービスセンター
座席指定を取っているので安心である。成田発着の長距離便は、 いつものように最後尾の席である。後ろに人がいないので楽である。 ・・・などとあれこれして、さらにスーツケースにパッキングしていたら、もう11時である。 出発は15時前なので、あと3時間ちょっとである。

ここで、いつものお仕事デスクから1106研究室の真ん中のテーブルに移動した。 旅行中の執筆は、いつもの2画面(1280*1024ドット)から、1画面(1280*800ドット)と窮屈になるので、 リハーサルである。 以下のように並べてみたが、Propeller/XBeeモジュールは、iPod touchの箱が丈夫そうだったので、これに入れた。 ただし機内で動かしたらXBeeからWiFiがワラワラと飛ぶので(^_^;)、いいところ、乗り継ぎの空港までである。

また、写真に写っている「超小型HDD」型USBメモリ(^_^;)を経由して、以下のようなXBeeの技術資料もコピーした。 「紙」の資料は旅行先ではアクセス出来ないからである。

まずは昨日の「XBee001.maxmap」と「XBee001.spin」をコピーして「002」にリネームして、Maxパッチを先に走らせてbstでPropellerをコンパイルして転送すると、無事に以下のようにちゃんとXBeeで通信しつつPropellerOLEDモジュールに描画した。

ここから先の改良については、すでに作戦がある。 機内ではXBeeが稼働できないが、Max側のパッチだけは機内プログラミングできる。 そこで、Propellerモジュール側のOLEDドライバとXBee受信ドライバを改訂して、 まずは基本的な情報をMaxから受け取ると描画する、というところを目指したい。 Max側でその情報を送る、というパッチのプログラミングと分離するという事である。

今回はXBeeでのデータ通信の能力を実験してみたい、という目的があるので、 XBeeのモードは「素通し」である。 これをATモードやAPIモードにするかどうかは、後回しである。 OLEDの描画については、XとYの座標と、そのドットのカラーを指定すればよい。 OLEDの色についてはかなり特殊で、「OLED-Driver1.spin」の中に

COLORS256		long $32		'256 colors, 8bit (%RRR_GGG_BB)

と記述されているように、「赤」3ビット、「緑」3ビット、「青」2ビット、という8ビットにパックして、 全体として1画素は256種類のカラー指定を行っている。 ところで今回のXBee通信では、過去にPropellerで実績のある、 オリジナルのMIDI通信のドライバとなっているが、 ここ にあるように、通信速度だけでなく、MIDIではステータスバイトに続くデータバイトが1バイトと2バイトの2種類があったり、ランニングステータスのルールがあったりする。 非同期通信でただデータが行き来するのに、複数バイトのメッセージがちゃんと区別できるためには「MIDI風規約」は有効だが、 OLEDを描画するためには、そのままMIDIのプロトコルに畳み込むと、かなり情報伝達効率が悪くなる。

OLEDは「96*64ドット」なので、ビクセルごとに描画する座標は、MIDIで送れる2バイトの7ビットデータでいいが、 画素のカラー情報は上述のように8ビットなので、MIDIデータバイトには1ビット過剰である。 そこでちょっと考えてみると、「X」「Y」「カラー」の3バイトのうち、「Y」は0-63の6ビットなので、このbit 6を例えばカラーのMSBにすれば、ちょうど7ビットのデータ3バイトで送れることになる。 さらに「X座標は」0-95までであり、7ビットデータとしてはあと32アドレスが無駄になっている。 このあたりを考えた結果、ランニンクステータスのルールを捨て、全てのメッセージをステータスとデータ2バイトで表す、 という、以下のような新しいプロトコルの発想に行き着いた。

  • メッセージは全て3バイトを単位とする
  • 先頭データ(ステータス)のMSBは1で、続く2バイトのMSBは0、つまり7ビットデータ(0-127)である
  • ステータスの最初の96バイト(%10000000-%110111111)では、下位7ビットがOLEDのX座標(0-95)を表し、次のデータバイト(7ピット)の下位6ビットがOLEDのY座標(0-63)を、bit 6がカラーのMSBを表し、最後のデータバイト(7ビット)にこのカラーのMSBを加えた8ビットで、その画素のカラーを表す
  • 残り32バイトのステータス(%11100000-%11111111)は、今後の定義を待つreservedである
かなりトリッキーであるが(^_^;)、データの無駄がなく、将来の拡張性も十分にあるプロトコルである。 詰め込みの尻拭いはMaxとPropellerで行えばいい。

昼食を挟んで、さっそくこのアイデアを実装してみることにした。 「XBee_In.spin」をコピーして「XBee_In2.spin」にリネームして、これを大幅に改変することになるが、 とりあえずMIDIの3バイトメッセージと同様のデータを受け取れば、Propellerメインプログラム側としては既に動いているので、 この部分が突破口となる。 だいぶPropellerアセンブラにも慣れてきたのか、以下のように「XBee_In2.spin」を改訂して、 15分ほどで無事に新しいプロトコルでのXBee受信とデータのHEX表示に成功した。

VAR
  long rx_Head, rx_Tail, rx_Buff[64]

PUB start(_midiPin) : status
  midiPin := _midiPin
  rx_top := @rx_Head
  rx_end := @rx_Tail
  rx_fifo := @rx_Buff
  bitticks := clkfreq / 38_400 * 80 / 64
  halfticks := bitticks / 2  
  longfill(@rx_Head,66,0)
  status := cognew(@asm_entry, 0)

PUB event : status
  status := -1
  if rx_Tail <> rx_Head
    status := rx_Buff[rx_Tail]
    rx_Tail := (rx_Tail + 1) & $3F
    
DAT
                        org
asm_entry
                        mov     midiMask,#1
                        shl     midiMask,midiPin
getMidiByte
                        waitpne midiMask,midiMask
                        mov     bitClk,cnt
                        add     bitClk,halfticks       
                        add     bitClk,bitticks
                        mov     testBits,#9                         
:check_loop
                        waitcnt bitClk,bitticks
                        test    midiMask,ina            wc
                        rcr     rx_data,#1
                        djnz    testBits,#:check_loop
                        shr     rx_data,#32-9
                        and     rx_data,#$FF
                        test    rx_data,#%10000000      wz
              if_z      jmp     #:running
                        mov     rsb,rx_data
                        mov     dcb,#0
                        jmp     #getMidiByte
:running
                        tjnz    dcb,#:byte_3
                        add     dcb,#1
                        mov     keyno,rx_data
                        jmp     #getMidiByte
:byte_3
                        mov     dcb,#0
                        mov     event_data,rsb
                        shl     event_data,#16                                      
                        mov     t1,keyno
                        shl     t1,#8
                        or      event_data,t1
                        or      event_data,rx_data
:write_event
                        rdlong  t1,rx_top
                        mov     rx_pointer,t1
                        shl     rx_pointer,#2
                        add     rx_pointer,rx_fifo
                        wrlong  event_data,rx_pointer
                        add     t1,#1
                        and     t1,#$3F
                        wrlong  t1,rx_top
                        jmp     #getMidiByte

t1                      long    0
midiMask                long    0
testBits                long    0
bitClk                  long    0
bitticks                long    0
halfticks               long    0
midiPin                 long    0
rx_top                  long    0
rx_end                  long    0
rx_fifo                 long    0
rx_data                 long    0
rx_pointer              long    0
event_data              long    0
rsb                     long    0
dcb                     long    0
keyno                   long    0

                        fit
いよいよ次はピクセル表示である。 こちらも15分ほどで、合わせて「ステータスバイトがFFでOLEDのクリア」という定義も追加して作ったのが、以下である。 とりあえずMaxから適当なデータを送ってみただけだが、OLEDのクリアと、何やら座標の指定に対応してプロットしている。
CON
  _clkmode = xtal1 + pll8x      'Use the PLL to multiple the external clock by 8
  _xinfreq = 8_000_000          'An external clock of 8MHz. is used (64MHz. operation)
  _OUTPUT       = 1             'Sets pin to output in DIRA register
  _INPUT        = 0             'Sets pin to input in DIRA register  
  _HIGH         = 1             'High=ON=1=3.3v DC
  _ON           = 1
  _LOW          = 0             'Low=OFF=0=0v DC
  _OFF          = 0
  _ENABLE       = 1             'Enable (turn on) function/mode
  _DISABLE      = 0             'Disable (turn off) function/mode
  _xpixels      = 96                                    'Screen width
  _ypixels      = 64                                    'Screen height          
  _pixelperlong = 4                                     'Each tile requires 4 longs
  _xtiles       = _xpixels/_pixelperlong                'Each tile is 4 pixels x 4 pixels
  _ytiles       = _ypixels/_pixelperlong                                        
  _screensize   = (_xtiles * _ytiles * _pixelperlong)   'Size needed for arrays
  _red          = %11100000
  _green        = %00011100
  _blue         = %00000011
  _yellow       = %11111100
  _purple       = %11100011
  _turq         = %00011111
  _white        = %11111111
  _black        = %00000000

VAR
  long MemDisp[_screensize]     'OLED display driver variables 
  long MemWork[_screensize]     'graphics driver variables  

OBJ
  OLED          : "OLED-Driver1"
  Graphics      : "OLED-Driver2"
  midiIn        : "XBee_In2"
  midiOut       : "XBee_Out"

PUB main | dummy, i[3], p, num, d, x, y, color
  OLED.start(@MemDisp)
  Graphics.setup(_xtiles, _ytiles, @MemWork)
  midiIn.start(21)
  midiOut.start(20)
  num := 0
  repeat
    i[0] := cnt & $8000000
    if i[0] <> i[1]
      i[1] := i[0]
      num := ++num & 127
      dummy := $C00000 + num
      midiOut.fifoset(dummy)
    dummy := midiIn.event
    if dummy <> -1
'      p := Hex_Display(dummy, p)
      d := (dummy & $FF0000) >> 16
      if d == $FF
        Graphics.clear
        Graphics.copy(@MemDisp)
      elseif d < $E0
        x := d & $7F
        d := (dummy & $007F00) >> 8
        y := d & $3F
        color := d & $40
        d := dummy & $00007F
        color := color + d
        Graphics.plotPixel(x, y,color)
        Graphics.copy(@MemDisp)

PUB Hex_Display(dummy, p) | d
  if dummy <> -1
    d := (dummy & $FF0000) >> 16
    Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _green)
    Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _green)
    Graphics.copy(@MemDisp)
    p := place(p)
      if (d > $DF) or (d < $C0)
        d := (dummy & $00FF00) >> 8
        Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _white)
        Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _white)
        Graphics.copy(@MemDisp)
        p := place(p)
      d := dummy & $0000FF
      Graphics.plotChar(HexConv(d/16), 3*(p//5)+1, p/5, 0, _white)
      Graphics.plotChar(HexConv(d//16), 3*(p//5)+2, p/5, 0, _white)
      Graphics.copy(@MemDisp)
      p := place(p)
  return(p)

PUB place(p)
  p := ++p//40
  if p == 0
    Graphics.clear
    Graphics.copy(@MemDisp)
  return(p)         
            
PUB HexConv(d)
  if d<10
    return("0"+d)
  else
    return("A"+d-10)

PRI pauseMSec(Duration)
  waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)

DAT
plines                  word 10,3 ' line 1, endpoint 1
                        word 50,60' line 1, endpoint 2
                        word 10,3 ' line 2, endpoint 1
                        word 50,60' line 2, endpoint 2

vlines                  word    1,2   ' line 1, endpoint 1 velocity
                        word    -3,5  ' line 1, endpoint 2 velocity
                        word    1,2   ' line 2, endpoint 1 velocity
                        word    -3,5  ' line 2, endpoint 2 velocity
なんと、あっさりとピクセル表示が出来てしまった。 ここにMaxから送るものについては、既に構想があり、ちょっとだけjitterで苦労する必要がある。 とりあえず、これをPropellerモジュールのEEPROMに送って確定させた。 出発前のプログラミングとしては、なかなか上出来である。(^_^)

過去のPropeller日記などを見ると、HTMLソースがおよそ100kBを越えると新しいページに移動している。 この日記も既に100kBを越えているので、ここを区切りとして、次は「続・Propeller日記(2)」としていこう。

続・Propeller日記(2) へ