2013年03月26日 01:34
最弱のPICマイコン、PIC10F200で電子オルガンを作ってみた。
【ニコニコ動画】最弱のPICマイコンで電子オルガンを作ってみた
後編できました。
電子オルガンはマイコン工作の定番である。
必要なのはキー入力がいくつかと音の出力が1つ。キー数は今回1オクターブ白鍵のみの8鍵とした。
8個のキーを読もうとすると、普通に考えれば8本の入力が要る。

マトリックスを組めば、3×3=9なので6本で済む。

だが、10F200のI/Oピンは4本しかないので、これでも足りない。
ADコンバータを使って1ピンで多数のキーを読む手法もあるが、

10F200にはADコンバータは無い。コンパレータすら無い。
ではどうするかというのが今回のメインテーマである。
(実際にはキー入力をいくつ取れるか考えたのが先で、使い道として電子オルガンを思いついた)
解説を書いていたのだが、どうも長くなりすぎて頭がまとまらなくなってきたので、前後編に分けることにする。
前編ではメインのキー入力の扱いについて説明し、残りのこまごまとしたものは全て後編に回す。
なお通常のキーマトリックスの扱いは理解している前提で進める。
まず参考になるのがCharlieplexingだ。
これはCharlieさんが考案した、少ないピンで多数のLEDを駆動する結線である。

このように結線することで、N本のピンでN×(N-1)個のLEDを個別に制御することができる。
これをスイッチに応用する。

こうはならない。
ダイオードを入れない限りスイッチは双方向なので、半分しか使えない。

こうだ。
しかしスイッチならではの利点もあって、スイッチはLEDと違って受動的なのでGNDとの導通も見ることができる。
つまり、LED出力の場合はGNDとの間に電圧を掛ければ常に点いてしまうが、スイッチ入力は電圧を掛けたうえでスイッチが押されなければ何も入力されないので、GNDも有効に使えるというわけだ。

結果、こうなる。これで4ピンで10キーの入力が取れる。
これに加えて圧電スピーカーへの出力が必要だ。
スピーカー出力とキー入力の両立については詳しくは後編で書くが、結論だけ言うと扱えるキーが1個少なくなるだけで両立可能である。
なお、GNDを使っているため走査が特殊になる。
普通に走査するなら、1度に1つのピンのみを出力とし、残りをプルアップで入力にしておくのが分かりやすい。
しかし今回の配線では、GNDは常にLを出力していることになるので、このような走査をすることにした。
読んだ値を解釈するコードが場合によっては多少複雑になるかもしれないが、今回はテーブルを引いて変換したため問題にならなかった。
テーブルについてもちょっと面倒なことがあったが、それは後編に回す。
全プログラムは下に載せたが、概要を示すとこんな感じ。
キーを読む
この際、スピーカーの状態がHighからLowになる
読んだ値を1バイトにまとめて音高のテーブルを引く
音高に応じてビジーウェイト
スピーカーをLowHighに
音高に応じてビジーウェイト
最初に戻る
他に無音時の処理があるが後編に回す。
なお、ビジーウェイトの常として、プログラムの命令数を正確に数えておく必要があり、しかもプログラムを変更したら数え直しである。その上、数サイクル数え間違ったところで、微妙に音がずれるだけなので自分の耳では判別不能である。
何が言いたいかというと、下に載せたコードは数え間違いがある可能性が高い。でもめんどいので気にしない。
ところでこのビジーループの部分のコードであるが、実はこれを書いた後にもっといいコードを思いついた。そのうち解説したい。
【ニコニコ動画】最弱のPICマイコンで電子オルガンを作ってみた
後編できました。
電子オルガンはマイコン工作の定番である。
必要なのはキー入力がいくつかと音の出力が1つ。キー数は今回1オクターブ白鍵のみの8鍵とした。
8個のキーを読もうとすると、普通に考えれば8本の入力が要る。

マトリックスを組めば、3×3=9なので6本で済む。

だが、10F200のI/Oピンは4本しかないので、これでも足りない。
ADコンバータを使って1ピンで多数のキーを読む手法もあるが、

10F200にはADコンバータは無い。コンパレータすら無い。
ではどうするかというのが今回のメインテーマである。
(実際にはキー入力をいくつ取れるか考えたのが先で、使い道として電子オルガンを思いついた)
解説を書いていたのだが、どうも長くなりすぎて頭がまとまらなくなってきたので、前後編に分けることにする。
前編ではメインのキー入力の扱いについて説明し、残りのこまごまとしたものは全て後編に回す。
なお通常のキーマトリックスの扱いは理解している前提で進める。
まず参考になるのがCharlieplexingだ。
これはCharlieさんが考案した、少ないピンで多数のLEDを駆動する結線である。

このように結線することで、N本のピンでN×(N-1)個のLEDを個別に制御することができる。
これをスイッチに応用する。

こうはならない。
ダイオードを入れない限りスイッチは双方向なので、半分しか使えない。

こうだ。
しかしスイッチならではの利点もあって、スイッチはLEDと違って受動的なのでGNDとの導通も見ることができる。
つまり、LED出力の場合はGNDとの間に電圧を掛ければ常に点いてしまうが、スイッチ入力は電圧を掛けたうえでスイッチが押されなければ何も入力されないので、GNDも有効に使えるというわけだ。

