あまり時間をかけたくないので要件を絞った。(というか実験の結果、簡単にできそうなのを選んだ)
「
迷走の果て・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時に再現できる?