Techno Toys Laboratory
連載第7回 今月のお題「デューパールーパ 完成編」
研究所からはチャカポコチャカポコとけたたましいリズムが流れ出ている。そう、それは来なかった近未来からやってきた謎のドラムマシン、「デューパールーパ」のうぶ声なのだ!…というわけで前回からの続き。デューパールーパの完成編です。
前回、試作編として基本的な動作は確認できたので、今回は最終的な機能として以下の2点を追加したいと思う。
- テンポやステップ数を変更できるようにする。
- スリープとウェイクアップの追加
いずれも前回製作したものに多少手を加えるだけでOKだ。早速詳しい説明に移ろう。
テンポ、ステップ数設定スイッチの追加
試作編では140BPMに固定だったテンポを変更できるようにし、また、ステップ数も16に固定だったのでこれも変更できるようにしよう。
そのためにロータリスイッチなるものを買ってきた。日本開閉器のDR-SR16という型番だ。ロータリスイッチは写真1のような外観で、ツマミに16進数で0〜Fが書いてある。端子はコモン(共通)がひとつと、1,2,4,8と書かれた端子の計5本が出ている。だいたい察しがつくだろうけど、このスイッチはツマミを合わせた数字の値を示すように4つの端子とコモン端子のON/OFF(導通/非導通)が切り替わる。動作の状況は第1図の通り。

《写真1》ロータリスイッチの外観
ここでは小型のDR-SR16(NKK)を使用した。
端子が5本あり、16進数のダイアルで値を指定できる。

《第1図》ロータリスイッチの動作
DR-SR16の動作状況を示したもの
ロータリスイッチは0h〜fhまでの値を指定できるので、16通りの設定が可能だ。選択可能なステップ数は8,10,12,16の4通りとし、それぞれのステップ数でテンポを80,100,120,140BPMに切り替えられるようにした。まとめると第1表のようになる。

《第1表》ダイアルの設定
ダイアルの位置によってステップ数とテンポはこのように変化することにする
回路の追加
前回作った回路ではPICのI/Oポートが随分余っているので、RB0〜RB3を使ってスイッチの値を読ませることにしよう。RB0にスイッチの"1"と書かれた端子を繋ぎ、RB1に"2"端子、RB2に"4"端子、RB3に"8"端子を繋ぐ。PORTBはプログラムによって内部プルアップができるからそれを使うことにして、コモン端子はグラウンドに繋ぐ。第2図が回路図、第3図が実体配線図だ。

《第2図》デューパールーパの回路図
図中の はともに電池に接続する

《第3図》デューパールーパの実体配線図
追加部分はロータリスイッチだけだ。端子付近の刻印に注意して配線しよう。
このスイッチは開閉によって2進数を表すのであって、電圧のHi/Loを直接出すわけではないから、入力ポートに抵抗を介してあらかじめ電圧をかけておいて、スイッチの開閉によってそれぞれのピンの電圧を落とすようにしている。内部プルアップ機能はこのような用途のためにPICに用意された機能だ。OPTION_REGレジスタのRBPU_というビットを0にするとPORTBの入力に設定されたピンすべてにプルアップがかかる(第4図)。これにより、何も接続しない時には電気的にHiになる。

《第4図》内部プルアップ
OPTION_REGレジスタのRBPUの設定で内部プルアップをすると、右図と同様の結果となる。
外付け抵抗がなくてすむ
ところで、PORTBで読むデータは、2進数の"0"がVcc(電源電圧)、"1"が0Vとなり、2進数のイメージが反転したものになっている。いわゆる負論理というやつだ。だから必要に応じて読んだ値をプログラム的に反転して使う。PICではcomfという命令でレジスタの反転値が得られる。またはffh(2進数で11111111)でxor(排他的論理和)をとればよい。
スイッチの問題点
プログラムでは随時スイッチの状態を読み込み、それに変化があったときに設定を変更する処理を行えばいいのだが、ひとつ厄介な問題がある。それはチャタリングによって不適切な値が飛び込む可能性があることだ。
スイッチは「メカ」なので、ONからOFF、OFFからONに変わる瞬間というのは不安定な状態になる。バタバタとONとOFFを行き来してしまう。これがチャタリングと呼ばれる現象だ。しかも今回使うデジタルスイッチの場合、4つのスイッチがバラバラにこのような動作をするわけだ。
これはスイッチがメカである以上どーしても避けられない問題なので、チャタリング防止回路をつけるか、ソフト的に解決する。今回はソフト的に対処しようと思う。その方が部品が少なくて済むからね。
チャタリングを吸収する
やり方は簡単。要は何回かスイッチを読んで、値が落ち着いたらそれを採用すればいいのだ。第5図を見てほしい。まず一定間隔でスイッチを読みに行く。すでにTRIGというフラグが一定間隔で立つように設定されているので、そのタイミングを利用しよう。そして読んだ値を前回読んだ値と比較する。毎回この処理をするたびに専用のカウンタ(sw_count)をデクリメントする。このカウンタが0になったらスイッチの状態が安定したと見なして、その値を採用する。一方、読みの値が違っていた場合にはこのカウンタをリセットして、採用を延期する。最後に次回の比較のために今読んだスイッチの値を保存しておく。まあ、こんな具合にやればよろしい。

