Techno Toys Laboratory

連載第7回 今月のお題「デューパールーパ 完成編」


研究所からはチャカポコチャカポコとけたたましいリズムが流れ出ている。そう、それは来なかった近未来からやってきた謎のドラムマシン、「デューパールーパ」のうぶ声なのだ!…というわけで前回からの続き。デューパールーパの完成編です。


 前回、試作編として基本的な動作は確認できたので、今回は最終的な機能として以下の2点を追加したいと思う。
  1. テンポやステップ数を変更できるようにする。
  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
《リスト1》デューパールーパのソース・リスト
ダウンロード

工作の時間です

 では、前回からの懸案であったスティック機構部分の工作について説明したい。組み立て手順を第7図に示そう。これを参考にして作ってほしい。細かい寸法については第8図を参照のこと。
 コツとしては、ソレノイドは鉄心がコイルから離れると飛躍的に吸引力が弱まるので、可動距離はできるだけ短くした方が良いみたい。3mmくらいにとどめると十分な力が得られる。


《第7図》組み立て工程


《第8図》主な部品の寸法
最低限この寸法を守ればうまく動くはずだ。

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


《写真2》完成したデューパールーパ

最後に

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