たまりば

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

最弱のPICマイコンで電子オルガン_前編
2013年03月26日 01:34

最弱のPICマイコン、PIC10F200で電子オルガンを作ってみた。

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

後編できました。

電子オルガンはマイコン工作の定番である。
必要なのはキー入力がいくつかと音の出力が1つ。キー数は今回1オクターブ白鍵のみの8鍵とした。
8個のキーを読もうとすると、普通に考えれば8本の入力が要る。
単純な多キー入力
マトリックスを組めば、3×3=9なので6本で済む。
キーマトリックス
だが、10F200のI/Oピンは4本しかないので、これでも足りない。
ADコンバータを使って1ピンで多数のキーを読む手法もあるが、
ADコンバータによる多キー入力
10F200にはADコンバータは無い。コンパレータすら無い。
ではどうするかというのが今回のメインテーマである。
(実際にはキー入力をいくつ取れるか考えたのが先で、使い道として電子オルガンを思いついた)

解説を書いていたのだが、どうも長くなりすぎて頭がまとまらなくなってきたので、前後編に分けることにする。
前編ではメインのキー入力の扱いについて説明し、残りのこまごまとしたものは全て後編に回す。
なお通常のキーマトリックスの扱いは理解している前提で進める。

まず参考になるのがCharlieplexingだ。
これはCharlieさんが考案した、少ないピンで多数のLEDを駆動する結線である。
Charlieplexing LED
このように結線することで、N本のピンでN×(N-1)個のLEDを個別に制御することができる。

これをスイッチに応用する。
Charlieplexing スイッチ(誤)
こうはならない。
ダイオードを入れない限りスイッチは双方向なので、半分しか使えない。
Charlieplexing スイッチ(半分)
こうだ。
しかしスイッチならではの利点もあって、スイッチはLEDと違って受動的なのでGNDとの導通も見ることができる。
つまり、LED出力の場合はGNDとの間に電圧を掛ければ常に点いてしまうが、スイッチ入力は電圧を掛けたうえでスイッチが押されなければ何も入力されないので、GNDも有効に使えるというわけだ。
Charlieplexing スイッチ GND使用
結果、こうなる。これで4ピンで10キーの入力が取れる。

これに加えて圧電スピーカーへの出力が必要だ。
スピーカー出力とキー入力の両立については詳しくは後編で書くが、結論だけ言うと扱えるキーが1個少なくなるだけで両立可能である。

なお、GNDを使っているため走査が特殊になる。

普通に走査するなら、1度に1つのピンのみを出力とし、残りをプルアップで入力にしておくのが分かりやすい。
時間→
ピン0
ピン1
ピン2
ピン3
ピン4

しかし今回の配線では、GNDは常にLを出力していることになるので、このような走査をすることにした。
時間→
GND
ピン0
ピン1
ピン2
ピン3

読んだ値を解釈するコードが場合によっては多少複雑になるかもしれないが、今回はテーブルを引いて変換したため問題にならなかった。
テーブルについてもちょっと面倒なことがあったが、それは後編に回す。

全プログラムは下に載せたが、概要を示すとこんな感じ。

キーを読む
 この際、スピーカーの状態がHighからLowになる
読んだ値を1バイトにまとめて音高のテーブルを引く
音高に応じてビジーウェイト
スピーカーをLowHigh
音高に応じてビジーウェイト
最初に戻る

他に無音時の処理があるが後編に回す。

なお、ビジーウェイトの常として、プログラムの命令数を正確に数えておく必要があり、しかもプログラムを変更したら数え直しである。その上、数サイクル数え間違ったところで、微妙に音がずれるだけなので自分の耳では判別不能である。
何が言いたいかというと、下に載せたコードは数え間違いがある可能性が高い。でもめんどいので気にしない。

ところでこのビジーループの部分のコードであるが、実はこれを書いた後にもっといいコードを思いついた。そのうち解説したい。

    list    p=10F200
    #include p10f200.inc

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

    __CONFIG _MCLRE_OFF & _CP_OFF & _WDT_ON
    ;MCLRE : GPIO3を使いたいのでOFF
    ;コードプロテクト : Don'tCare
    ;WDT : 消費電力低減のためSLEEPするので、起こすために必要

    cblock    0x10
        waitcount ;ウェイト用カウンタ
        keytemp ;押されたキーの判定に使う一時変数
        period ;音の周期の一時変数
    endc