《第5図》チャタリング現象と、その影響の吸収方法
RB0〜3の電圧はこんな風に乱高下する。
そこで定期的にスイッチをチェックして、値がいったん変化した後に安定したら、その値を採用する。
スイッチ入力値の採用が決まったら下位2ビットをテンポ設定として、上位2ビットをステップ数設定としてそれぞれ取り出し、現状と違う値の場合だけ設定変更処理をする。
テンポ変更はcount0_initを書き換える。この変数はTRIGフラグを立てる間隔を決めるものだ。
ステップ数変更は総ステップ数を示すstep_maxにあらたなステップ数を入れ、音符情報の入ったrhythm_buf(2バイト)とstep(これは今演奏している位置を表している)を0クリアする。これをしないとこまごまとした不具合が出るのだけど、どんなことが起きてしまうかは各自想像してみてね。
スリープとウェイク・アップ
入力がなくなってスティックが動作する必要がない状態、つまり完全な沈黙状態ではPICが動く必要がないので、スリープさせてしまおう。スリープ機能については以前も触れたことがあるよね。PICがクロックを止めて、ウェイク・アップの条件が満たされるまで休止し、電池の消費を最小限に出来る機能のことだ。
各ステップごとに音符情報(rhythm_buf)をチェックして、すべて休符だったらスリープするようにした。おっと、スリープの前には起きる条件を指定しておかないと、永遠の眠りについてしまうので、それもちゃんとやっておこう。ここでは演奏再開=プレーヤが何かを叩いて衝撃が検出されたときにウェイク・アップするのが望ましいわけで、例の「RB7〜4のどれかの値が変わったよフラグ」つまりRBIFをウェイク・アップの条件とする。センサはRB7に接続されているので、何か衝撃を検出したらRB7の値が変わり、このRBIFが1になる。今回は割り込み処理に飛ばずに単にウェイク・アップするやり方にした。手順は、まずGIE(全体の割り込み許可フラグ)を0にして全割り込みを禁止する。その後RBIE(RBIFによる割り込み許可フラグ)を1にする。そしてsleep命令を実行してスリープする。全割り込みを禁止しているのにRBIFを立てて意味があるの?と訝しく思われる人もいることでしょう。しかし、この設定をすると、RBIEが1になると割り込み処理にジャンプしないけどウェイク・アップしてsleep命令の次のアドレスから実行を再開しますよ、という意味になる。
それからウェイク・アップ直後には、count0をリセットし、TRIGとHALFフラグを立てて、すぐに後半処理(前号参照のこと)を始めるように指示している。こうしておくと起き上がった瞬間を基点とした演奏のタイミングが取れるからだ。
以上、変更点をフローにまとめると第6図のようになる。またリスト1がそのソースリストだ。書き込み時はHSモード、ウォッチドッグなし、パワーアップタイマありです。

