今回は、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を利用した電子オルゴールを紹介します。