#define keyReadTime d'20' ; 発音サイクルのキー読み側の時間
#define nonKeyReadTime d'9' ; 発音サイクルの非キー読み側の時間
#define waitTimeDiff d'10' ; waitxで指定するカウント数と実際のwait時間の差
#define dif (waitTimeDiff+keyReadTime) ; 音程に指定する値と実際の音程に相当する値の差

;押されたキーの組み合わせと音高(半周期のcycle数、1で無音)の対応

;例
;k03 :   GPIO0-GPIO3間のキー
;kg1g3 : GND-GPIO1間のキーとGND-GPIO3間のキーの同時押し

k0 equ d'1' ;押されていないことを表す。decfszで見るため0でなく1。

k03 equ d'239' - dif ;c
kg1 equ d'213' - dif ;d
kg0 equ d'190' - dif ;e
kg3 equ d'179' - dif ;f
k01 equ d'159' - dif ;g
k23 equ d'142' - dif ;a
k02 equ d'127' - dif ;h
k12 equ d'119' - dif ;C

kg103 equ d'225' - dif ;cd同時押し、c#
kg0g1 equ d'201' - dif ;de
kg0g3 equ d'201' - dif ;ef 使用しないが、k0223を反応させるために必要
kg301 equ d'169' - dif ;fg
k0123 equ d'150' - dif ;ga
k0223 equ d'134' - dif ;ah
k0212 equ d'1' ;hC
k0312 equ d'1' ;Cc

kg1g3 equ d'1';
k0103 equ d'1';
k1223 equ d'1';
kg012 equ d'1';
kg023 equ d'1';
kg102 equ d'1';
kg123 equ d'1';
kg302 equ d'1';
kg312 equ d'1';


init
    org 0x00
    movwf OSCCAL ;補正
    ;補正しないと音が狂う
    ;もっとも、補正しても±1%=17セントと大分狂っている
    
    ;スリープからの復帰の場合は不要な初期化処理を飛ばそうとしたが、
    ;いまいち仕様がよく分からなかったため、やめた
;    btfss STATUS, NOT_PD ; /PD=0 then wake up from sleep
;    goto afterSleep

    movlw b'10000000'
    ;       ^/GPWU=1 : ピン変化によるウェイクアップは使用しないためOFF
    ;                  WDTでなくこちらを使うことも考えたが、全キーに反応させるのは多分無理。
    ;        ^/GPPU=0 : PullUp ON
    ;         ^T0CS : タイマは使用しないためDon'tCare
    ;          ^T0SE : タイマは使用しないためDon'tCare
    ;           ^PSA=0 : WDTを最速で動かすためプリスケーラをTMR0に割り当て
    ;            ^^^PS2:0 : タイマは使用しないためDon'tCare
    option

main
    movlw b'00001011'
    tris GPIO

mainloop
    call readkey;<-18->
    movwf period
    movwf waitcount ;20->
    call waitx
    decfsz period,W ;無音を分岐; if period-1=0 then sleep
    goto soundOn
    sleep
soundOn
    clrwdt
    bsf GPIO,2 ;圧電スピーカーへの出力をH ;22+waitx ->|
    ;<-9+waitx
    movlw nonKeyReadTime ;キー読み分の時間調整
    addwf period,W
    movwf waitcount
    call waitx
    goto mainloop

readkey
    ;8つのキーの組み合わせを読む
    ;不自然にビット反転しているのはテーブルに隙間を都合よく開けるため
    
    swapf GPIO,w ;発音時であれば、p2がHの状態でGNDのみとの導通を見る x?xx0000
    ;待機時であれば、次の入力取得と同じものが取れるため、p2-XのキーとGND-Xのキーを誤認するが、
    ;次回からは発音しているので正しいキーが取れるため問題ない。
    ;keytemp[6]は、発音時は1、無音時は0になる
    bcf GPIO,2 ;圧電スピーカーへの出力をL / 9+waitx->|
    ;|<-
    movwf keytemp
    comf GPIO,w ;p2またはGNDとの導通を見る 1111y0yy
    andlw b'01001011' ;p0,1,3相当のデータを残す。6bit目は1に 0100y0yy
    iorwf keytemp,f ;上記2つのデータをまとめる x1xxy0yy
    bcf GPIO,0 ;p0をL
    movlw 0x0A
    tris GPIO
    btfsc GPIO,1 ;p0p1間の導通を見る keytemp[2] = P1
    bsf keytemp,2
    btfsc GPIO,3 ;p0p3間の導通を見る keytemp[6] = /P3
    bcf keytemp,6
    movlw 0x0B
    tris GPIO
    movf keytemp,w
    movwf PCL ;16 + retlw = 18cycle (call含まず)

