2017年7月30日日曜日

AD9833ファンクションジェネレータ 設計


テスト用ブレッドボード配線図
写真とブレッドボード図は上下が逆です。

回路図 メイン部

回路図 出力部

基板はメイン部と出力部に分けることにした。

メイン部は3.3V系で統一、出力部は電池電源(6V~9Vを想定)のなりゆきで使う。

メイン部


MPUにはATMega328Pを使い、ファームウェアはArduinoでプログラミング。Arduino Unoは外部クリスタルを使って16MHz駆動しているが、ATMega328Pの仕様では外部クリスタルを使って10MHz以上で駆動する場合は5V電源が必要となる。

今回は、AD9833モジュールとUI系を司るだけなので、高速&正確なクロックは必要がなく、内部RCで8MHz駆動することにした。8MHzなら3.3V電源で間に合う。(参考「ArduinoのスケッチをAVR単体で動作させる。」)

I2C LCD、AD9833モジュールとも3.3V系で動作するので、全体として3.3Vで統一した。

電源電圧を監視するために、回路図のC2エリア付近のR2、R3で電源電圧を分圧してAVRのPC0(ADC0)に入力して、LCDに表示するようにしている。

ADCのVREFは内部レファレンスの1.1Vにしているのでここの分圧をMAX 9Vぐらいまで測れるようにするか悩み中。

3.3k、12kで分圧: 5V * (3.3k / (12k + 3.3k)) ≒ 1.078V
4.7k、33kで分圧: 9V * (4.7k / (33k + 4.7k)) ≒ 1.122V

出力部


出力部は0.6Vp-pの波形を非反転増幅回路で、約5Vp-pまで増幅する。

出力部の回路図のR1とR2で増幅率が決まり、

A = 1 + (R1 / R2) = 1 + (8.2k / 1k) = 9.2

0.6V × 9.2 = 5.52V

となる。

約5Vp-pまで増幅するので、フルスイングOPAMPを使っても電源は6V程度以上は必要で、低電圧タイプのOPAMPは不可となる。

基板設計


メイン部

出力部

※配線まだ。


メイン部はC基板、出力部は秋月で売っているDタイプのシールドメッシュ基板を試しに使ってみることにする。

シールドメッシュ基板の裏面はベタGNDではない(よく見たらベタGNDでした)が、ランド周囲をぐるっとベタパターンが囲んでいて、なんとなくノイズに強そう。

外周のホール以外は穴経が小さくて、AMP EIのポストヘッダーがささらない。

筐体の構想



電池を外出しにすれば、タカチのプラケース(PB-2)で収まりそうだが、破綻したら秋月の一回り大きいポリカーボネート・ケースにする。

ケース内配線

波形(Sine/Triangle)切り替えスイッチはタクトスイッチだと、パネル取り付けの加工が難しそうなのでトグルスイッチにした。

メモ:


レベル調整用のPOTは通信機用のものを使いたい。

基板はんだ付け→モックアップ作成→筐体作成。

シールドメッシュ基板は周囲のベタパターンをGNDに繋がないとだめ?1点にすべき?

2017年7月27日木曜日

AD9833ファンクションジェネレータの構想


あまり時間をかけたくないので要件を絞った。(というか実験の結果、簡単にできそうなのを選んだ)

  • 単電源波形のサイン波と三角波を出力。
  • 出力振幅は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 というのが出たようだ(ちょっと高い)。また、秋月のAQM0802AAQM1602Aもコマンドコンパチなので使えると思う。

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時に再現できる?

2017年7月26日水曜日

AD9833モジュールをふたたび使ってみる。

以前、かんたんにテストして放置していた、AD9833のモジュールを使ってファンクションジェネレータを作ることを考えた。



自作のPCM5102とPSoC 5LPを使ったファンクションジェネレータをサイン波の生成に使っているが、帯域は192kHzまで(きれいな正弦波を出力できるのは50kHz程度まで)で、両電源波形しか出力できない。

AD9833は単電源波形で仕様上は12.5MHz出力できるので使ってみることにした。

迷走の果て・Tiny Objects」さんと、「John Owen」さんの記事を参考にしました。

