« (東京の)雪の結晶 | トップページ | ラブジョイ彗星・2015年1月31日(~4月10日)の位置 »

2015年1月30日 (金)

PIC16F1705の8ビットDACを使って(実用的)正弦波発振器を作る - 1

この方法の最大の問題は出力周波数が離散的になってしまうことです。これについては出力周波数を連続的にする画期的なアイデアがあります。最高出力周波数も伸びます。

  「
ほよほよのブログ - 周波数可変な正弦波発生回路

  これを参考に自分の“ニーズ”に合わせたものが
    「
PIC16F1705のDAコンバータを使った正弦波発振器(発生器) - 改良版
  になります。


-------

インダクタンスの測り方・まとめ」みたいなことをやっているとちゃんとした正弦波発振器がほしくなります。“ちゃんとした”というのは再現性がいいという意味です。

1. 確度の高い周波数
2. 一定の出力レベル
3. 歪率ができるだけ小さいこと
  少なくとも歪率が一定であること
4. 90度位相のずれた正弦波を出力できること(要するにサインとコサインを出力)
  これは私の特殊事情です。なぜ必要なのか説明し始めるとややこしいので省略します。
  

よく使われるウィーンブリッジ発振器(「ウィーンブリッジ発振回路の帰還量(増幅率)と波形の関係」)や移相型CR発振器(「オペアンプで作る移相型発振回路 (1)」)はこれらの条件を満たすことは(私には)難しいです。
そこで「PIC/16F1705のDAC(DAコンバータ)を使ってみた(ソース付き)」に書いたPICのDACを使った手法で正弦波発振器を作りました。

ウィーンブリッジ発振回路で比較的“成功”した例もあります。
  「
もっとも波形がきれいなウィーンブリッジ発振回路 - 1
  「
もっとも波形がきれいなウィーンブリッジ発振回路 - 2
波形はきれいですが、周波数の確度や出力レベル安定化を確保できたわけではありません。


ソースを記事の末尾に付けました。これ(特にコメント)をご覧いただければどうやって作ったかわかると思います。

ちょっと注意した方がいいとかこれから作るかたに参考になると思われるところだけ記事として書きます。“2”として“実装編”を書く予定です。

  ==> 「PIC16F1705の8ビットDACを使って(実用的)正弦波発振器を作る - 悪い実装の例

================

4.は(オペアンプが一つ余っているわけですし)正弦波出力を積分するという方法があるのですがいろいろと面倒なのでPIC16F1705 を二つ使って同期した発振器を作ることにしました(これをマスター、スレーブと書くことにします)
8bit以上のDACが二つあるPICがあれば一つで作れますが....

マスターのクロック出力をスレーブのクロック入力に使います

できるだけ周波数の確度を高めるために水晶発振子を使います。この場合マスターのFOSCはHS、スレーブのFOSCはECHとします(余談ですが手元に8MHzの水晶がなく10MHzを使い40MHzにしています。あんまりこういうことはしない方がいいでしょう。ソースは8MHz/32MHzクロックを前提に書いてあります)

内部的に(つまり配線することなく)オペアンプのOPA2IN-は出力に接続しボルテージフォロワーを構成し、OPA2IN+はDAC出力に接続することができます。

この場合OPA2IN-は汎用入力ピンとして使えるようになり今回は周波数設定用に使っています。一方OPA2IN+はDAC出力に接続しても汎用入力ピンとしては使えないようです(マニュアルにも使えるとは書いてありません)

このDAC出力をオペアンプでバッファするのは必須です。DACは出力電圧によって出力抵抗が変化するためバッファしないととんでもないことになります。

一周期のサンプル点は64としていますが(特に高い周波数の場合は)ここまで必要ないかもしれません(目的・用途によって判断していただければと思います)

振幅は8ビット256ではなく200としています。分解能を落としてもったいないように見えますが歪率を下げるという意味ではこの方がよさそうです。“レールツレール”というのは謳い文句であってほんとに“レールツレール“と思うと痛い目に会うこともありそうです。

