<前の記事 : 次の記事>
5bit分解能をやめて8bitに拡張する
前の記事でもちょっと触れたが、PWMのデューティを5bitで可変することで正弦波を生成するくらいなら最初から5bitDACを使えばいい。逆に言えば8bit分解能ならPWMを使う意義もあるかもしれない、ということか。
PWMをきちんと8bitで使う、つまりPWM5DCLの2bitを余すところなく使う前提ならTMR2のPR2は0x3F(6bitマスク)でいい。こうするとPWM周波数(=サンプリング周波数)は31.25kHzまで低下するが、ギリギリ可聴域外なのでこれを採用する。割り込みはTMR2をそのまま使用すればサンプリング周波数でPWMデューティを更新できる。
NCOではなくソフトウェアDDS
前の記事ではNCOによって割り込み時間を可変にしたが、今回はDDSで固定割り込みをやってみる。アキュムレーターはNCOと同じ20bitとし、入力周波数は31.25kHzがそのまま使える。16MHz入力のNCOではノート0から12くらいの周波数エラーは30セントくらいあったが、31.25kHzのDDSでは2セント程度に抑えられる。音楽的に使いにくい音域ではあるが音程が正確なのは電子楽器としては大事なことだろう。
MCC-Classicで各部を設定
ゼロから書くのは疲れてきたのでMCC-Classicを使って設定を行なった。システム設定ではFOSCを32MHzに、Timer2とPWM5モジュールを設定してPR2が0x3Fで周期が31.25kHzになるようにプリスケーラを4にする。これでPWM5の分解能は8bitに設定される。TMR2で割り込みを発生させるようにしておく。
PWMはRA2から出力するが、RA5に同期信号が出るように出力設定しておく(後述)。ピンの名前は「TRIG」にした。
メインルーチンはかなりスッキリしている。skiptblの中身はノート番号毎にskip値(位相のインクリメント値)を計算しておいたテーブルである。
void main(void) {
SYSTEM_Initialize();
OSCTUNE = 0b100000+28;
TMR2_SetInterruptHandler(t2isr);
skip=skiptbl[69];
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
while (1) {}
}
割り込みルーチンがDDSの本体である。単にアキュムレーターにオフセットを積んで位相を進めるだけだ。
volatile uint24_t phase=0;
volatile uint24_t skip=0;
void t2isr(void) {
phase += skip;
phase &= 0xFFFFF;
uint16_t p=(uint16_t)stbl[(uint8_t)(phase >> 12u)];
PWM5_LoadDutyValue(p);
switch(p) {
case 255:
TRIG_SetHigh();
break;
case 0:
TRIG_SetLow();
break;
default:
break;
}
}
phaseにskipを足して20bitを超えないようマスクして20bitの上位8bitからPWMデューティを決めて(予め計算しておいたstblかから取り出し)PWM5に書き込む。最後のswitch文は同期信号を出すためだ。100%デューティでセット、0%デューティでリセットしている。
出力結果は
440Hz(ノート69)の出力結果が下の図だ。黄色いのが同期信号である。フィルタのカットオフが10kHz程度なので31.25kHzのキャリアが除去できていない。ただし、得られた正弦波はなかなか良さそうな雰囲気で輪郭はカクカクしていない。
結論
PWMを使ったDA変換により8bit分解能で正弦波を発生できた。また低いノート番号(低周波)における周波数エラーを大幅に改善することもできた。PWM出力後段にノート番号に連動したVCFを配置すれば綺麗な正弦波が得られると推定できる。また、8bitの波形テーブルにも任意の波形を入れることができる。
投稿者の人気記事

-
akira.kei
さんが
前の日曜日の1:55
に
編集
をしました。
(メッセージ: 初版)
-
akira.kei
さんが
前の日曜日の1:58
に
編集
をしました。
ログインしてコメントを投稿する