UARTでシンプルにテスト


ブレッドボード配線図
パラメータの設定はUART経由で行うので配線はシンプル。

AD9833モジュールにはREFピンというのがあって、モジュールに載っている水晶のクロック信号が出力されている。「迷走の果て・Tiny Objects」のedyさんにここは接続しないほうがいいよと以前アドバイスいただいたので、はんだ付けしていたピンヘッダをニッパーで切断した。

REFピンの出力

Arduinoのスケッチ
<AD9833_Test2.ino>

#include <SPI.h>

const int SINE = 0x2000;                    // Define AD9833's waveform register value.
const int SQUARE = 0x2020;                  // When we update the frequency, we need to
const int TRIANGLE = 0x2002;                // define the waveform when we end writing

const int FSYNC = 10;                       // Standard SPI pins for the AD9833 waveform generator.
const int CLK = 13;                         // CLK and DATA pins are shared with the TFT display.
const int DATA = 11;

const float refFreq = 25000000.0;           // On-board crystal reference frequency

long frequency = 1000;
int waveType = SINE;

void setup()
{
  Serial.begin(9600);
  Serial.println("AD9833 Test.");
  Serial.println("Input <waveType>, <frequency>");
  Serial.println(" waveType: 0:Sine 1:Square 2:Triangle");
  Serial.println(" frequency: 0..12500000");
   
  SPI.begin();
  delay(50);
 
  AD9833reset();
  delay(50);
  AD9833setFrequency(frequency, SINE);
}

void loop()
{
  while (Serial.available() > 0) {
    int _wave = Serial.parseInt();
    long _freq = Serial.parseInt();  

    if (Serial.read() == '\n') {
      constrain(_wave, 0, 2);
      constrain(_freq, 0, 12500000);

      switch (_wave) {
      case 0: waveType = SINE; break;
      case 1: waveType = SQUARE; break;
      case 2: waveType = TRIANGLE; break;
      }
      frequency = _freq;

      Serial.print("0x");
      Serial.print(waveType, HEX);
      Serial.print('\t');
      Serial.println(frequency);

      AD9833setFrequency(frequency, waveType);
    }
  }
}

// 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
}

出力波形


2MHz

5MHz

12MHz

2MHzぐらいまでは正弦波っぽい波形だが、5MHz程度で出力レベルが低下して波形があやしくなり、12MHzではDDSのテーブルのサンプリング値の周期性のためと思われる波が発生している。※オシロの表示値は10.01MHzになっているが設定は12MHz。

自作のPCM5102を使ったファンクションジェネレータでもこの現象は経験した。(参考「PCM5102A ファンクションジェネレータの出力波形」)

DDS方式の波形生成だとナイキスト周波数付近は波形が怪しくなるので、使わないほうが無難かも知れない。

WaveSpectraで測定


1kHzサイン波を出力して、WaveSpectraでFFTで測定してみた。

AD9833

自作のPCM5102ファンクションジェネレータ

サンプリング周波数は25MHzだが、10bit DACなので可聴帯域でも高調波歪が多い。

メモ:


UART通信を行うと出力波形にUARTのクロックノイズ(というのか?)が乗るようだ。

AD9833は矩形波も出力できるが、正弦波、三角波は0.6Vp-pなのに大して、矩形波は電源電圧いっぱいで出力される。

2017年7月24日月曜日

PSoC 5LP内蔵DACとMCP4922の処理能力を見てみる。


ブレッドボード配線図

デバッグ情報表示用にキャラクタ液晶を接続している。P2[6:0]。

80MHz駆動するために、20MHzの水晶と22pFのCを接続。水晶をつなぐPinは固定で、P15[0]、P15[1]。

MCP4922はSPIの信号線を割り当てた。SCK P12[0]、MOSI P12[2]、CS P12[3]。

処理タイミングを計測するPinを割り当てた。P0[0]。

内蔵DACの出力はP3[0]。

内蔵DAC(VDAC8)


TopDesign

PSoC 5LPの内蔵DACはVDAC8というコンポーネントを使う。8bit精度。VDAC8の出力は弱いのでOPAMP(Follower)で増強する。

