キーヤーは最後の詰めに入っていますが、その前にプログラムした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は共通です なぜだかわかりますよね |
そうやって出来上がったプログラムを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;
}
}
}