やっとメインループに入ります。
コードに移る前に簡単なフローチャートを掲げます。
3和音と外部EEPROMが将来組み込まれることを想定しての図になります。
4重くらいのループ構造です。
・複数の楽曲を繰り返し演奏
・1曲のデータを繰り返し取り出し
・1音を音符長だけ繰り返し発音
・正弦波を4ミリ秒(今回)基本ループ出力
メインルーチン
285~287行目、BANKSEL文、PICのレジスタは複数のメモリバンクに散っているので、操作対象レジスタが現れる直前に BANKSEL 文を置きます。コンパイル時に、MOVLB命令(バンクセレクト命令)になり、確実にそのレジスタが存在するバンクに切り替えてくれます。
この行では、OSCON(PICの動作周波数やPLLなどオシレータ関連)の設定をしています。
レジスタ設定だけなら同じバンクにあるレジスタをまとめておくと、1回のBANKSELで済むのでプログラムエリアを節約できます。
288~289行目、I2Cで使う SDA と PWMで使う CCP1 がデフォルトで RA2 でかち合ってます。これを避けるために APFCON を設定してCCP1を RA5 ピンに逃しています。
290~292行目、TRISA(トライステートレジスタ・ポートA) の設定。入力で使うのか出力で使うのかデジタルなのかアナログなのか設定が必要。ここでは入出力方向を設定。
293~294行目、ANSELA(アナログセレクト・ポートA)の設定。PICはデフォルトでIOピンがアナログ入出力に設定されていることが多いです。ANSELAを0にすれば、各bitに割り当てられた全IOポートがデジタル入出力設定になります。
295~297行目、PR2(ピリオドレジスタ2)の設定。PWM出力の期間(時間)設定です。今回のPIC設定で0xFFにすると32μ秒になります。
298~302行目、PWM用制御レジスタ CCP1CON の設定。さらにPWM値CCPR1Lを0x80に設定しています。今回のオルゴールでは、サイン波の中心値を0x80においています。
303~305行目、T2CON(タイマー2用制御レジスタ)の設定。PWM出力ではタイマー2が使われます。ここではタイマー2をスタートさせています。
307~311行目、LoopPlay では複数の曲をループ再生できるようにしています。外部EEPROMなどを接続した暁には、この辺に各曲の先頭アドレスなどを書いて次のループ fetch をコールすることになります。今回は単音の内部ROMテーブルからの曲なのでシンプルです。
フェッチルーチン
314~318行目、各チャネルの使用状況を反映する LpBit のクリアやバッファの読み込み先を示す RngRp などを初期化しています。
318行目から外部EEPROMから内部バッファにデータを取り込む想定です。今回は内部バッファは定義がなくて内部ROMテーブル(mother)になっています。
320~321行目、ループカウントを128に設定。3和音プログラムでは、この値を調整することで曲のテンポを変えました。128では基本ループが4.096ミリ秒になります。これを4分音符なら96回、8分音符なら48回などテーブルに音符長を持たせています。
322~340行目、バッファから1つデータを取得して0xFFなら曲終了でリターン、そうでないならチャネル1つ確保、トーンを角速度に変換して格納、さらにデータ取得して音符長に変換して格納します。3和音プログラムでは音符長データの上位4bitに情報を持たせて339行目から同時発音する音があれば322行に戻るようにしていました。
341~342行目、1音(3チャネルまで可)発音データができたので PlayAtk をコールします。発音が終わると fetch_1 へ戻ります。
発音ルーチン
344~349行目、累算された角速度からテーブル参照してチャネル0の出力値を出しています。他チャネルの出力値も求めるのであれば、348行あたりに追加していきます。全チャネルの出力を合算します。
351~363行目、PWM値は10ビット値なので、CCPR1Lには上位8ビットを、下位2ビットはCCP1CON<5:4>に設定します。この辺はPIC12F1822データシートにあるとおり。
365~368行目、タイマー2が満了すると、それまでのPWM周期は終わり、先ほど設定したPWM値(CCPR1L:CCP1CON<5:4>)が新たに設定され次の周期になります。満了したことを示すフラグはクリアしておきます。
370~394行目、各チャネルの角速度の累算をして基本ループ数に達するまでは PlayAtk 先頭に戻ります。
396~397行目、ループカウントを再び128回に設定。この汚い感じは、256回だったときはこの2行がいらなかったので、今回急遽入れたため。
399~412行目、エンベロープカウンタのインクリメントと音符長ループ回数のデクリメント。音符長ループ回数が0になった場合は、チャネルの開放を行い、全チャネルが開放された場合は PlayAtk からリターン。1部チャネルが開放された場合もデータ補充のためリターンします。
1行目から1807行目までつなげれば完成です。
単音オルゴールでも、ここまで説明した内容がPICのデータシートを読んでわかっている必要があるということです。
I2C関連のサブルーチンはこのブログで公開していますし、私独自の音符データ定義も公開しているので、ぜひ改修して3和音外部EEPROMにも挑戦してください。