<main.c>

/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include "project.h"
#include <stdio.h>
#include <math.h>

#define TITLE_STR1  ("VDAC Test")
#define TITLE_STR2  ("20170723")

#define PI        (3.141592653589793238462)
#define AMPLITUDE (1.0)    // x * 3.3V
#define PHASE     (PI * 1) // 2*pi is one period
#define RANGE     (0x7FFF)
#define OFFSET    (0x7FFF)

// Configuration for wave output
#define BUFFER_SIZE (360)
uint16_t buffer_sine[BUFFER_SIZE];

char strBuffer[80];
    
// Create the wave buffer
void calculate_sinewave(void){
    for (int i = 0; i < BUFFER_SIZE; i++) {
        double rads = (PI * i)/180.0; // Convert degree in radian
        buffer_sine[i] = (uint16_t)(AMPLITUDE * (RANGE * (sin(rads + PHASE))) + OFFSET);
    }
}

int main(void)
{
    uint8_t cnt = 0;
    
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    LCD_Char_Start();
    UART_Start();
    VDAC8_1_Start();
    Opamp_1_Start();
    
    LCD_Char_PrintString(TITLE_STR1);
    LCD_Char_Position(1, 0);
    LCD_Char_PrintString(TITLE_STR2);
    
    sprintf(strBuffer, "\r\n%s %s\r\n", TITLE_STR1, TITLE_STR2);
    UART_PutString(strBuffer);

    calculate_sinewave();
    
    for(;;)
    {
        /* Place your application code here. */
        for (int i = 0; i < BUFFER_SIZE; i++) {
            Pin_Check1_Write(1);
            VDAC8_1_SetValue(buffer_sine[i] >> 8);
            Pin_Check1_Write(0);
        }
        
        cnt++;
    }
}

/* [] END OF FILE */

プログラムはNucleo F303で試したものとほぼおなじ処理で、事前にサイン波テーブルを生成しておいて、値を連続してDACに出力している。

DACに出力する前後でPin_Check1をH/Lさせているので、Highの時間を見れば処理に要した時間がわかる。

浮動小数点演算を行う場合、math libraryをリンクする必要がある。

メニューのProjectから[Build Setting...]を選択。ダイアログでコマンドラインオプションに「-lm」を設定する。


MCP4922


TopDesign

<main.c>

/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include "project.h"
#include <stdio.h>
#include <math.h>

#define TITLE_STR1  ("MCP4922 Test")
#define TITLE_STR2  ("20170723")

#define PI        (3.141592653589793238462)
#define AMPLITUDE (1.0)    // x * 3.3V
#define PHASE     (PI * 1) // 2*pi is one period
#define RANGE     (0x7FFF)
#define OFFSET    (0x7FFF)

// Configuration for wave output
#define BUFFER_SIZE (360)
uint16_t buffer_sine[BUFFER_SIZE];

char strBuffer[80];
   
// Create the wave buffer
void calculate_sinewave(void){
    for (int i = 0; i < BUFFER_SIZE; i++) {
        double rads = (PI * i)/180.0; // Convert degree in radian
        buffer_sine[i] = (uint16_t)(AMPLITUDE * (RANGE * (sin(rads + PHASE))) + OFFSET);
    }
}

// Write to MCP4922
// v: 0..4096
void MCP4922_write_u16(uint16 v)
{
    SPIM_WriteTxData((v >> 8) | 0x30);
    SPIM_WriteTxData(v & 0xff);
    while (! (SPIM_ReadTxStatus() & SPIM_STS_SPI_DONE))
        ;
}

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    LCD_Start();
    SPIM_Start();
    UART_Start();
   
    LCD_Position(0u,0u);
    LCD_PrintString(TITLE_STR1);
    LCD_Position(1u,0u);
    LCD_PrintString(TITLE_STR2);
   
    sprintf(strBuffer, "%s %s \r\n", TITLE_STR1, TITLE_STR2);
    UART_PutString(strBuffer);
   
    CyDelay(1000u);
    //LCD_ClearDisplay();
   
    calculate_sinewave();
   
    /*
    for (int i = 0; i < BUFFER_SIZE; i++) {
        sprintf(strBuffer, "%u\r\n", buffer_sine[i]);
        UART_PutString(strBuffer);
    }  
    UART_PutString("Dump end\r\n");
    */

    for(;;)
    {
        for (int i = 0; i < BUFFER_SIZE; i++) {
            Pin_Check1_Write(1);
            MCP4922_write_u16(buffer_sine[i] >> 4);
            Pin_Check1_Write(0);
        }
        //CyDelay(1);
    }
}

