たまりば

  パソコン・インターネット パソコン・インターネット  三鷹市 三鷹市

最弱のPICマイコンで電子オルガン(デラックス版)
2013年08月14日 02:04

最弱のPICマイコン、PIC10F200でまた電子オルガンを作ってみた。
I/Oピン4本の10F200で、前回は追加部品なしで8キーの入力をとる方法を示したが、ダイオードを使えばなんと28入力まで可能となる。


【ニコニコ動画】最弱のPICマイコンでまた電子オルガンを作ってみた(実演編)


【ニコニコ動画】最弱のPICマイコンでまた電子オルガンを作ってみた(解説編) (※縮小表示になってるので埋め込みプレイヤーでの視聴は非推奨)

28キーの配線

解説しよう。
まず参考になるのがこちらのページ。
ELM - ポートが足りないときは
3ピンの場合で描いた図がこちら。(プルアップ抵抗は省略)
スイッチ_ダイオード
左3つのキーは普通にそれぞれ1,2,3番のピンのみをGNDに落とすが、他のキーは複数のピンを同時にGNDに落とす。ダイオードは逆流防止だ。
マイコン側では3本のピンの値の組み合わせを見ることで、
HHH
HHL
HLH
LHH
HLL
LHL
LLH
LLL
の2^3=8通りを判別できる。このうち何も押されていないHHHを除いた7通りで7キーまで扱える。
4ピンなら2^4-1=15通りとなる。
なおマイコンへの入力のありうる全組み合わせがそれぞれのキーに対応していることからも分かるように、キーの同時押しは判別不可能である。同時押しはどこか別のキーとして読み取られる。

さらにこれにキーマトリクスの原理を組み合わせる。
ピンを2本追加した場合を考えると、
スイッチ_ダイオード+マトリックス
ピン4とピン5の出力をどちらをLowにするかで、左右の7つづつのキーをピン1-3の入力側で上と同じように判別することができる。
これをさらにCharlieplexingのように入力側と出力側を兼用する配線にしたのが最終的な今回の回路である。
回路図
基板上の配線は、秋月の細長い基板に綺麗に収まった。なかなかの自信作である。
基板配線

テーブルキーのbit数

さて25個のキーを扱うにはもうひとつ問題がある。

押されたキーを判定するには、
・全ピンがHighの時、ピン0,1,3の値
・ピン2がLowの時、ピン0,1,3の値
・ピン0,2がLowの時、ピン1,3の値
・ピン1,2がLowの時、ピン0,3の値
の計10個の値を判断する必要がある。
10bitのテーブルを引ければそのままテーブルのキーにするだけだが、Flashが256ワードしかないためテーブル引きをするには8bitしか扱えない。
そこでどうするかだが、この10bitのテーブルは空きが多いので、2つの値を1つにまとめてしまっても運よく別のエントリと衝突しないことがある。
言葉で説明しづらいので例を挙げる。
このような4bitのテーブルがあったとする。
A・B1100
C・D\1010
11ab
10cd
01e
00f
この場合、AとBの代わりにA|Bという値を使っても、全てのエントリが区別できる。
A|B
C・D\
10
11ab
10cd
01e
00f

これにより4bitから3bitにキーのビット数を減らすことができた。

実際には、
A + B<<1 + C<<1 + D<<2 + E<<2 + F<<3 + G<<4 + H<<4 + I<<6 + J<<7
のような計算で10bitを8bitに畳んでいる。

音高データのbit数

ついでに今回は音高の精度を11bitに上げた。
テーブルの値は8bitなので、次のような2段階のテーブル引きで11bitの値を取得している。

キー押下パターンをキーにして引く第1のテーブルの値は、「音高データの下位3bit」「8va 1bit」「第2テーブルを引くキー4bit」という構造をしている。
次にそのキーで第2のテーブルを引き、音高データの上位8bitを得る。
最後に、8vaが1ならば、右シフトして値を半分にする。
なお細かいことだが、ここで「8vaが0」で判定すると余計なbitを消すのに手間がかかる。1で判定すれば8vaが0の時も1の時も下位バイトの下位4bitを消すだけで済む。

第1のテーブルに入れるキーと音高データを兼用することで同じ2段階のテーブルでもっと精度を上げることもできる。ただ11bitあれば人間に判別できない精度にはなるようなので今回はやらなかった。

ソース

以下のとおり。なお前回の8キー版で詳しく説明した部分はコメントをサボっているので前回のものも見た方がよい。
    list    p=10F200
    #include p10f200.inc
    radix dec ;プログラム中では一応16進も10進も明記するつもりで10進はよく忘れるのでデフォルトは10進

    ;    N/C+-v-+(GP3)
    ;    Vdd|   |Vss
    ;    GP2|   |N/C
    ;CLK GP1+---+GP0 DAT

    __CONFIG _MCLRE_OFF & _CP_OFF & _WDT_ON
