公開日:2025/12/20
title.png

37th Season


#37-6 デジタル制御のVCO

 今回の主題は、VCOの作成です。

VCOとは何ぞや?

 VCO(Voltage Controlled Oscillator)とは、電圧制御発振器の事を指します。アナログ電圧を入力として、その電圧値に従って発振周波数を変動させる、発振器です。
 回路を構成する素子(発振子など)や回路定数等によって発振周波数を変動するのではなく、入力電圧によって動的に周波数を変動させるのが特長です。PLL(Phase Locked Loop)を構成する発振器としても知られています。

 所謂VCOは、アナログ回路で作られています。

 今回は、ATtiny85とソフトウェア制御で作った可変周波数クロック発振器(以下、VCLKGENと呼ぶ)の応用で、ソフトウェアかつ、デジタル制御のVCOを作ってみたいと思います。

  「#37-1 可変周波数クロック発振器(ひと)

  「#37-2 可変周波数クロック発振器(ふた)

  「#37-3 可変周波数クロック発振器(さん)

 VCLKGENの設計については上の記事を参照してください。以降、VCLKGENを前提として話が進みます。

ATtiny85で作るVCO

 ATtiny85を使って作成したVCLKGENが既に完成していますので、後は、入力されたアナログ電圧に従って出力クロック周波数を変動させる機構を作り込めば良いという事になります。

 ATtiny85(下図)には、10ビットAD変換器が4チャンネル搭載されています。

a293_attiny85.jpg

 アナログ電圧をAD変換器で計測して、そのAD変換値に従って出力クロックの周波数を変動させるVCLKGENに改造すれば良いのです。これで、入力電圧に対応した周波数でクロックを出力するVCOとなります。

 VCLKGENとしては、約2Hz〜42.5MHzという、広範囲な周波数を出力可能でしたね。
 これだけ広範囲の周波数が出力可能なので、入力電圧に対して決め打ちの周波数を出力するよりも、出力するクロック周波数範囲を動的に設定できた方が得策だと考えました。よって、VCLKGENで採用していたI2Cスレーブデバイスの機能は残したいと思います。こうしておくと、I2C通信経由で出力周波数範囲を設定することができますから。

 I2Cスレーブアドレスは、後ほど実験で使う評価プログラム(pwm_freq.ino)を、VCLKGENの評価で使った物と共通化したいため、同じスレーブアドレスとしました。ただしスレーブアドレスは、コンパイル時に指定した定数値で設定可能とします(以下コード)。

  const int i2c_slv_adr=0x0c;  // i2c: slave address (7 bits)

 もし、他のI2Cスレーブデバイスと重複する場合には、異なるスレーブアドレスに設定してコンパイルして使えばOKです。

AD変換器の端子割り当て

 ATtiny85の貴重な8本のI/Oポートから、アナログ入力電圧を受ける端子を捻出しておきましょう。
 AD変換器が使える端子は、A0〜A3の4本なのですが、VCLKGENでは全ての端子が他の機能として割り当て済みです……。

 今回設計しているVCOはVCLKGENとは一線を画すので、全くの別応用と捉えて良いでしょう。と言っても、クロック出力端子の確保は勿論、I2Cスレーブデバイスとして通信端子を確保する事は譲れません。
 残ったのは、リセット出力端子RESOだけなのですが、丁度ここにアナログ入力A3端子が割り当たっています。アナログ入力A3端子を使って、入力電圧AINを計測する事とします(下表)。

DIP Arduino ATtiny85 電圧制御発振器
1 D5[A0] PB5(RESET) RESET
2 D3[A3] PB3(ADC3) AIN
3 D4[A2] PB4(OC1B) CLKO
4 GND
5 D0 PB0(SDA) SDA
6 D1 PB1
7 D2[A1] PB2(SCL) SCL
8 VCC

 改めて、ATtiny85と言うマイコンは、たったの8端子とは思えないほど高機能なマイコンです。裏返すと、「高機能に端子数が見合っていない」とも言えます。でも個人的には、そう言うの大好きです。(ギャップ萌え?)

目標周波数を出力するための設定値逆算

 前節までに検討した様に今回のVCOでは、AD変換した値によって目標周波数fCLKOが与えられます。この目標周波数fCLKOを達成するため、ATtiny85内蔵I/Oレジスタ設定値を逆算して求める必要があります。

 VCLKGENでは、OSCCAL、PLLCSR、TCCR1、OCR1Cの各I/Oレジスタ4個を設定して、結果として以下周波数のクロックCLKO出力を得ていました。

 今回の応用では先にfCLKOが決まるので、上式からの逆算で各I/Oレジスタ値を求めなくてはなりません。自由度が高いため(4変数)、複数の組み合わせで目標周波数を達成することができます。それ故に逆算するのが難しいということです。
 そこで、以下の様な逆算アルゴリズムを考えてみました。

  目標周波数からI/Oレジスタ値を逆算:

 上で考案したアルゴリズムはVCLKGENとして使う時にも有効です。256個のfOSCCAL[ ]で総当たりするアルゴリズムなので、プログラムやスクリプトなどに落とし込んで使うのが吉です。

※なお、エラー(2か所)は無視していますが、VCLKGENが出力可能な周波数範囲で使う事が大前提です。

