2013年08月14日 02:04
最弱のPICマイコン、PIC10F200でまた電子オルガンを作ってみた。
I/Oピン4本の10F200で、前回は追加部品なしで8キーの入力をとる方法を示したが、ダイオードを使えばなんと28入力まで可能となる。
【ニコニコ動画】最弱のPICマイコンでまた電子オルガンを作ってみた(実演編)
【ニコニコ動画】最弱のPICマイコンでまた電子オルガンを作ってみた(解説編) (※縮小表示になってるので埋め込みプレイヤーでの視聴は非推奨)
まず参考になるのがこちらのページ。
ELM - ポートが足りないときは
3ピンの場合で描いた図がこちら。(プルアップ抵抗は省略)

左3つのキーは普通にそれぞれ1,2,3番のピンのみをGNDに落とすが、他のキーは複数のピンを同時にGNDに落とす。ダイオードは逆流防止だ。
マイコン側では3本のピンの値の組み合わせを見ることで、
の2^3=8通りを判別できる。このうち何も押されていないHHHを除いた7通りで7キーまで扱える。
4ピンなら2^4-1=15通りとなる。
なおマイコンへの入力のありうる全組み合わせがそれぞれのキーに対応していることからも分かるように、キーの同時押しは判別不可能である。同時押しはどこか別のキーとして読み取られる。
さらにこれにキーマトリクスの原理を組み合わせる。
ピンを2本追加した場合を考えると、

ピン4とピン5の出力をどちらをLowにするかで、左右の7つづつのキーをピン1-3の入力側で上と同じように判別することができる。
これをさらにCharlieplexingのように入力側と出力側を兼用する配線にしたのが最終的な今回の回路である。

基板上の配線は、秋月の細長い基板に綺麗に収まった。なかなかの自信作である。

押されたキーを判定するには、
・全ピンが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とBの代わりにA|Bという値を使っても、全てのエントリが区別できる。
これにより4bitから3bitにキーのビット数を減らすことができた。
実際には、
A + B<<1 + C<<1 + D<<2 + E<<2 + F<<3 + G<<4 + H<<4 + I<<6 + J<<7
のような計算で10bitを8bitに畳んでいる。
テーブルの値は8bitなので、次のような2段階のテーブル引きで11bitの値を取得している。

キー押下パターンをキーにして引く第1のテーブルの値は、「音高データの下位3bit」「8va 1bit」「第2テーブルを引くキー4bit」という構造をしている。
次にそのキーで第2のテーブルを引き、音高データの上位8bitを得る。
最後に、8vaが1ならば、右シフトして値を半分にする。
なお細かいことだが、ここで「8vaが0」で判定すると余計なbitを消すのに手間がかかる。1で判定すれば8vaが0の時も1の時も下位バイトの下位4bitを消すだけで済む。
第1のテーブルに入れるキーと音高データを兼用することで同じ2段階のテーブルでもっと精度を上げることもできる。ただ11bitあれば人間に判別できない精度にはなるようなので今回はやらなかった。
--15/06/15
前回へのリンク追加
I/Oピン4本の10F200で、前回は追加部品なしで8キーの入力をとる方法を示したが、ダイオードを使えばなんと28入力まで可能となる。
【ニコニコ動画】最弱のPICマイコンでまた電子オルガンを作ってみた(実演編)
【ニコニコ動画】最弱のPICマイコンでまた電子オルガンを作ってみた(解説編) (※縮小表示になってるので埋め込みプレイヤーでの視聴は非推奨)
28キーの配線
解説しよう。まず参考になるのがこちらのページ。
ELM - ポートが足りないときは
3ピンの場合で描いた図がこちら。(プルアップ抵抗は省略)

左3つのキーは普通にそれぞれ1,2,3番のピンのみをGNDに落とすが、他のキーは複数のピンを同時にGNDに落とす。ダイオードは逆流防止だ。
マイコン側では3本のピンの値の組み合わせを見ることで、
H | H | H |
H | H | L |
H | L | H |
L | H | H |
H | L | L |
L | H | L |
L | L | H |
L | L | L |
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・B | 1 | 1 | 0 | 0 | |
C・D\ | 1 | 0 | 1 | 0 | |
1 | 1 | a | b | ||
1 | 0 | c | d | ||
0 | 1 | e | |||
0 | 0 | f |
A|B C・D\ | 1 | 0 | |
1 | 1 | a | b |
1 | 0 | c | d |
0 | 1 | e | |
0 | 0 | f |
これにより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
前回へのリンク追加