;GPIO3を使いたいので_MCLRE_OFF
;消費電力低減のためWDTを使う

    cblock    0x10
        waitcount ;ウェイト用カウンタ
        index ;テーブルを引くためのキー
;(ここにあった不要な変数を消したので、このソースのアセンブル結果は動画の最後のものと一致しない)
        tonel ;音の周期上位
        toneh ;音の周期下位
        acc ;周期の微調整のためのアキュムレータ
    endc

#define origin 169 ;テーブルの一番大きな隙間が0番地~に来る位置

;音階に対応するサイクル数と、マクロで扱うための順序数(第2テーブルのキー)
;配列で定義したいところだがそういう機能が無いので桁で分けて強引に2つをまとめた
do  equ .1911 + 0x0000
di  equ .1804 + 0x1000
re  equ .1703 + 0x2000
ri  equ .1607 + 0x3000
mi  equ .1517 + 0x4000
fa  equ .1432 + 0x5000
fi  equ .1351 + 0x6000
so  equ .1276 + 0x7000
si  equ .1204 + 0x8000
la  equ .1136 + 0x9000
li  equ .1073 + 0xA000
ti  equ .1012 + 0xB000
doh equ  .956 + 0xC000
;低いオクターブでは1周期がこの値(HighとLowでこの半分づつ)
;高いオクターブではその半分
;例: 1000000/1136=880.28169 ≒ 880Hz

;上で定義した音階名と1オクターブ上げる指示(以下8va)から第1テーブルの値を生成するマクロ
tone macro name, high
    retlw low(name<<5) + (high<<4) + (name>>12)
    ;     音高の下3bit   8va         第2テーブルのキー
    endm

#define ADJUST 57 ;音高データを補正するための、地のプログラムで消費するサイクル数

init
    org 0x00
    movwf OSCCAL ;補正

    ;スリープからの復帰の場合は不要な初期化処理を飛ばそうとしたが、
    ;いまいち仕様がよく分からなかったため、やめた
;    btfss STATUS, NOT_PD ; /PD=0 then wake up from sleep
;    goto afterSleep

    movlw b'10000000'
    ;       ^/GPWU :ピン変化によるウェイクアップはWDTによるウェイクアップと競合しそうなのでOFF
    ;        ^/GPPU=0: PullUp ON
    ;         ^T0CS=0: Don'tCare
    ;          ^T0SE : Don'tCare
    ;           ^PSA=0: WDTを最速で動かすためプリスケーラをTMR0に割り当て
    ;            ^^^PS2: タイマは使用しないためDon'tCare
    option

main
    movlw b'00001011' ;とりあえずピン2以外Z
    tris GPIO

mainloop
    call readkey ;スイッチに応じたビットパターンを生成
    call gettone ;スイッチに応じた音階データを取得(第1のテーブル引き)
    movwf tonel
    incfsz tonel,W ;無音を分岐; if FF then sleep
    goto soundOn
    sleep ;無音ならスリープ、WDTで18ms後にリセット
soundOn
    andlw 0x0F
    call gettoneh ;C=0 ;第2のテーブル引き
    movwf toneh
    ;8va処理
    btfsc tonel,4
    rrf toneh,F
    btfsc tonel,4
    rrf tonel,F

    ;音高データのサイクル数から地のプログラムで消費するサイクル数を引く
    movlw (ADJUST&3)<<6
    subwf tonel,F
    movlw ADJUST>>2
    btfss STATUS,C
    decf toneh,F
    subwf toneh,F

    call wait ;指定サイクル数のウェイト
    clrwdt
    bsf GPIO,2 ;圧電スピーカーへの出力をH

    call adjustHL

    call wait
    goto mainloop


;H側とL側のサイクル数の差を補正
;無駄の多いコードだが、Flashには余裕があるのでよしとする
adjustHL:
    goto $+1
    goto $+1
    goto $+1
    goto $+1
    goto $+1
    goto $+1
    goto $+1
    retlw 0


;スイッチを読み、10bitを8bitに畳んでテーブルを読むためのキーとする
;テーブルを引くキーと鍵盤のキーが紛らわしいので後者をスイッチって呼ぼうとしたけど関数名はkeyのままだった
readkey
;全てHで読む
;xxx3x10x ←この位置にピンを読んだ結果を入れる
    rlf GPIO,W
    andlw b'00010110'
    movwf index

    movlw b'00001010'
    tris GPIO
    bcf GPIO,0

    nop ;プルアップでHighになるまでの猶予