waitx ;waitcountの値に応じてウェイト。waitcount >= 4
;call・return含め、(waitcount -4 +14)cycle待つ。
    comf waitcount,W ;下2bit相当の待ち→
    andlw 0x03
    addwf PCL,f
    nop
    nop
    nop ;→|

    rrf waitcount,w ;上位6bitの分だけ4サイクルのループ→
    andlw 0x7E
    movwf waitcount
    decf waitcount,f
    decfsz waitcount,f
    goto $-2 ;→|
    retlw 0


;キー押下パターンテーブル
;
;55 ← テーブル中の隙間の数(めぼしいもの)
    org d'57'
    retlw kg302
;32
    org d'90'
    retlw kg1g3
;18
    org d'109'
    retlw kg0g3
;10?
    org d'120'
    retlw kg301
    org d'124'
    retlw kg3
    org d'126'
    retlw kg312
    org d'131'
    retlw kg0g1
;14
    org d'146'
    retlw kg1
    org d'147'
    retlw kg102
;15
    org d'163'
    retlw kg012
    org d'165'
    retlw kg0
;10
    org d'176'
    retlw k01
    org d'178'
    retlw k12
    org d'179'
    retlw k0212
    org d'180'
    retlw k0
    org d'181'
    retlw k02
;28
    org d'210'
    retlw kg103
    org d'218'
    retlw kg123
;18
    org d'237'
    retlw kg023
    org d'240'
    retlw k0103
    org d'242'
    retlw k0312
    org d'244'
    retlw k03
    org d'248'
    retlw k0123
    org d'250'
    retlw k1223
    org d'252'
    retlw k23
    org d'253'
    retlw k0223

    end
  
タグ :マイコン

  • 1360×768でMetroスナップ
    2013年03月03日 19:16

    PCを新調しました。

    前のPCはCPUがCeleronE1400(当時としても性能低いやつ)だったのに対し、今回はCore i5(それなり)。
    メモリが4GB(当時としてはまあまあ)買ったら相性で動かなくて2GBだったのに対し、今回は16GB(それなり)。
    あとメインのドライブをSSDにしたり電源を奮発して80+Goldのにしたり、全体的にそれなりのマシンに仕上がりました。

    そしてOSはとりあえず安かったWindows8。(この記事を書き始めたのは1ヶ月以上前である…)

    で、Metroスナップ。
    いわゆる「旧称はMetro」な、正式名称「Modern UI」だか「Windowsストアアプリ」だかなんかよく分からないアレですが、これでスナップ機能を使うためには画面の横幅が1366px以上必要です。
    少なくとも公式サイトにはそう書いてあります。
    ・Windows ストアにアクセスし、アプリをダウンロードして実行する場合は、インターネット接続と 1024 x 768 以上の画面解像度
    ・アプリをスナップする場合は、1366 x 768 以上の画面解像度
    Metroスナップは横幅が320px固定と決まっていますので、非スナップ時の最小値1024×768に加えて1344、メジャーな解像度の1366×768に合わせて決めたと見えます。
    この1366×768というのは、TVでいうフルじゃない方のHDの解像度で、縦が2進数で切りのいい768、横はそれに合わせて16:9に近い整数値となっています。
    しかしこの横幅の1366というのは16進で0x556とさすがに切りが悪いと見えて、横が6px少ない1360(0x550)と扱われる場合があります。うちのTVもそうでした。
    Windows8アップグレードアシスタントでチェックしても、「画面の解像度にスナップとの互換性がありません」と言われます。
    1360×768でWindows8アップグレードアシスタント

    でも、できました。インストールしただけのデフォルト状態で何の問題も無くスナップできています。
    1360×768でスナップ
    見ると、スナップ側の幅は320px、本体の幅は1024pxと規定通りで、区切り線の幅で吸収したようです。

    まあ、1360もそれなりにメジャーな解像度なのに6px足りないだけ(しかも区切り線だけのために)で使えないと言われたら嘲笑の的だろうしなー。
    でも1360でも使えるって記述をあんまり見ない気がするのはなんでだろう。  
    タグ :情報