PR

Arduino UNO R4 MinimaのDAC ~ analogWaveクラス編

チュートリアルのサンプルコード

以前、公式チュートリアルのDACの解説のサンプルコードを試してみた。そのときのサンプルコードはノコギリ波のデータを配列で用意しておき、それをanalogWrite()でDACに出力するというものだった。

ところが、その解説が変更されていて、今はanalogWaveクラスを使ったものになっている。以前のサンプルコードは消えている。ちょっともったいない気がするので、手元に保存してあったものを載せておく(問題があったら教えてください)。

ファイルは二つ。最初がWaveform.hで、もう一つがWaveform.c。

#ifndef _Waveforms_h_
#define _Waveforms_h_

#define maxSamplesNum 120

static int waveformsTable[maxSamplesNum] = {

    0x22, 0x44, 0x66, 0x88, 0xaa, 0xcc, 0xee, 0x110, 0x132, 0x154,
    0x176, 0x198, 0x1ba, 0x1dc, 0x1fe, 0x220, 0x242, 0x264, 0x286, 0x2a8,
    0x2ca, 0x2ec, 0x30e, 0x330, 0x352, 0x374, 0x396, 0x3b8, 0x3da, 0x3fc,
    0x41e, 0x440, 0x462, 0x484, 0x4a6, 0x4c8, 0x4ea, 0x50c, 0x52e, 0x550,
    0x572, 0x594, 0x5b6, 0x5d8, 0x5fa, 0x61c, 0x63e, 0x660, 0x682, 0x6a4,
    0x6c6, 0x6e8, 0x70a, 0x72c, 0x74e, 0x770, 0x792, 0x7b4, 0x7d6, 0x7f8,
    0x81a, 0x83c, 0x85e, 0x880, 0x8a2, 0x8c4, 0x8e6, 0x908, 0x92a, 0x94c,
    0x96e, 0x990, 0x9b2, 0x9d4, 0x9f6, 0xa18, 0xa3a, 0xa5c, 0xa7e, 0xaa0,
    0xac2, 0xae4, 0xb06, 0xb28, 0xb4a, 0xb6c, 0xb8e, 0xbb0, 0xbd2, 0xbf4,
    0xc16, 0xc38, 0xc5a, 0xc7c, 0xc9e, 0xcc0, 0xce2, 0xd04, 0xd26, 0xd48,
    0xd6a, 0xd8c, 0xdae, 0xdd0, 0xdf2, 0xe14, 0xe36, 0xe58, 0xe7a, 0xe9c,
    0xebe, 0xee0, 0xf02, 0xf24, 0xf46, 0xf68, 0xf8a, 0xfac, 0xfce, 0xff0
  

};

#endif
/*
  Simple Sawtooth Waveform generator with Arduino UNO R4 Minima

 */

#define oneHzSample 10000 / maxSamplesNum  // sample for the 1Hz signal expressed in microseconds


int i = 0;
int sample;


void setup() {
  analogWriteResolution(12);  // set the analog output resolution to 12 bit (4096 levels)
  analogReadResolution(12);  // set the analog input resolution to 12 bit (4096 levels)
}

void loop() {

  sample = map(analogRead(A5), 0, 4095, 0, 1000);

  analogWrite(DAC, waveformsTable[i]);  // write the selected waveform on DAC0

  i++;
  if (i == maxSamplesNum)  // Reset the counter to repeat the wave
    i = 0;

  delayMicroseconds(sample);  // Hold the sample value for the sample time
}

今のサンプルコードはとってもシンプル。比較のために、これも掲載(ファイルは一つだけ)。

#include "analogWave.h"

analogWave wave(DAC);

int freq = 10;  // in hertz, change accordingly

void setup() {
  Serial.begin(115200);
  pinMode(A5, INPUT);
  wave.sine(freq);
}

void loop() {
  freq = map(analogRead(A5), 0, 1024, 0, 10000);
  Serial.println("Frequency is now " + String(freq) + " hz");
  wave.freq(freq);
  delay(1000);
}

wave.sine()の引数で周波数を指定するだけで正弦波を出せるようだ。周波数の変更はwave.freq()。実にシンプル。A5ピンに可変抵抗をつないで周波数を変えられるようになっている。なお、ArduinoIDEのスケッチ例にもこのコードはあるが、バグっている。可変抵抗をつなぐピンがA0になってしまっている。A0はDACの出力ピンなので他のアナログピンに変えなければならない(DAC出力はA0ピンに固定されている)。ついでにいうと、「freq = map(analogRead(A5), 0, 1024, 0, 10000)」も1024じゃなくて1023だと思うが。