/* [] END OF FILE */

処理時間の比較


VDAC8

ch2:Pin_Check1の出力

周波数は1.702MHzで1ループは約587.5ns。
DAC出力の処理時間は350.0ns

MCP4922

ch2:Pin_Check1の出力

周波数は491.0kHzで1ループは約2037ns。
DAC出力の処理時間は1800ns

DAC LOOP(ns) DAC(ns) LOOP-DAC
VDAC8 587.5 350.0 237.5
MCP4922 2037 1800 237

PSoC 5LPはCoretex-M3でCortex-M4のNucleo F446と比較すると、
Nucleo F446の内蔵DAC(12bit精度)は700nsだったので、PSoC 5LPの内蔵DACは8bit精度ではあるが処理が速い。

SPI接続のMCP4922はNucleo F446でSPIクロックが22.44MHzのとき6.74us(6740ns)だったので、こちらもかなり速い。

PSoC Creatorはグラフィカルで使い易いが、ネイティブなので高速なコードを生成してくれるようだ。

出力波形

VDAC8

サイン波の周波数:4.726kHz

MCP4922

サイン波の周波数:1.336kHz

WaveSpectraで測定

VDAC8

MCP4922

周波数が違うので単純に比較はできないが、VDAC8は高調波歪がはっきり出ている。

MCP4922のSPI信号


SCK : MOSI

ch1:MOSI ch2:SCK

SPI Masterコンポーネントのクロックに40MHzを入れているがSCKは8.676MHzになっている。

SCK : CS

ch1:CS ch2:SCK

CSがアクティブ(L)の区間は目盛りから読み取って約840ns。1周期は約2026nsでPin_Check1で測定した周期とほぼ同じ。

Github:
VDAC8
https://github.com/ryood/PSoC-Creator-4.1-Test/tree/master/VDAC_Test.cydsn

MCP4922
https://github.com/ryood/PSoC-Creator-4.1-Test/tree/master/MCP4922_Test.cydsn

<追記:2017.07.26>

SCKを拡大して測定し直したら、20MHzだった。


ch1:SCK ch2:CS

8.676MHzになっている画像も、SCKは200ns×4マス=800nsで16クロックなので

800ns / 16clk = 50ns
1 / 50ns = 20MHz

になり、たまたま少ない周波数が表示されていただけだと思う。

また、SPI Masterコンポーネントに「Enable High Speed Mode」というプロパティがあり、これをチェックしない場合最大クロック9MHz、チェックすると最大クロック18MHzまで対応する。
←したがって、SCK 20MHzは仕様外。

</追記>

2017年7月23日日曜日

可変電流源 でけた&リードベンダでジャンパ線の加工


回路図

基板図

前回ブレッドボードで組んだ回路で測定したのとほぼおなじ条件で測定

OPAMP: OPA2134

10kΩ (出力電圧 = 入力電圧×10)

ch1:入力電圧 ch2:出力電圧

リードベンダでジャンパ線の加工


サンハヤトのリードベンダでジャンパ線の加工をしてみた。


リードベンダで所望のピッチでスズメッキ線を曲げる。



熱収縮チューブをスズメッキ線に通して、基板の表側になる部分を絶縁する。


φ2mmの熱収縮チューブなので収縮させてもガバガバです。もう少し径が細い方がいいかも。

熱収縮チューブで絶縁しなくても使えますが、基板の表になる部分を絶縁しておくと、何かの拍子にジャンパ線がショートしてしまう可能性を減らせます。

Github:
https://github.com/ryood/VCCS_Unit