2014年6月28日土曜日

PICで正弦波を

またまたPICネタであります。

キーヤーは最後の詰めに入っていますが、その前にプログラムしたDDS-VFO トランシーバー制御システムにもサイドトーンをPIC内部で発生させようと考えています。ただ単にブザーオンオフではつまらないので、正弦波に近いトーンをPICから直接発生させるといいなと思いました。

波形発生方法としては、1つのポート系列をすべてつかってラダー抵抗で8bitD/A変換する方法とPIC内蔵のPWMモジュールを使う方法がありますが、ポート数の関係からPWMモジュールでやってみることにしました。専用のD/A変換ICを使うのもよいですが、PIC単独でやらせたいので今回は却下です。

 PWMモジュールを使う場合は搬送波の周波数を高くする必要があるためキーヤーのクロック周波数4MHzでは600Hzのまともな正弦波はつくれません(AFフィルター通せば正弦波になりますがそれではあまり面白くない(笑))。なのでキーヤープログラムのほうは搬送波そのものを600Hz周辺で発生させています。もちろん方形波ですがサウンダを鳴らす分には方形波でも問題ないのでこのままにしてます。今回は対象をDDS-VFOトランシーバー制御システムに使っている18F26K22 32MHzに想定して、同じPICを使って実験してみました。

 プログラムにあたってまず、搬送波周波数をどうするかです。PWMはある一定のパルス幅をもつ搬送波のデューティーサイクルをプログラムのデータに従って変化させることで出力レベルを変化させるという方法です。搬送波のタイミングで波形が分割される(サンプリングされる)というイメージで、ある正弦波の一周期に対する分割は細かいほど元の波形がより正確に再現できるというわけです。

どこかの情報ですと(出所失念^^;)出力したい最大周波数の20倍以上が必要とのこと。サイドトーンとして使われる周波数は400~800Hz程度なので搬送波周波数は最低16kHzということになります。それよりも高ければ高いほどよさそうですが、その分波形データが必要になるのとPWMのデューティーサイクルの階調が制限されます(PICのクロック周波数は一定なので)。っていうもろもろの要素を鑑みた結果大体32kHzあたりが妥当のようです。

そうなると、正弦波1周期分で800Hzなら40、400Hzなら80分割のデータを用意できればOKです。600Hzでは整数で分割できないので、近いところで約615kHzの52分割データ用意としました。

PWMの出力はDDS-VFOシステムでの空きピンを割り当てます。今回はPWM出力可能なRB4ピン(CCP1/P1D)とし、PWMタイムベースをTimer4に設定。この条件で各レジスタ設定値をメイン関数内の初期化プロセスに記述していきます。その後のループ処理内には、Timer4のタイミングフラグが立つごとにあらかじめ用意した正弦波のデータを順々に読み込ませてデューティーサイクル設定レジスタ(CCPR1L)にコピーするという処理を書き込みます。

正弦波データをプログラムで計算させるのは8ビットPICではきついので、あらかじめ表計算ソフトで算出したデータをromに入れておくことにします。計算方法は、1周期360度を分割数で割った角度ごとのsinまたはcos値を求めてそれぞれに1を加え(負の数をなくすため)階調(今回は250階調にしました)の半分を乗算し、小数点以下を四捨五入して整数化した値を1元配列におさめます。階調は10ビットまで指定できますが、上位2ビット分は別のレジスタになるのでデータを2元配列にするなど少々手間がかかります。
 
表計算ソフトでデータ作成 400Hzと800Hzは共通です なぜだかわかりますよね
 あとは、お決まりのconfigビットの設定文、クロック関連、ポート関連のレジスタを記述していきます。

そうやって出来上がったプログラムをPickit3で注入しました。

お城で観察。

RB4ポート出力波形 パルス幅が徐々に変化してますね

ポートから適当なRCフィルタを通した波形 ギザギザ^^;

スピーカーから出てくる音は正弦波に近い結構澄んだ音になってます。ギザギザは搬送波の成分なのでスピーカーでは出力されませんからね。

比較的簡単に正弦波もどきは出せるようです。18F26K22ではプログラムメモリ消費も微々たるものなので、DDS-VFOトランシーバー制御システムのサイドトーン発生プログラムに採用しようと思います。

以下サンプルプログラムです。出力周波数600Hz(正確には615Hz)

//
// 18F26K22 PWM Tone Generator
// JL1VNQ/HARU
//
// PWM base frequency 32kHz, Tone frequency 800Hz, 40 divisions = 9 degree
// PWM base frequency 32kHz, Tone frequency 600(615)Hz, 52 divisions = 6.92307 degree
// PWM base frequency 32kHz, Tone frequency 400Hz, 80 divisions = 4.5 degree
// resolution nearly 10bits (1000steps < 1024)
//

#include    <xc.h>