;0がLで読む
;xxxxx31x
    rrf GPIO,W
    andlw b'00000101'
    addwf index,F
    andlw 1
    addwf index,F

    movlw b'00001001'
    tris GPIO
    bcf GPIO,1

    nop

;1がLで読む
;xxxx3xx0
    movf GPIO,W
    bcf GPIO,2 ;下で読むためにGP2をL (圧電スピーカーの容量で時間がかかりそうなので早めに)
    andlw b'00001001'
    addwf index,F

    movlw b'00001011'
    tris GPIO

    nop

;2がLで読む
;3x10xxxx
    swapf GPIO,W
    addwf index,F

    retlw 0


;テーブルの隙間にコードを書いているので場所が飛ぶ
    org low(origin+.214)
;toneh:tonelの値に応じてウェイト
wait:
    ;toneh*4
    movf toneh,W
    movwf waitcount
    nop
    decfsz waitcount,f
    goto $-2

    ;tonel[7:6]
    btfsc tonel,6
    goto $+1
    btfsc tonel,7
    goto $+1
    btfsc tonel,7
    goto $+1

    ;tonel[5:4]/4 (ディザリング)
    movf tonel,W
    andlw 0x30
    addwf acc,F
    btfsc acc,6
    goto $+1
    bcf acc,6

    retlw 0


gettoneh ;第2のテーブルから音高データその2を取得
    addwf PCL,F
    nop
    retlw low(do>>3) ;音高データの上位8bit
    retlw low(di>>3)
    retlw low(re>>3)
    retlw low(ri>>3)
    retlw low(mi>>3)
    retlw low(fa>>3)
    retlw low(fi>>3)
    retlw low(so>>3)
    retlw low(si>>3)
    retlw low(la>>3)
    retlw low(li>>3)
    retlw low(ti>>3)
    retlw low(doh>>3)


    org origin-2
gettone ;第1のテーブルから音高データを取得
    movf index,W
    addwf PCL,F
;第1のテーブルはFlash全域にわたる
    org low(origin+.179)
    tone do, 0
    org low(origin+.204)
    tone di, 0
    org low(origin+.212)
    tone re, 0
    org low(origin+.85)
    tone ri, 0
    org low(origin+.165)
    tone mi, 0
    org low(origin+.38)
    tone fa, 0
    org low(origin+.81)
    tone fi, 0
    org low(origin+.209)
    tone so, 0
    org low(origin+.197)
    tone si, 0
    org low(origin+.181)
    tone la, 0
    org low(origin+.53)
    tone li, 0
    org low(origin+.69)
    tone ti, 0
    org low(origin+.196)
    tone do, 1
    org low(origin+.207)
    tone di, 1
    org low(origin+.211)
    tone re, 1
    org low(origin+.57)
    tone ri, 1
    org low(origin+.156)
    tone mi, 1
    org low(origin+.77)
    tone fa, 1
    org low(origin+.201)
    tone fi, 1
    org low(origin+.205)
    tone so, 1
    org low(origin+.194)
    tone si, 1
    org low(origin+.175)
    tone la, 1
    org low(origin+.19)
    tone li, 1
    org low(origin+.0)
    tone ti, 1
    org low(origin+.37)
    tone doh, 1

    org low(origin+.213)
    retlw 0xFF ;無音

    end


--15/06/15
前回へのリンク追加

  • 同じカテゴリー(電子工作)の記事画像
    I2C液晶が動かない理由が分かった
    ゲームボーイの吸い出し機を作った (後編)
    ARMマイコンはじめました。
    SDカードから1セクタ読み取るまでの手順解説
    EEPROM書換え上限テスト
    最弱のPICマイコンでカレンダー
    同じカテゴリー(電子工作)の記事
     I2C液晶が動かない理由が分かった (2017-01-31 01:54)
     ゲームボーイの吸い出し機を作った (後編) (2017-01-16 22:44)
     ARMマイコンはじめました。 (2016-05-28 14:43)
     SDカードから1セクタ読み取るまでの手順解説 (2015-10-05 01:09)
     EEPROM書換え上限テスト (2014-11-17 08:06)
     最弱のPICマイコンでカレンダー_ちょっと短縮 (2014-11-02 04:03)
    URL欄を実験的に消してる間に廃止されてしまいました。まあいいか。
     
    <ご注意>
    書き込まれた内容は公開され、ブログの持ち主だけが削除できます。
    削除
    最弱のPICマイコンで電子オルガン(デラックス版)
      コメント(0)