《第6図》フローチャート
ここでは変更のあった部分だけを示す。それ以外は前回の記事を参照してほしい。
;========================================================================
;
; d u p e r l o o p e r
;
; c o d e d b y ク ワ ク ホ ゙ リ ョ ウ タ
;
;========================================================================
;
LIST C=120, p=16F84, r=dec, x=off, n=0
include
;======== マクロの定義 ==================================================
push macro
movwf w_temp
swapf STATUS,W
bcf STATUS,RP0
movwf status_temp
endm
pop macro
swapf status_temp,W
movwf STATUS
swapf w_temp,F
swapf w_temp,W
endm
;======== シンボルの定義 ================================================
;-------- レジスタ名の定義 --------
temp equ 0x0c
w_temp equ 0x0d ; 割り込み時のレジスタ退避用
status_temp equ 0x0e ; //
count0 equ 0x0f ; タイミング用カウンタ
count0_init equ 0x10 ; count0リセット値
tempo_code equ 0x11 ; スイッチによるテンポ設定値
step equ 0x12 ; リズムのステップカウンタ
step_max equ 0x13 ; 総ステップ数
step_code equ 0x14 ; スイッチによるステップ数設定値
event equ 0x15 ; イベントフラグ
stat equ 0x16 ; ステータスフラグ
portb_buf equ 0x17 ; PORTBの内容
prev_portb equ 0x18 ; 前回読んだのPORTBの内容
sw_count equ 0x19 ; sw内容の確定用カウンタ
rhythm_buf equ 0x20 ; リズム用バッファ (2 byte)
;-------- 各変数の初期値 --------
SW_COUNT_INIT equ 2 ;
;-------- eventのビットマップ --------
TRIG equ 0 ;
;-------- statのビットマップ --------
HALF equ 0 ;
;-------- PORTAのビットマップ --------
DRIVE_PIN equ 0 ;
;======== リセットベクタ ===============================================
org 0x0000 ; 起動時は0番地から実行を始める
goto initialize
;======== 割り込みベクタ ===============================================
org 0x0004 ; 割り込みが起きると
; 4番地にジャンプする
;======== 割り込みルーチン ============================================
interrupt:
push ; コンテキストの保存
decfsz count0,F ; count0--==0なら以下を実行
goto int_90 ; それ以外は90へ
movf count0_init,W ; count0の初期化
movwf count0 ;
bsf event,TRIG ; TRIGフラグをセット
movlw 1<>3
; temp <- stepの下位3ビット
btfss INTCON,RBIF ; 入力に変化あり?
goto first_half_10 ; なければ10へ
movf temp,W ; リズムパターンに音符を書き込む
iorwf INDF,F ;
goto first_half_20 ;
first_half_10:
comf temp,W ; リズムパターンに休符を書き込む
andwf INDF,F ;
goto first_half_20 ;
first_half_20:
movf PORTB,F ; RBIFのリセット
bcf INTCON,RBIF ;
incf step,F ; stepの更新
movf step,W ; (0〜step_maxを繰り返す)
subwf step_max,W ;
btfsc STATUS,Z ;
clrf step ;
return ;
;========================================================================
second_half:
call select_memory ; FSR <- step>>3
; temp <- stepの下位3ビット
movf INDF,W ; 音符があったら出力をHiに
andwf temp,W ;
btfss STATUS,Z ;
bsf PORTA,DRIVE_PIN ;
return ;
;========================================================================
; サブルーチン : select_memory
; stepの値に応じてアクセスするメモリのアドレスと、ビット位置を設定する
; FSR <- step >> 3
; temp <- 3 least bit of step
;========================================================================
select_memory:
rrf step,W ; 現在のstepに対応する
movwf temp ; 音符が格納されているアドレスを算出
rrf temp,F ; (FSR <- step >> 3)
rrf temp,W ;
andlw b'00011111' ;
addlw rhythm_buf ;
movwf FSR ;
movf step,W ; 現在のstepに対応する
andlw b'00000111' ; 音符が格納されているビット位置
call bit_table ; (temp <- stepの下位3ビット)
movwf temp ;
return
bit_table: ; 0〜7の数値を
addwf PCL,F ; ビット位置に変換するテーブル
retlw 1<<7 ;
retlw 1<<6 ;
retlw 1<<5 ;
retlw 1<<4 ;
retlw 1<<3 ;
retlw 1<<2 ;
retlw 1<<1 ;
retlw 1<<0 ;
;========================================================================
; サブルーチン : check_sw
; ロータリスイッチの値(負論理)を読み、以下の条件に応じて処理する
; テンポ設定(第1,0ビット)が変化 ----> tempo_code, count0_initを更新
; ステップ数設定(第3,2ビット)が変化 -> step_code, step_maxを更新
; rhythm_bufをクリア
; stepを0にリセット
;========================================================================
check_sw:
movf PORTB,W ; PORTBの内容をportb_bufにコピーし、
movwf portb_buf ; prev_portbと比較する
xorwf prev_portb,W ;
movlw SW_COUNT_INIT ; 内容が違っていたら
btfss STATUS,Z ; sw_countをリセット
movwf sw_count ;
movf sw_count,F ; sw_countが0なら90へジャンプ
btfsc STATUS,Z ; それ以外は以下へ
goto check_sw_90 ;
decfsz sw_count,F ; sw_count--
goto check_sw_90 ; 0になったら以下へ
; それ以外は90へジャンプ
;-------- check tempo -----------
comf portb_buf,W ; temp <- !(portb_bufの第1,0ビット)
andlw b'00000011' ;
movwf temp ;
subwf tempo_code,W ; テンポ設定に変化があれば以下へ
btfsc STATUS,Z ;
goto check_sw_10 ; なければ10へ
movf temp,W ; tempo_code <- temp
movwf tempo_code ;
call tempo_table ; count0_init <- tempo_table[W]
movwf count0_init ;
check_sw_10:
;-------- check step ------------
rrf portb_buf,W ; temp
movwf temp ; <- !(portb_bufの第3,2ビット)>>2
rrf temp,W ;
xorlw b'11111111' ;
andlw b'00000011' ;
movwf temp ;
subwf step_code,W ; ステップ数設定に変化があれば以下へ
btfsc STATUS,Z ;
goto check_sw_90 ; なければ90へ
movf temp,W ; step_code <- temp
movwf step_code ;
call step_table ; step_max <- step_table[W]
movwf step_max ;
clrf rhythm_buf ; rhythm_bufをクリア
clrf rhythm_buf+1 ;
clrf step ; stepを0にリセット
check_sw_90:
movf portb_buf,W ; 次回の比較のために
movwf prev_portb ; prev_portb <- portb_buf
return
;-------- 0〜3に応じてテンポ設定用の数値を返すテーブル --------
tempo_table:
addwf PCL,F
retlw 229 ; 80 BPM
retlw 183 ; 100 BPM
retlw 152 ; 120 BPM
retlw 130 ; 140 BPM
;-------- 0〜3に応じてステップ数設定用の数値を返すテーブル --------
step_table:
addwf PCL,F
retlw 8 ; 8 ステップ
retlw 10 ; 10 ステップ
retlw 12 ; 12 ステップ
retlw 16 ; 14 ステップ
;========================================================================
check_all_off:
;-------- check all off ---------
movf rhythm_buf,W ; rhythm_bufの2バイトが
iorwf rhythm_buf+1,W ; すべて0なら以下へ
btfss STATUS,Z ;
return ; そうでなければリターン
bcf INTCON,GIE ; ウェイクアップ時に割り込みルーチンへ
; 飛ばないように
; 割り込みを禁止しておく
bsf INTCON,RBIE ; PORTBの変化でウェイクアップを
; 起こすように設定
sleep ; スリープ...
bcf INTCON,RBIE ; 割り込み条件を元に戻す
bsf INTCON,GIE ; //
movf count0_init,W ; count0をリセット
movwf count0 ;
bsf event,TRIG ; すぐに後半処理を行うように
bsf stat,HALF ; フラグを立てておく
return
;========================================================================
end
工作の時間です
では、前回からの懸案であったスティック機構部分の工作について説明したい。組み立て手順を第7図に示そう。これを参考にして作ってほしい。細かい寸法については第8図を参照のこと。
コツとしては、ソレノイドは鉄心がコイルから離れると飛躍的に吸引力が弱まるので、可動距離はできるだけ短くした方が良いみたい。3mmくらいにとどめると十分な力が得られる。

《第7図》組み立て工程

《第8図》主な部品の寸法
最低限この寸法を守ればうまく動くはずだ。
機構が出来たら、筐体に収める。ここは例によって各自好きにやってください。写真2が完成したデューパールーパだ!ちなみに、スリープ機能はあるものの、念のため電源スイッチを付けてある。持ち運ぶときに衝撃検出して動作し始めるのも困るし、夜中に突然暴れ出したりしたら怖いからね。

《写真2》完成したデューパールーパ
最後に
実は今回のチューンアップは、本機を複数作った時にその真価をあらわすのだ。例えば、テンポ設定をデジタル式にしたので、複数のデューパールーパで同期を取らなくてもテンポが合うし、ウェイク・アップした瞬間を演奏タイミングの基点にするので、複数での演奏時に拍のアタマを合わせやすい。さらに8ステップと10ステップの混合など、ポリリズムも演奏することが出来るという、我ながらマニアックな仕様と相成った。って僕もまだ1台しか作ってないんだけどね。早く2号機つくろー。といったところで、今回の研究はひとまず終了!