#pragma config FOSC = INTIO67, PLLCFG = ON, PRICLKEN = ON, FCMEN = OFF, IESO = OFF
#pragma config PWRTEN = OFF, BOREN = SBORDIS, BORV = 190
#pragma config WDTEN = OFF
#pragma config MCLRE = INTMCLR, HFOFST = OFF, PBADEN = ON
#pragma config XINST = OFF, STVREN = ON, LVP = OFF, DEBUG = OFF
#pragma config CP1 = OFF, CP0 = OFF, CP2 = OFF, CP3 = OFF
#pragma config CPB = OFF, CPD = OFF
#pragma config WRT1 = OFF, WRT0 = OFF, WRT2 = OFF, WRT3 = OFF
#pragma config WRTB = OFF, WRTC = OFF, WRTD = OFF
#pragma config EBTR1 = OFF, EBTR0 = OFF, EBTR2 = OFF, EBTR3 = OFF
#pragma config EBTRB = OFF

#define EEPROM_SIZE     1024
#define _XTAL_FREQ      32000000

#define PWM_OUT PORTB,4        // CCS1/P1D

const unsigned char SIN400[80]={
    125,135,145,154,164,173,182,190,198,206,
    213,220,226,232,236,240,244,247,248,250,
    250,250,248,247,244,240,236,232,226,220,
    213,206,198,190,182,173,164,154,145,135,
    125,115,105,96,86,77,68,60,52,44,
    37,30,24,18,14,10,6,3,2,0,
    0,0,2,3,6,10,14,18,24,30,
    37,44,52,60,68,77,86,96,105,115
};

const unsigned char SIN600[52]={       // divisions number is "multiple of 4" recomended
    125,140,155,169,183,196,208,219,228,236,
    242,246,249,250,249,246,242,236,228,219,
    208,196,183,169,155,140,125,110,95,81,
    67,54,42,31,22,14,8,4,1,0,
    1,4,8,14,22,31,42,54,67,81,
    95,110
};

void main(void){
   
    OSCCON = 0x64;          // 8MHz internal clock, primary clock (IMPORTANT!! NOT SELECT "internal OSC" for PLL)
    OSCCON2 = 0x04;            // OSC drive circuit on
    OSCTUNE = 0x40;            // 4xPLL enable, calibration = 0

    ANSELA = 0x00;
    ANSELB = 0x00;
    ANSELC = 0x00;
   
    TRISA = 0x00;
    TRISB = 0x00;
    TRISC = 0x00;
    TRISE = 0x08;
   
    CCP1CON = 0x0C;            // PWM mode
    CCPTMRS0 = 0x01;           // TMR4 use for PWM time base
    PSTR1CON = 0x08;           // only P1D

    T4CON = 0x04;              // prescaler 0, postscaler 0, TMR4ON
    PR4 = 249;                 // 32kHz, resolution 1000steps
    CCPR1L = 125;              // Duty cycle initialize
   
    while(1){       
        static unsigned char i = 0;
        if(PIR5bits.TMR4IF){
            PIR5bits.TMR4IF = 0;
            i++;
            CCPR1L = SIN600[i];
            if(i > 51) i = 0;
        }
    }
}


追記:
周波数を変えるだけなら搬送波の周波数(PR4, TMR4のプリスケーラ)を変化させればよいので、正弦波データはひとつで済みますね。

4 件のコメント:

  1. こんにちは。大変面白い実験ですね。

    自分の蔵書の中に NHK出版の「電子回路ノウハウ 発振回路の完全マスター」というものがあります。1988年が初版の古い技術書ですが、この中で「ROMデータで正弦波を出力」というものが紹介されています。比較的波形も綺麗で実用的ではあるものの、ちょっと工作が複雑になりそう・・・と思ったら、確かにPICなら外付けの回路はLPFくらいで済みますね。

    実は、自分も何れ、PICエレキーの第二弾として少々多機能なものをこしらえようと思っていますが、モニタ音送出このアイディアを追試させて頂こうかと思いました。

    返信削除
    返信
    1. どよよんさん、おはようございます。

      いわゆるごく簡単なD/A変換器として動作するので、ディジタル信号処理のLチカみたいな感じです。データ次第で三角波とかのこぎり波も、またサンプリングした音声ファイルを外付けフラッシュメモリに入れてアクセスすれば、CQマシンもできてしまいそうです(笑)

      SDRソフトの局発部分もこんな感じで生成しているようです。

      削除
  2. 卒業研究でやった記憶があります.周波数可変の三相正弦波発信器で,当時はEPROMとD/A変換器で作りました^^;

    返信削除
    返信
    1. JE1SGHさんこちらでははじめまして

      こんなちいさなPICで実験できるなんてほんといい世の中になったんですね。おもしろすぎて無線のアクティビティが落ちまくっています^^;;

      削除