結果、こうなる。これで4ピンで10キーの入力が取れる。
これに加えて圧電スピーカーへの出力が必要だ。
スピーカー出力とキー入力の両立については詳しくは後編で書くが、結論だけ言うと扱えるキーが1個少なくなるだけで両立可能である。
なお、GNDを使っているため走査が特殊になる。
普通に走査するなら、1度に1つのピンのみを出力とし、残りをプルアップで入力にしておくのが分かりやすい。
時間→ | |||||
ピン0 | 出 | 入 | 入 | 入 | |
ピン1 | 入 | 出 | 入 | 入 | |
ピン2 | 入 | 入 | 出 | 入 | |
ピン3 | 入 | 入 | 入 | 出 | |
ピン4 | 入 | 入 | 入 | 入 |
しかし今回の配線では、GNDは常にLを出力していることになるので、このような走査をすることにした。
時間→ | |||||
GND | 出 | 出 | 出 | 出 | |
ピン0 | 入 | 出 | 出 | 出 | |
ピン1 | 入 | 入 | 出 | 出 | |
ピン2 | 入 | 入 | 入 | 出 | |
ピン3 | 入 | 入 | 入 | 入 |
読んだ値を解釈するコードが場合によっては多少複雑になるかもしれないが、今回はテーブルを引いて変換したため問題にならなかった。
テーブルについてもちょっと面倒なことがあったが、それは後編に回す。
全プログラムは下に載せたが、概要を示すとこんな感じ。
キーを読む
この際、スピーカーの状態がHighからLowになる
読んだ値を1バイトにまとめて音高のテーブルを引く
音高に応じてビジーウェイト
スピーカーを
音高に応じてビジーウェイト
最初に戻る
他に無音時の処理があるが後編に回す。
なお、ビジーウェイトの常として、プログラムの命令数を正確に数えておく必要があり、しかもプログラムを変更したら数え直しである。その上、数サイクル数え間違ったところで、微妙に音がずれるだけなので自分の耳では判別不能である。
何が言いたいかというと、下に載せたコードは数え間違いがある可能性が高い。でもめんどいので気にしない。
ところでこのビジーループの部分のコードであるが、実はこれを書いた後にもっといいコードを思いついた。そのうち解説したい。
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
タグ :マイコン
Post time :
2013年03月26日 01:34
│Comments(0)
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以上必要です。
少なくとも公式サイトにはそう書いてあります。
この1366×768というのは、TVでいうフルじゃない方のHDの解像度で、縦が2進数で切りのいい768、横はそれに合わせて16:9に近い整数値となっています。
しかしこの横幅の1366というのは16進で0x556とさすがに切りが悪いと見えて、横が6px少ない1360(0x550)と扱われる場合があります。うちのTVもそうでした。
Windows8アップグレードアシスタントでチェックしても、「画面の解像度にスナップとの互換性がありません」と言われます。

でも、できました。インストールしただけのデフォルト状態で何の問題も無くスナップできています。

見ると、スナップ側の幅は320px、本体の幅は1024pxと規定通りで、区切り線の幅で吸収したようです。
まあ、1360もそれなりにメジャーな解像度なのに6px足りないだけ(しかも区切り線だけのために)で使えないと言われたら嘲笑の的だろうしなー。
でも1360でも使えるって記述をあんまり見ない気がするのはなんでだろう。
前のPCはCPUがCeleronE1400(当時としても性能低いやつ)だったのに対し、今回はCore i5(それなり)。
メモリが4GB(当時としてはまあまあ)買ったら相性で動かなくて2GBだったのに対し、今回は16GB(それなり)。
あとメインのドライブをSSDにしたり電源を奮発して80+Goldのにしたり、全体的にそれなりのマシンに仕上がりました。
そしてOSはとりあえず安かったWindows8。(この記事を書き始めたのは1ヶ月以上前である…)
で、Metroスナップ。
いわゆる「旧称はMetro」な、正式名称「Modern UI」だか「Windowsストアアプリ」だかなんかよく分からないアレですが、これでスナップ機能を使うためには画面の横幅が1366px以上必要です。
少なくとも公式サイトにはそう書いてあります。
・Windows ストアにアクセスし、アプリをダウンロードして実行する場合は、インターネット接続と 1024 x 768 以上の画面解像度Metroスナップは横幅が320px固定と決まっていますので、非スナップ時の最小値1024×768に加えて1344、メジャーな解像度の1366×768に合わせて決めたと見えます。
・アプリをスナップする場合は、1366 x 768 以上の画面解像度
この1366×768というのは、TVでいうフルじゃない方のHDの解像度で、縦が2進数で切りのいい768、横はそれに合わせて16:9に近い整数値となっています。
しかしこの横幅の1366というのは16進で0x556とさすがに切りが悪いと見えて、横が6px少ない1360(0x550)と扱われる場合があります。うちのTVもそうでした。
Windows8アップグレードアシスタントでチェックしても、「画面の解像度にスナップとの互換性がありません」と言われます。

でも、できました。インストールしただけのデフォルト状態で何の問題も無くスナップできています。

見ると、スナップ側の幅は320px、本体の幅は1024pxと規定通りで、区切り線の幅で吸収したようです。
まあ、1360もそれなりにメジャーな解像度なのに6px足りないだけ(しかも区切り線だけのために)で使えないと言われたら嘲笑の的だろうしなー。
でも1360でも使えるって記述をあんまり見ない気がするのはなんでだろう。
タグ :情報
Post time :
2013年03月03日 19:16
│Comments(0)