直感的にDDSで使う波形テーブルはビット数と要素数が大きいほどきれいな波形が出力できそうな感じがします。数学的な検証はわたくしにはムリなのでWaveSpectraで出力波形の歪を測定しました。
テストスケッチ <MCP4922_DDS_WaveTableSize_Test.ino>
/*
Arduino LFO
DDSの波形テーブルの検証
2018.01.26
*/
#include <SPI.h>
#include "avr/pgmspace.h"
#include "wavetable_12bit_8k.h"
#define PIN_CHECK (0)
#define BIT_LENGTH_8 (0)
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
// Pin Assign
const int MCP4922Ldac = 9;
const int MCP4922Cs = 10;
#if (PIN_CHECK)
const int CheckPin1 = 18; // A4
const int CheckPin2 = 19; // A5
#endif
// MCP4922
SPISettings MCP4922_SPISetting(8000000, MSBFIRST, SPI_MODE0);
// Parameter
double drate = 50.0; // initial output rate (Hz)
const double refclk = 15625.0; // = 16MHz / 8 / 128
// DDS
volatile uint32_t phaccu;
volatile uint32_t tword_m;
//-------------------------------------------------------------------------------------------------
// Interrupt Service Routine
//
// param
// channel: 0, 1
// val: 0 .. 4095
void MCP4922Write(bool channel, uint16_t val)
{
uint16_t cmd = channel << 15 | 0x3000;
cmd |= (val & 0x0fff);
digitalWrite(MCP4922Ldac, HIGH);
digitalWrite(MCP4922Cs, LOW);
SPI.transfer(highByte(cmd));
SPI.transfer(lowByte(cmd));
digitalWrite(MCP4922Cs, HIGH);
digitalWrite(MCP4922Ldac, LOW);
}
ISR(TIMER2_OVF_vect)
{
#if (PIN_CHECK)
digitalWrite(CheckPin1, HIGH);
#endif
// synthesize
phaccu = phaccu + tword_m;
// テーブルサイズに合わせてシフトするビットを変更
int idx = phaccu >> 19; // use upper n bits (table size)
#if (BIT_LENGTH_8)
MCP4922Write(0, pgm_read_word_near(sin_table + idx) << 4);
#else
MCP4922Write(0, pgm_read_word_near(sin_table + idx));
#endif
#if (PIN_CHECK)
digitalWrite(CheckPin1, LOW);
#endif
}
//-------------------------------------------------------------------------------------------------
// Setup
//
// TIMER2 setup
void Setup_timer2()
{
// non-PWM / Normal port operation, OC0A disconnected.
cbi (TCCR2A, COM2A0);
cbi (TCCR2A, COM2A1);
// Mode 7 / Fast PWM
sbi (TCCR2A, WGM20);
sbi (TCCR2A, WGM21);
sbi (TCCR2B, WGM22);
// 16000000 / 8 / 128 = 15625 Hz clock
OCR2A = 127;
// Timer2 Clock Prescaler to : 8
cbi (TCCR2B, CS20);
sbi (TCCR2B, CS21);
cbi (TCCR2B, CS22);
}
void setup()
{
tword_m = pow(2, 32) * drate / refclk; // calculate DDS tuning word;
#if PIN_CHECK
pinMode(CheckPin1, OUTPUT);
#endif
pinMode(MCP4922Cs, OUTPUT);
digitalWrite(MCP4922Cs, HIGH); // set CS as inactive
pinMode(MCP4922Ldac, OUTPUT);
SPI.begin();
SPI.beginTransaction(MCP4922_SPISetting);
Setup_timer2();
// disable interrupts to avoid timing distortion
cbi(TIMSK0, TOIE0); // disable Timer0 !!! delay() is now not available
sbi(TIMSK2, TOIE2); // enable Timer2 Interrupt
sei();
}
//-------------------------------------------------------------------------------------------------
// Main Loop
//
void loop()
{
}
Github:
https://github.com/ryood/ArduinoLFO/tree/e2cc9572cc76aa3309c5c6d5e4b51de9a17d279d/Arduino/MCP4922_DDS_WaveTableSize_Test
製作中のArduino LFOのスケッチから不要な部分を削除しました。
テーブルは、
ぴゅんぴゅん2号で使っていた8bit✕256と、MCP4922のビット長12bitにして要素数が1024、2048、4096、8192にしたものを比較しました。
DDSはフェーズアキュムレータの上位bitをテーブルのインデックスとして使うので、テーブルの要素数は2^nである必要があります。
Arduino Unoのフラッシュメモリのサイズが32kBで(その一部はプログラムで使用)、int16_t型(2Byte)のテーブルなので、8192以上はメモリーオーバーします。
測定時にはUSB経由のノイズを回避するためにArduinoは電池電源(単3✕6)を使用しました。
Audio I/F: TASCAM US-144 MKII MIC/Line入力
窓関数: FlatTop
Avg: 100
8bit長 256要素
12bit長 1024要素
12bit長 2048要素
12bit長 4096要素
12bit長 8192要素
テーブルが8bit長のときと、12bit長のときでははっきりと歪が改善されます。また、テーブルの要素数が増えると高次の歪が減ります。
聴感で敏感な1kHz~10kHzあたりを比較すると、やはりテーブルの要素数は大きければ多いほどよさそうです。8192になると、サンプリング周波数の15kHz付近のエイリアスが支配的になってきます。
サイン波の傾きが大きい原点あたりのテーブルの値を比較すると、
12bit長 1024要素
// table of 2048 values / one period / stored in flash memory/*** MAX_VALUE = 4096 SAMPLE_NUM = 1024 delta = 3.999023***/
/*** sine wave ***/
const PROGMEM uint16_t sin_table[] = {
2047 ,
2060 ,
2072 ,
2085 ,
2097 ,
2110 ,
2122 ,
2135 ,
2147 ,
2160 ,
12bit長 8192要素
// table of 8192 values / one period / stored in flash memory
const PROGMEM uint16_t sin_table[] = {
2047,
2049,
2050,
2052,
2053,
2055,
2056,
2058,
2060,
2061,
2063,
1024要素の場合は値がトビトビで、サンプリング・ポイントでの誤差が歪となって現れる結果だと思います。
ちなみに、12bit長8192要素のテーブルでも1kHzのサイン波を出力すると以下のような波形になります。
随分ガタガタしてますが、12bit@15.625kHzなのでしかたありません(^q^; Arduino Uno+MCP4922で出せる波形もこのあたりが上限だと思います。
処理時間
前回のUIを付けたスケッチで処理時間を測定しました。テーブルは12bit長、2048要素です。UART_TRACEは無効にしています。
Arduinoのスケッチ <MCP4922_LFO.ino>
Github:
https://github.com/ryood/ArduinoLFO/tree/e2cc9572cc76aa3309c5c6d5e4b51de9a17d279d/Arduino/MCP4922_LFO
割り込みとSPI処理
ch1:LDAC ch2:A4
SPI通信にかかっている時間は20usで同じですが、割り込み処理時間は38.4us→44usと増えています。増やした処理は、配列をint16_tにしたことと、switch文で出力する波形を切り替えているぐらいですが、もはやカツカツな感じです。
割り込みとメインループ
ch1:A5 ch2:A4
analogRead()でPOT2個の値を読み取るようにしただけですが、loop()内の処理時間(ch1:A5がHの時間)が増えています。
SPI通信
ch1:MOSI ch2:SCK
メモ:
ノコギリ波はテーブル参照しなくても、位相値(phaccu)の値を見ればできそう?上昇下降は足し算引き算で?
三角波もできそう?(計算量によりそうですが)
矩形波も位相値でH/Lを切り替え?
処理時間次第ですがノイズ(S&H)もできるかも?