正弦波はウェイトだけでやっています。そのためみっともないソースになっています。タイマー割込みを使えばいいようにも思えるのですが割込み時のオーバーヘッドを考えるとかえって最高出力周波数は下がるのではないかと思います。
抜本的な対策はループの部分をアセンブラーで書くことでしょうが面倒で.... (^^;;

マスターとスレーブを同期させるのに割込みを使っています。余計なことを考えたく(したく)ないのならこればいちばん確実です。一見マスターとスレーブを同時にリセットすればいいように思えてしまうのですが実際はそれではうまくいきません。

-----

マスター(上)とスレーブ(下)の出力波形
Pic2output

90度(π/2)位相がずれた波形が得られます。正確に90度ずれているのか?はこれからのブログネタにしたいと思います (^^)

スペクトラム
Pic2output2

発振周波数に対する高調波はほとんど見られません。サンプル周波数とその高調波が見られますがこれはDACで波形を作るときは避けられません。ただこれだけ周波数が違えば簡単なローパスフィルターでほとんど除くことができると思います。

----

関連記事のリスト

  趣味の電子工作
    PIC
  趣味の気象観測
  趣味の実験

------

ソース

// PIC16F1705を使います(XCコンパイラ+PICkit3)
//  DACが8bitというのが有利です。

// 01 VDD....(2)
//  +5Vに接続します。
// 02 RA5/T1CKI/SOSCI/CLCIN3/OSC1/CLKIN
//  マスターのとき
//    a. 水晶振動子の一方に接続します
//    b. もう一方の端子を接地したコンデンサに接続します。
//  スレーブのとき
//    a. マスターのCLKOUTに接続します
// 03 RA4/AN3/T1G/SOSCO/OSC2/CLKOUT
//  マスターのとき
//    a. 水晶振動子の一方に接続します。
//    b. もう一方の端子を接地したコンデンサに接続します。
//    c. スレーブのCLKINに接続します。
//  スレーブのとき
//    NC
// 04 RA3/MCLR/VPP....(1)
//  周波数設定を変更したときのリセット用です。
//  マスターのとき
//    a. プルアップします。
//    b. スレーブのMCLRに接続します。
//    c. もう一方を接地したタクトスイッチに接続します。
//  スレーブのとき
//    a. マスターのMCLRに接続します。
// 05 RC5/OPA2IN+/CCP1/RX
//  内部的にはDACOUTに接続します。
//  a. NC
// 06 RC4/OPA2IN-/CK/CLCIN1
//  周波数設定用です。
//  OPA2INは内部的にはOPA2OUTに接続されボルテージフォロワーを構成します。
//  マスターのとき
//    a. スレーブのRC4と接続します。
//    b. DIPスイッチでVDD/VSS切り替えます。
//  スレーブのとき
//    a. マスターのRC4に接続します。
// 07 RC3/AN7/C1IN3-/C2IN3-/OPA2OUT/CCP2/SS/CLCIN0
//  オペアンプ2出力端子=正弦波出力端子です。
// 08 RC2/AN6/C1IN2-/C2IN2-/OPA1OUT
//  オペアンプ1出力端子です(OPA1自体は未使用です)
// 09 RC1/AN5/C1IN1-/C2IN1-/OPA1IN-/SDI/SDA/CLCIN2
//  オペアンプのOPA1IN-として使用します(OPA1自体は未使用です)
//    a. 帰還抵抗を介してOPA1OUTに接続します
// 10 RC0/AN4/C2IN+/OPA1IN+/SCK/SCL
//  OPA1IN+として使用します(OPA1自体は未使用です)
// 11 RA2/AN2/DAC1OUT2/ZCD/T0CKI/COGIN/INT
//  マスター・スレーブのタイミングをあわせるための割込みに使用します。
//  マスターのとき
//    a. スレーブのINTに接続します。
//    b. プルダウンしておきもう一方の端子をVDDに接続したタクトスイッチへ
//  スレーブのとき
//    a. マスターのINTに接続します。
// 12 RA1/AN1/VREF+/C1IN0-/C2IN0-/ICSPCLK....(5)
//  周波数設定用です。
//  マスターのとき
//    a. スレーブのRA1と接続します。
//    b. DIPスイッチでVDD/VSS切り替えます。
//  スレーブのとき
//    a. マスターのRA1に接続します。
// 13 RA0/AN0/VREF-/C1IN+/DAC1OUT/ICSPDAT....(4)
//  周波数設定用です。
//  マスターのとき
//    a. スレーブのRA0と接続します。
//    b. DIPスイッチでVDD/VSS切り替えます。
//  スレーブのとき
//    a. マスターのRA0に接続します。
// 14 VSS....(3)
//  a. GND(VSS)に接続します。

#include <pic.h>

#include <math.h>

#define MASTER #define _XTAL_FREQ 32000000   // delay用クロック設定 // DAC1CON0 #define DAC1EN  0x80            // DACを有効にします #define DAC1PSS_VDD     0x00    // DACのプラス側の基準電圧 #define DAC1NSS_VSS     0x00    // DACの-側の基準電圧 // OPAxCON #define OPAxEN  0X80            // オペアンプを有効にします #define OPAxSP_Low  0x00        // 周波数特性の設定です。要注意 #define OPAxUG_ON   0x10        // ボルテージフォロワーとして使います。 #define OPAxUG_OFF   0x00       // 帰還抵抗を使います #define OPAxCH_DAC  0x02        // オペアンプの入力をDACとします。 #define OPAxCH_OPAxIN   0x00    // ふつうのオペアンプとしての使い方です #ifdef MASTER #pragma config FOSC = HS        // 水晶発振子、高速用です #else #pragma config FOSC = ECH       // クロック外部入力、高速用です #endif #pragma config WDTE = OFF       // ウォッチドッグタイマーを使いません #pragma config PLLEN = ON       // PLLを使ってクロックを4倍にします。 // マスターとスレーブの正弦波出力のタイミングをあわせるために割込みを使います。 short Interrupt=1; void interrupt InterFunction( void ) {     // INT割込み     if( INTF == 1 ) {         Interrupt = 0;         INTF = 0;     } } void main() {     static short    lvl[64];     float           val;     float           PIx2=6.2832;     unsigned short  i,j,k,idx;     // 内部クロックを使う場合は8MHz x 4の設定です。     OSCCON = 0b11110000 ;     // デジタル入力ピンにするために設定です。     // 使わないピンは事故を避けるた入力のままにします。     ANSELAbits.ANSA0 = 0;     ANSELAbits.ANSA1 = 0;     ANSELAbits.ANSA2 = 0;     ANSELCbits.ANSC4 = 0;  // オペアンプの入力はアナログだからです     // DAC出力の設定     DAC1CON0 = DAC1EN | DAC1PSS_VDD | DAC1NSS_VSS;     // オペアンプ1の設定     // OPA1は使ってはいないんですが...     OPA1CON = OPAxEN | OPAxSP_Low | OPAxUG_OFF | OPAxCH_OPAxIN;             // オペアンプ2の設定     //  ボルテージフォロワーとして使う場合はV-ピンは入出力として使用可能です・     //   V+ は配線せずにDACに接続可能です。     OPA2CON = OPAxEN | OPAxSP_Low | OPAxUG_ON | OPAxCH_DAC;     //  RA0、RA1、RC4の状態で周波数を決定します。     //  だから周波数を変えたときはリセットが必要です。     idx =PORTAbits.RA0;     idx = idx << 1 | PORTAbits.RA1;     idx = idx << 1 | PORTCbits.RC4;     for( i=0; i<64; i++) { #ifdef MASTER         val =100.0*sin(PIx2*(float)(i)/64.0)+128.0; #else         // スレーブはマスターに対し90度位相をずらします。         val =100.0*sin(PIx2*(float)(i+16)/64.0)+128.0; #endif         if( val < 0.0 ) {             lvl[i] = -(int)(fabs(val)+0.5);         } else {             lvl[i] = (int)(val+0.5);         }     }     // マスターとスレーブが同時にスタートできるように割込みを待ちます。     // 一見PICを同時にリセットすればこんなことをしなくてもよさそうですが....     // それがそうじゃないんですね。     INTE = 1;     GIE=1;     while( Interrupt );     // 以下間抜けなコーディングですが確実に動きます。     // もっと高い周波数を出力したいときは     //  サンプリング数を64から32とか16に減らす(とうぜん高調波は増えます)     //  アセンブラーでコーディングしましょう     //  Cで書く限りタイマー割込みは無意味なような....     switch(idx) {         case 0:  // 60Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(275);             }             break;         case 1:  // 100Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(153);             }             break;         case 2:  // 180Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(84);             }             break;         case 3:  // 310hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(46);             }             break;         case 4:  // 570Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(24);             }             break;         case 5:  // 1000Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(12);             }             break;         case 6:  // 1800Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(5);             }             break;         case 7:  // 3400Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(1);             }             break;         default:  // 30Hz             while(1) {                 DAC1CON1 = lvl[i & 0x3F];                 i++;                 __delay_us(485);             }             break;     } }

« (東京の)雪の結晶 | トップページ | ラブジョイ彗星・2015年1月31日(~4月10日)の位置 »

趣味の電子工作」カテゴリの記事

コメント

90度位相をずらす理由はわかりませんが、2つのPICで同期を取る方法が面白いですね。
ちょっとスレッドプログラミングっぽいところもクールです^^。
__delay_xs() は測定してみるとわかりますがほんとにぴったりのコードを生成してます。アセンブラで書いても大変なだけでこのコードとの結果はそう変わらないと思います。つまりこれがベストだと思います。
待ち以外の部分でのオーバーヘッドがあるので高い周波数を正確に出すのは難しいとは思います。

このあたりが限界ですか....
となるともっとクロックの高いPICを使うとかサンプリングを間引きするとかそういう方向でしょうか。
これDACが二つあればPIC一つで済ませられるわけですが、買いに行くのも手間なのでそういうのがあるかどうかも調べていません (^^;;
sinとcosを同時に作っているのは四象限アナログ乗算器でちょっと遊んでみようと思いまして.... (^^)

こんばんは
__delay_us/__delay_msの場合は少し計算が入ります
なので若干オーバヘッドが有るので
それを避けるならダイレクトに_delay()を呼び出せば少し
オーバヘッドを小さく出来ますよ。

あれ、そういうのがあるんですか。知りませんでした (^^;;
とにかく何もわかっていないのでずいぶんムダなことをしているのではないかと思います。
こんなのでも自分が必要としていた条件は満たしているのでしばらく使って問題点をまとめてからまた改良に励みたいと思います (^^)

ごめんなさい、上のコメント嘘を教えたみたいですm(_ _)m
__delay_us/__delay_msが展開されたアセンブラコードを見て見たら
コンパイラが予め計算した結果でコードを吐き出しているので
__delay_us/__delay_msでも_delay()でも同じです。
おそらくオーバヘッドは無いと思えます、
安心して__delay_us/__delay_msを使って下さい。

わざわざありがとうございます m(._.)m
基板もひどいことになってますが、プログラムもめちゃくちゃなのかなあと心配していました (^^;;
これ周波数が伸びないことを除けば扱いやすく波形も安定していてなかなかいいです。実験に安心して使えます (^^)

この記事へのコメントは終了しました。

トラックバック


この記事へのトラックバック一覧です: PIC16F1705の8ビットDACを使って(実用的)正弦波発振器を作る - 1:

« (東京の)雪の結晶 | トップページ | ラブジョイ彗星・2015年1月31日(~4月10日)の位置 »

フォト

サイト内検索

  • 記事を探されるんでしたらこれがいちばん早くて確実です。私も使ってます (^^;; 検索窓が表示されるのにちょっと時間がかかるのはどうにかしてほしいです。

新着記事

リンク元別アクセス数

  • (アクセス元≒リンク元、原則PCのみ・ドメイン別、サイト内等除く)

人気記事ランキング

  • (原則PCのみ、直近2週間)
無料ブログはココログ