VCOのI/Oレジスタ仕様

 今度は、VCO(電圧制御発振器)としてI2C通信でアクセス可能なI/Oレジスタの仕様を策定して行きます。

  I/Oレジスタ目標仕様:

 本当は、指数部3ビットで充分だったので、その分仮数部を1ビット増やすと、もう少し有効数字を増やせたのですが、人間が見て解り易い様に、ニブル(4ビット)区切りとしました。あまり細かな周波数を指定できたところで、RC発振器にも、OSCCALレジスタの設定値等にも、それほど高い精度は無いため、この仕様で充分だと考えられます。

プログラム

 VCLKGENのプログラム(ti85_vclkgen.ino)を流用して、電圧制御発振器(VCO)プログラムへ改造しました。完成したプログラムを以下にリンクします。

  完成プログラム:ti85_vctlosc.ino

 ATtiny85へは、UNOを書き込み装置(Arduino ISP)として使い、シリアルプログラミングモードで書き込みます。具体的な書き込み方法は、こちらを参照してください。

評価用プログラム

 VCLKGENの評価(実験)に作って使用したプログラム(pwm_freq.ino)を改造して使います。このプログラムには、入力されたPWMの周波数を測定したり、I2C通信でスレーブデバイスを設定したりする機能があるので、これを利用します。

 VCOの評価としては上述機能の他に、発振周波数を指定するためのアナログ電圧を外部から印可できる必要があります。アナログ電圧の印可には、12ビットDA変換器モジュールMCP4725(下図)を使います。

ar79_dac12.jpg

 MCP4725モジュールへのアクセス・制御を追加して、以下にリンクします。

  実験プログラム:pwm_freq.ino

 後ほどArduino UNOにATtiny85(VCO)を接続して実験します。上のプログラムをコンパイルしてUNOへ書き込むと、シリアルモニタから、以下のコマンドを受け付ける様になっています。

  追加シリアルモニタコマンド仕様:

電圧制御する実験

 VCOとしてのATtiny85、アナログ電圧を発生するDA変換器MCP4725モジュール、実験をホストするArduino UNOを、以下表に従って接続してください。

Arduino UNO 接続 電圧制御発振器
ATtiny85
接続 MCP4725
モジュール
5V VCC VCC
GND GND GND
A5(SCL) D2(SCL) SCL
A4(SDA) D0(SDA) SDA
  A3(AIN) OUT
D8(ICP1) D4(CLKO)  

※今回は1/1,000分周器としての328duinoを使わないので、数10kHzを超える高速なクロックは評価できないことに注意。高速クロックの評価については、こちらを参照の上、各自対応のこと。

 ATtiny85には、こちらのプログラム、UNOには、こちらのプログラムを書き込んでおいてください。

 以下図に実験中の様子を示します。

a376_expr.jpg

 では、以下の様な実験サンプルに従って実験してみましょう。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【srd1,0】
  OK+ srd1.0c[0x00]=0x56
  OK: srd1 1

 軽いジャブで、CMNDIDレジスタを読み出してみました。目標仕様通りです。


  【swr1,2,0,54,0,52】
  OK+ swr1.0c[0x02]=0x00
  OK+ swr1.0c[0x03]=0x54
  OK+ swr1.0c[0x04]=0x00
  OK+ swr1.0c[0x05]=0x52
  OK: swr1 4

 出力周波数範囲を以下の通りに設定しました。

 電圧が高いと周波数が低くなり、電圧が低いと周波数が高くなる、所謂、負比例の状態です。確かめてみましょう。

  【daco,0】
  OK: DAC=0[LSB] fPWM=50000[Hz]

  【daco,4095】
  OK: DAC=4095[LSB] fPWM=741[Hz]

 12ビットDA変換モジュールMCP4725を使い、最低電圧(0LSB)と最高電圧(4,095LSB)を印加し、その時の出力クロック周波数を計測しました。設定した通り、負比例の関係になっている様です。

  【daco,2048】
  OK: DAC=2048[LSB] fPWM=25396[Hz]

 VCC÷2の中間電位を印加して見ました。入力電圧と周波数は比例関係なので、(50kHz+500Hz)÷2=25,250Hzが期待値です。こちらも、ほぼ仕様通りの値が出ているようです。
 ただ、アナログ電圧(AD変換結果)に従って出力周波数が変化するので、ピッタリ正確に設定したままの周波数が出るわけではありません。入力電圧のAD変換結果次第で出力周波数が変動しますから。たった三点のピンポイントでは良く解りませんね。
 もう少し細かくデータを取って見ましょう。

  【evco】
  0,50081
  64,50019
  128,50111
  192,47887
  :
  : (中略)
  :
  3840,4473
  3904,3745
  3968,3088
  4032,2711
  OK: evco

 入力電圧を変化させながら周波数測定しました。結果をExcelに貼り付けてグラフ化して見ると、以下図となります。

a376_vctlosc.png

 横軸に入力電圧[LSB]、縦軸に出力クロックの周波数[Hz]をプロットしたグラフです。入力電圧に対して直線的に周波数が変化する様子が見て取れます。また、設定した通り、負比例の関係になっていました。
 多少の凸凹も気になるところですが、それ以上に安定時間の長い事が気になりました。上図の測定自体は、たっぷり安定時間(数100ms)を取りながら実施してあります。

 安定時間が長い原因は、概ね以下の様な処理によるものと考えています。

 ATtiny85のソフトウェア制御ですからね……。この安定時間では、あんまり実用的ではないかな?


 デジタル制御のVCOなら、入力電圧と出力周波数の関係を単なる直線関係だけではなく、対数軸で直線にしたりとか、「自由にプログラム出来て良さそう」と思ったのだけど、安定時間の長さは想定外でした。


btn_arduino.png

btn_prev.png btn_next.png

home