今回は、8ビットのPIC16F18313に搭載されているNumerically Controlled Oscillator (NCO)とCCPモジュールを使いオルゴールの音色を模した1オクターブ分の音階を作ります。
1 NCOとPWMを使い音を変化させる
NOCで発生させた音の音量を、CCP (Capture/Compare/PWM)モジュールPWM機能を使用し変化させます。PWMは、出力方形波波形のデューティサイクルを調整し実質的な出力平均電圧を、0-100%可変することができるモジュールですが、今回も、PWM分解能を128カウント、PWM周期は 250KHzに設定しました。
PWN出力のデューティーサイクルを徐々に小さくすれば、実物のオルゴールのように出力音は、徐々に小さくなります。
音を小さくする程度は、30ms で 15/16 になるようプログラムしています。
NCOで発生させた音階の周波数を持つ方形波で、PWM の出力をON/OFFすれば電子オルゴールの音色の完成です。
以下のモジュールを使用します。
- TMR0
- PWMの減衰を決めるため、1 ms 毎のタイミングパルスを出力するよう設定します。このほかタイミングパルスは、PushSWのチャタリング防止や演奏時間を決めるのにも使用されます。
- TMR2
- CCP1と連携しPWMを構成します、さらにNCOに 250KHzパルスを出力するのもこのタイマーの役割です。
- CCP1
- 時間と共にDutyの小さくなるPWM波形をDSMに供給します。PWM分解能は、7ビット (128ステップ) で、PWM周期は250KHzです。
- CLC1
- NCOは直接TMR2出力を取り込むことができないので、CLC1を経由させてTMR2の250KHzパルスをNCOクロック入力に接続します。
- NCO
- 音の周波数を決める発信器です。
- DSM
- PWM出力波形を、NCOからの方形波でON/OFFしPICから出力します。
2 使用回路
前回記事と同じ回路です。部品数が少ないので、ブレッドボードに回路を組みました。方形波出力のため、コンデンサーを介したスピーカーでも十分な音量で鳴ります。
3 プログラム概要
このプログラムでは、20bit長 (最大値 : 2^20)のアキュムレータ(NCO1ACC)に20bitの一定の値(NCO1INC)を、一定間隔 ( 250 KHz )で加算します。
NCOのクロック
Timer2で Fosc/4を 1/32して、250KHzが作られます。この信号をNCOのクロック入力にするのですが、Timer2と NCOを直接接続することができないので、CLC1を経由して接続します。
音の高さを決める定数
希望する出力周波数を得る加算値は、NCOのOverflowが 2回で一周期なので、以下の式で表されます。
Increment Value = Freq. x 2 x 2^20250,000
つまり、
各音に対応する加算値を上の式で計算し配列 inc_tbl[ ] にしてプログラムに書き込みます。計算結果は以下のようでした。
Do, Re, Mi, Fa, So, Ra, Si, Do 0x892,0x99F,0xACD,0xB71,0xCD8,0xE6A,0x102E,0x1125
音の出力処理
波形を発生させるために、加算値レジスターのNCO1INC (NCO1INCU, NCO1INCH, NCO1INCL の3 bytes) に値を保存します。各音の演奏時間 500msのディレーの後に、次の音の加算値に書き換えます。
CCP1では、TMR2と連携して、時間と共にDutyの小さくなるPWM波形をDSMに供給します。PWM分解能は、7ビット (128ステップ) で、PWM周期は258KHzです。
最初は、128ステップ、100%デュティー、の出力ですが、30ms毎に15/16に減少させ、採取的にはゼロ出力になります。
CCP1で作った徐々にその平均電圧の低くなる 250KHzのパルス出力を、NCOで発生させた音階周波数のデュティ 50%の方形波で ON/OFF します。
1オクターブ分の演奏が終了すると、SWの監視をし、SWが押されると再び演奏を始めます。
4 プログラムリスト
下にプログラムのリストを掲載します。
/************************************* * File: ドレミ演奏 * ドレミ連続演奏 SWで繰り返し * NCO で 音階の方形波を作る * ccp1 Dutyを徐々に下げて音を小さくする * PIC16F18313 * Created on 2021-12-29 **************************************/ // 音が減衰する程度 指定値(ms)で1/16減少 #define EnvConst 50 #include <xc.h> #define _XTAL_FREQ 32000000 // delay_ms(x) のための定義 #pragma config FEXTOSC = OFF,RSTOSC = HFINT32 #pragma config CLKOUTEN = OFF,CSWEN = OFF,FCMEN = OFF #pragma config MCLRE = ON,PWRTE = OFF,WDTE = OFF #pragma config LPBOREN = OFF,BOREN = OFF,BORV = LOW #pragma config PPS1WAY = OFF,STVREN = ON,DEBUG = OFF #pragma config WRT = OFF,LVP = ON,CP = OFF,CPD = OFF #define LED LATA4 // LED出力 #define pushSW RA5 // SW入力 #define MonCnt 10 // チャタリング防止時間(10ms) // 音の高さを決める定数 for NOC clock 250KHz const uint16_t dW_tbl[] = { // Do, Re,, Mi, Fa, So, Ra, Si, Do 0x892,0x99F,0xACD,0xB71,0xCD8,0xE6A,0x102E,0x1125 }; uint8_t EnvCnt = EnvConst; // 音量減衰時間 uint8_t amp = 0; // 波形振幅 uint16_t remnTime = 0; // 音符演奏残り時間 uint8_t SWpressed = 0; // SW フラグ int8_t SWclean = 1; // SW pin 状態 int8_t SWmonit = MonCnt; // SW変更禁止時間 uint8_t ptrOnp = 0; // 音符データポインタ void main(void) { LATA = 0; // LED Off TRISA = 0b101011; // A2,A4出力、A5入力に設定 ANSELA = 0; // 全てのpinをデジタルに設定 WPUA = 0b111111; // 入力に弱プルアップ // Timer0 設定 *************************************** T0CON1 = 0b01000101; // Fosc/4 Sync PreS 1/32 T0CON0 = 0b10000000; // T0 ON 8bit Post 1/1 TMR0H = 249; // 32MHz/4/32/250=1KHz (1ms) // PWM 設定 ****************************************** T2CON = 0b00000100; // Timer 2 PS1/1設定 PR2 = 0x1F; // Timer2 周期 250KHz設定 CCP1CON= 0b10001111; // Duty 下位 PWMモード // CLC1 設定 *************************************** // T2_matchをNCO Clockに接続する CLC1POL = 0x02; // LC1G2POL 反転 CLC1SEL0 = 0x1A; // LC1D1S T2_match; CLC1SEL1 = 0x00; // LC1D2S 接続なし CLC1SEL2 = 0x00; // LC1D3S 接続なし CLC1SEL3 = 0x00; // LC1D4S 接続なし CLC1GLS0 = 0x02; // LC1G1D1T enabled CLC1GLS1 = 0x00; // LC1G2 全入力なし CLC1GLS2 = 0x00; // LC1G3 全入力なし CLC1GLS3 = 0x00; // LC1G4 全入力なし CLC1CON = 0x80; // LC1EN 割り込みなし AND-OR; // NCO 設定 ************************************ NCO1CLK = 0x02; // LC1OUTがクロック NCO1CON = 0x80; // NCO ON // DSM 設定 ************************************ MDCON = 0x80; // 情報信号で搬送波『On/Off』 MDSRC = 0x0B; // 情報信号 正論理非同期 NOC1 MDCARH = 0x04; // 搬送波H 正論理非同期 CCP1 MDCARL = 0; // 搬送波L VSS RA2PPS = 31; // RC3にDSM出力 while(1){ // 1ms毎に TimeUp (タイミング) ########## if(TMR0IF){ // 1ms毎に TimeUp TMR0IF = 0; // TimeUp flag クリア // 音量減衰 ------------------------------------- if(EnvCnt-- == 0){ // 30ms毎に EnvCnt = EnvConst; // amp = (int)amp * 15/16; // 音量を -6% する CCPR1H = 0; CCPR1L = amp; if(amp > 100)LED = 1; // 音量大で LED On else LED = 0; // 音量小で LED Off } // 音符演奏 残 ------------------------------------ if(remnTime) remnTime--; // 残り時間を1ms減少 // Clean SW bounce -------------------------------- if(SWmonit == 0){ // SWが安定状態時 if(SWclean != pushSW) // SWの状態変更たら SWmonit = MonCnt; // チャタリング防止設定 }else{ // チャタリング防止中に if(SWclean != pushSW){ // SWの状態が変化か SWmonit--; // 変化継続を5回確認 if(SWmonit == 0){ // 防止期間終了で if(SWclean) // ONなら SWpressed = 1; // 「押した」をtrue SWclean = pushSW; // 新しい状態を反映 } }else{ // SWが元に戻ったら SWmonit = 0; // SWが安定状態を維持 } } // Clean SW bounce } // if(TMR0IF) // ######################################## if(remnTime == 0){ // 一音演奏が終わったら if(ptrOnp < 8){ // 全音終了でないなら NCO1INCU = 0; // 次の音をNCOにセット NCO1INCH = dW_tbl[ptrOnp] >> 8; NCO1INCL = dW_tbl[ptrOnp] & 0xFF; remnTime = 500; // 音継続 500msに設定 amp = 127; // 音量を最大にする ptrOnp++; // 次の音を準備する }else if(SWpressed){ // 演奏終了しSWが押下なら SWpressed = 0; // SWフラグクリア ptrOnp = 0; // 最初の音を準備 } } // f(remnTime == 0) } }
5 次回の予定
次回は、NCO, PWMを利用した電子オルゴールを紹介します。