その後、可変抵抗の接続ピンがA0になっていたバグは修正された模様。pinMode()の行も省略された。map()の最大値に1024が設定されているのは相変わらずだけど。

話がそれたので戻す。

analogWave.hを探してみてみたところ、sineのほかにsquareやsawもある。amplitudeで大きさも変えられるようだ。

public:
  /* constructor 1 with only DAC pin (to be used when predefined waveform are used) */
  analogWave(pin_size_t pinNumber);
  /* constructor 2 with buffer to be used (sample in buffer are output ciclycally */
  analogWave(pin_size_t pinNumber, uint16_t *_buffer, uint32_t _size, uint32_t _offset);
  
  /* begin the output of the waweform the frequency is the one the complete
     set on sample are output (all samples will be output in 1 period */
  bool begin(float freq_hz);
  /* updated the frequency used */
  bool freq(float freq_hz);
  /* make the current index in the buffer to "jump" of the specified quantity */
  void offset(uint32_t o);
  /* start the generation of sample */
  void start();
  /* stop the genration of sample */
  void stop();
  /* every sample is multiplied for this factor must be a value between 0 and 1 */
  void amplitude(float amplitude);
  /* get the number of sample used */
  uint32_t num_of_sample();

  void _pre_sync();

  void sync(analogWave &w);

  void sine(float freq_hz);
  void square(float freq_hz);
  void saw(float freq_hz);
};

動作(出力波形)

では、実際にanalogWaveクラスを使ったコード(サンプルコードそのもの)を動かして波形を見てみる。

1kHz

最初は1kHz。可変抵抗を回して1000Hzになるように(シリアルコンソールに表示される)。しかし、これが難しい。細かな変化をさせにくい。なので、約1000。

DAC出力そのままでLPFは入れていないので階段がそのまま見える。階段状の一つ(カーソル間)が25kHzということは、サンプリングが25kHzということかな?

CDもDACにLPFを付けないとこういう事になっているんだろう。

周波数を変化させる

あのコードだと、出力周波数は0Hz~10kHzの範囲。

しかし、実際に設定できた最低周波数は120Hzくらい。シリアルに表示された値もそれくらいだったので、ADコンバータからの出力(をmapしたもの)がそうなのだろう。ここではその追求はしないでおく。

サンプリング周波数は3kHzくらいになっている。サンプリング周波数を変えることで出力周波数を勝ているようだ。

続いて最高周波数。約10kHz。オシロでの表示では15.1kHzとなっているが、これは波形が揺らいでおり、たまたまその周波数と判定したため。250kHzサンプリングで、その階段が一周期中に24個ほどあるので10kHzくらい(15kHzではないことがわかる)。

周波数変化は離散的

可変抵抗を回していて気づいたのが、周波数が飛び飛びに変化すること。シリアルコンソールには連続的に数字が並んでいくが、波形が変化するのは飛び飛び。

500Hz付近

502Hz(カーソル間で判断)の次は507Hz。

1kHz付近

シリアルコンソールでは約1000Hzの表示だったが、オシロ上は1.01kHz。可変抵抗を回してもしばらく変化せず、次の周波数は1.04kHz、その次は1.06kHzだった。

2kHz付近

シリアルコンソール上の周波数表示、すなわち、freqの設定値は2000なのだけど、観測される周波数は2.19kHz。設定値と実際の出力周波数の乖離が大きくなってきた。

周波数の飛びも大きくなり、次は2.31kHz、その次も見たつもりだったけど波形を保存し忘れたようだ。

5kHz付近

設定値との乖離はますます広がり、5000の設定で5.23kHz。

周波数の飛びも益々大きく、次は5.95kHz、その次は6.99kHz。ここまで粗いと使い道が限られそう。

まとめなど

analogWaveクラスを使うとDACを使ったアナログ波形を容易に出力できる。しかしながら、周波数の変化量が連続的ではなく離散的なことと、設定値(周波数)と出力周波数が一致しないのが問題。この問題は周波数が高いほど顕著。

この問題がArduino UNO R4 Minima(のチップ)によるものなのか、analogWaveクラスによるもの(つまり、ソフト的なもの)なのかは未調査。

なお、測定の際には、可変抵抗での設定をしやすくするために設定範囲を都度変えた。例えば、5kHz付近のときは次のように。

  freq = map(analogRead(A5), 0, 1023, 4900, 6500);

最初は4900~5500にマップしたのだけど、これだと一段階しか変化してくれなかったので上限を6500にした。6500までしか設定できないのに、出力されたのは6.99kHzだったのは上の説明のとおり。

コメント