あまり時間をかけたくないので要件を絞った。(というか実験の結果、簡単にできそうなのを選んだ)
- 単電源波形のサイン波と三角波を出力。
- 出力振幅は3Vp-p程度。
- 出力帯域は可聴帯域から、できる範囲で広く。
- 電池駆動でコンパクトな筐体(PCM5102ファンクションジェネレータ程度)
「迷走の果て・Tiny Objects」さんは、RF帯域のフィルタリングを実験されていて勉強になりますが、私はRFには全く手をつけてなく(手に負えない)、オーディオ帯域のちょっと上ぐらいまでで用は済みそう。
AD9833は出力が0.6Vp-pで振幅が小さすぎるので、OPAMPで増幅することを中心にテストしてみた。
ブレッドボード配線図
Arduinoのひとつ下のブレッドボードがAD9833モジュール用で、ArduinoとSPI接続している。
AD9833の出力から10kΩ/BのPOTで出力レベルを減衰させて、そのひとつ下のブレッドボードの出力部につないでいる。
出力部は非反転増幅回路で振幅を増幅し、同じOPAMPでボルテージフォロアを組んで出力を増強している。
AD9833の出力は単電源波形で出力部の電源も+5Vの単電源としたので、OPAMPは単電源用(もしくはGNDまで出せるフルスイング)のOPAMPを使う。
Arduinoの上のブレッドボードはUI部で、aitendoのI2C LCDのSPLC792-I2Cと周波数選択用のロータリー・エンコーダーと波形選択用のタクトスイッチ。
SPL792-I2Cが3.3V仕様なので、ここは3.3V電源にしている。aitendoのSPLC792-I2Cは在庫がないことが多かったが、ATD1602C-P というのが出たようだ(ちょっと高い)。また、秋月のAQM0802AやAQM1602Aもコマンドコンパチなので使えると思う。
SPLC792-I2CはオンボードでI2Cのプルアップ抵抗が載っているので、I2CのSCL、SDAは電源電圧の3.3Vでプルアップされる。RESET線はArduinoからの出力レベルが5Vなので、6.8kと12kのRで分圧して3.3Vとしている。
Arduinoのスケッチ
<AD9833_Test.ino>
#include <SPI.h> #include <Wire.h> #include <stdio.h> #define UART_TRACE (0) #define TITLE_STR1 ("AD9833 FG") #define TITLE_STR2 ("20170726") //-------------------------------------------------------------------------------- // pin assign // // AD8933 const int FSYNC = 10; // Standard SPI pins for the AD9833 waveform generator. const int CLK = 13; const int DATA = 11; // Rotary Encoder const int RE_A = 2; const int RE_B = 3; // Tact SW const int SW1 = 4; // I2C LCD const int resetPin = 17; // analog pin 3 const int sdaPin = 18; // analog pin 4 const int sclPin = 19; // analog pin 5 const int i2cadr = 0x3e; const byte contrast = 32; // 最初は大きめにして調整する //-------------------------------------------------------------------------------- // constants // const float refFreq = 25000000.0; // On-board crystal reference frequency // Wave Form const int wfSine = 0; const int wfTriangle = 1; const int wfIndexMax = 2; // mask of AD9833 Control Register const uint16_t waveFormTable[] = { 0x2000, // Sine 0x2002 // Triangle }; const char waveFormName[][20] = { "SIN ", "TRI " }; const uint32_t frequencyTable[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, 100000, 200000, 300000, 400000, 500000, 600000, 700000, 800000, 900000, 1000000, 2000000, 3000000, 4000000, 5000000, 6000000, 7000000, 8000000, 9000000, 10000000, 11000000, 12000000, }; const int frequencyIndexMax = (sizeof(frequencyTable) / sizeof(uint32_t)); //-------------------------------------------------------------------------------- // Variables // int frequencyIndex = 27; // 1kHz int waveFormIndex = 0; // Sine wave int prevFrequencyIndex = frequencyIndex; int prevWaveFormIndex = waveFormIndex; const char strBuffer[80]; //-------------------------------------------------------------------------------- // Main routins // void setup() { pinMode(RE_A, INPUT_PULLUP); pinMode(RE_B, INPUT_PULLUP); pinMode(SW1, INPUT_PULLUP); #if UART_TRACE Serial.begin(9600); Serial.println("AD9833 UI Test."); sprintf(strBuffer, "%s %s", TITLE_STR1, TITLE_STR2); Serial.println(strBuffer); delay(1000); #endif // I2C LCD lcd_init(); lcd_puts(TITLE_STR1); //lcd_move(0x40); lcd_pos(1, 1); lcd_puts(TITLE_STR2); // AD8933 SPI.begin(); delay(50); AD9833reset(); delay(50); AD9833setFrequency(frequencyTable[frequencyIndex], waveFormTable[wfSine]); } void loop() { readParams(); uint32_t frequency = frequencyTable[frequencyIndex]; int waveForm = waveFormTable[waveFormIndex]; #if UART_TRACE displayParamsSerial(frequency, waveForm); #endif if (frequencyIndex != prevFrequencyIndex || waveFormIndex != prevWaveFormIndex) { prevFrequencyIndex = frequencyIndex; prevWaveFormIndex = waveFormIndex; // LCDに表示 displayParamsI2CLCD(frequency, waveFormIndex); // AD9833に出力 AD9833setFrequency(frequencyTable[frequencyIndex], waveFormTable[waveFormIndex]); } } //-------------------------------------------------------------------------------- // UI Input functions // void readParams() { // 周波数設定 Rotary Encoderの読み取り frequencyIndex += readRE(); if (frequencyIndex <= 0) { frequencyIndex = 0; } else if (frequencyIndex >= frequencyIndexMax) { frequencyIndex = frequencyIndexMax - 1; } // 波形設定SWの読み取り if (digitalRead(SW1) == LOW) { waveFormIndex++; if (waveFormIndex >= wfIndexMax) { waveFormIndex = 0; // wfSine; } delay(200); // (とりあえず)チャタリング防止 } } // Rotary Encoderの読み取り akizuki/Alps int readRE() { static uint8_t index; int retVal = 0; index = (index << 2) | (digitalRead(RE_B) << 1) | (digitalRead(RE_A)); index &= 0b1111; switch (index) { // 時計回り case 0b0111: // 01 -> 11 retVal = 1; break; // 反時計回り case 0b1101: // 11 -> 01 retVal = -1; break; } delay(1); // (とりあえず)チャタリング防止 return retVal; } //-------------------------------------------------------------------------------- // UI Display functions // #if UART_TRACE void displayParamsSerial(uint32_t frequency, int waveForm) { Serial.print(waveFormIndex); Serial.print('\t'); Serial.print("0x"); Serial.print(waveForm, HEX); Serial.print('\t'); Serial.print(frequencyIndex); Serial.print('\t'); Serial.println(frequency); } #endif void displayParamsI2CLCD(uint32_t frequency, int waveFormIndex) { //lcd_clear(); // 周波数表示 lcd_pos(0, 0); sprintf(strBuffer, "%8luHz", frequency); lcd_puts(strBuffer); // 波形表示 lcd_pos(1, 0); lcd_puts(waveFormName[waveFormIndex]); } //-------------------------------------------------------------------------------- // I2C LCD: akizuki AQM0802 / aitendo SPLC792-I2C // void lcd_cmd(byte x) { Wire.beginTransmission(i2cadr); Wire.write(0x00); Wire.write(x); Wire.endTransmission(); } void lcd_data(byte x) { Wire.beginTransmission(i2cadr); Wire.write(0x40); Wire.write(x); Wire.endTransmission(); } void lcd_puts(const char *s) { while(*s) lcd_data(*s++); } void lcd_init() { // reset delay(500); pinMode(resetPin, OUTPUT); digitalWrite(resetPin, LOW); delay(1); digitalWrite(resetPin, HIGH); delay(10); // LCD initialize delay(40); Wire.begin(); lcd_cmd(0x38); // function set lcd_cmd(0x39); // function set lcd_cmd(0x14); // interval osc lcd_cmd(0x70 | (contrast & 15)); // contrast low lcd_cmd(0x5c | (contrast >> 4 & 3)); // contrast high / icon / power lcd_cmd(0x6c); // follower control delay(300); lcd_cmd(0x38); // function set lcd_cmd(0x0c); // display on lcd_cmd(0x01); // clear display delay(2); } /* void lcd_move(byte pos){ lcd_cmd(0x80 | pos); } */ void lcd_pos(byte raw, byte col) { lcd_cmd(0x80 | ((raw & 0x01) << 6) | col); } void lcd_clear() { lcd_cmd(0x01); } //-------------------------------------------------------------------------------- // AD9833: Waveform Generator // // AD9833 documentation advises a 'Reset' on first applying power. void AD9833reset() { WriteRegister(0x100); // Write '1' to AD9833 Control register bit D8. delay(10); } // Set the frequency and waveform registers in the AD9833. void AD9833setFrequency(long frequency, int Waveform) { long FreqWord = (frequency * pow(2, 28)) / refFreq; int MSB = (int)((FreqWord & 0xFFFC000) >> 14); //Only lower 14 bits are used for data int LSB = (int)(FreqWord & 0x3FFF); //Set control bits 15 ande 14 to 0 and 1, respectively, for frequency register 0 LSB |= 0x4000; MSB |= 0x4000; WriteRegister(0x2100); WriteRegister(LSB); // Write lower 16 bits to AD9833 registers WriteRegister(MSB); // Write upper 16 bits to AD9833 registers. WriteRegister(0xC000); // Phase register WriteRegister(Waveform); // Exit & Reset to SINE, SQUARE or TRIANGLE } void WriteRegister(int dat) { // Display and AD9833 use different SPI MODES so it has to be set for the AD9833 here. SPI.setDataMode(SPI_MODE2); digitalWrite(FSYNC, LOW); // Set FSYNC low before writing to AD9833 registers delayMicroseconds(10); // Give AD9833 time to get ready to receive data. SPI.transfer(highByte(dat)); // Each AD9833 register is 32 bits wide and each 16 SPI.transfer(lowByte(dat)); // bits has to be transferred as 2 x 8-bit bytes. digitalWrite(FSYNC, HIGH); //Write done. Set FSYNC high }
AD9833の正弦波、三角波の出力レベルは0.6Vp-p、矩形波は出力レベルが電源電圧なので、5V駆動だと0V/5Vの波形になり危険なので使わないことにした。
周波数切替は上位1桁を可変することにして、ロジックを考えるのがめんどくさかったのでテーブル参照にした。←まだメモリ余ってますし(^q^;
自作のPCM5102ファンクションジェネレータはキーパッドで1Hz単位で周波数を指定できるようにしているが、あんまり使わなくて、上位2桁の増減でほとんど間に合っている。上位2桁でも、周波数を大きく変えるときはロータリーエンコーダーをガチャガチャ回さないといけないのでこういう仕様にした。
出力部のOPAMP
単電源用のNJM13404、高速単電源用のNJM2742、高精度単電源用のNJM2119、フルスイングのAD8532、AD822を差し替えてようすをみてみた。
単電源用の定番のLM358はクロスオーバー歪があるので候補から外した。(ほんとにLM358は難儀な子)
周波数は300kHzあたりからOPAMPの品種の違いが顕著になる。
NJM13404
ch1:出力部の出力 ch2:AD9833の出力
ダメダメ
AD8532
少しいびつ
AD822
比較的きれい
NJM2742
スルーレート:10V/us、GBW:2MHz。高速と銘打っているので期待したが、変なノイズが乗っている。発振してるのかも。
もしかすると、ボルテージフォロアに耐えられない?
NJM2119
高精度だが、300kHzぐらいだと帯域外のようだ。
AD822を使ってみる。
手持ちの中ではAD822が良さそうなので周波数、波形を変えて測定した。
出力部の非反転増幅回路は1.2kΩと4.7kΩのRを使って、A = 1 + (4.7kΩ / 1.2kΩ) ≒ 4.9倍。AD9833の出力に入れているPOTは右いっぱいにまわして最大出力とした。
1kHz
ch1:出力部の出力 ch2:AD9833の出力
10kHz
100kHz
1MHz
1MHzになると出力レベルが低下し、ほとんど増幅されない。この辺の帯域は出力部をバイパスしたほうがよさそうだ。
三角波1kHz
三角波100kHz
100kHzになると三角波の上下端がなまり始める。
OPAMPの非反転増幅回路を通すと、正弦波で数100kHz程度までしか使えなさそうだが、高周波数のノイズはOPAMPの帯域によって自然と取れるようだ。
WaveSpectra
WaveSpectraを使って可聴帯域のようすをみてみた。USB経由のノイズを回避するために電池(単3×6)をArduinoの電源とした。
AD9833の出力
出力部通過
振幅が同程度になるようにオーディオ・インターフェイスの入力レベルを調整しているので正確ではないと思うが、出力部を通したほうがノイズが小さくなっている。
SPLC792-I2Cまわり
I2C信号
ch1:SDA ch2:SCL
信号レベルは3.3Vになっている。
RESET線の電圧はテスタで測って、3.13Vだった。
メモ:
消費電流(Arduino込) 73mA
RFアンプとは言わないまでも、OPAMPでもう少し高性能のものもあるが、だいたい両電源なので電源をちゃんと作らないといけない。いずれは試してみるとして、まずは数100kHzまでの単電源波形出力のファンクションジェネレータとして作ってみる。
現状だと5V/3.3Vの電源が必要になる。3.3VはI2C LCDだけに使うのでわざわざ作るのもくやしいが、2014年末に作った可変安定化電源で使っていて実績があるので、しょうがないかな~
3.3V電源だけだと出力振幅を稼げない。
設定パラメータを内部EEPROMに保存して、電源ON時に再現できる?
0 件のコメント:
コメントを投稿