製作モジュール一覧

ラベル DDS の投稿を表示しています。 すべての投稿を表示
ラベル DDS の投稿を表示しています。 すべての投稿を表示

2018年1月29日月曜日

Arduino LFO DDSのテーブルサイズの影響&処理時間の計測

直感的に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)もできるかも?

2018年1月26日金曜日

Arduino LFO ブレッドボードで実験

UIをつけて、スケッチを書いてみました。

ブレッドボード配線図


Arduinoのスケッチ <MCP4922_LFO.ino>

https://github.com/ryood/ArduinoLFO/tree/de0d2a7960f098d09f66701f78884aede1355075/Arduino/MCP4922_LFO

外部割り込みの使用


波形選択用のタクトスイッチの読み取りは外部割り込み(INT0)を利用しました。

attachInterrupt(digitalPinToInterrupt(ButtonWaveShape), waveshape_pushed, FALLING);

loop()内のポーリングでdigitalRead()を使って読み取ると、スイッチの押し下げ→開放状態をそれぞれ読み取って比較する必要がありますが、外部割り込みを使うと立ち上がり、または立ち下がりを補足できるので便利です。

デメリットはArduino Unoの場合、外部割り込みに使えるピンがD2とD3の2本に限られていることです。MCP4922とのSPI通信でD9~D13を使っていて、D0とD1はPCとのシリアル通信のために開けておいた方がいいので、その他に使えるピンがなかなか制限されてきます。

また、波形生成に使っているTimer2よりも割り込みの優先順位が高いので気をつけておく必要があります。

ATMega328PのDATASHEETに割り込みベクタテーブルがのっていますが、割り込みベクター番号が若いほうが優先順位が高くなります。


INT0の割り込み処理を長々とやっているとTimer2の割り込みのタイミングが遅れてしまって、波形に揺れが生じる可能性があります。

チャタリング対策


外部割り込みをそのまま読み取るとチャタリングが発生したので対策しました。外部割り込み時に、読み取り状態を確定させるまでの時間(waveshape_pushed_wait)をセットして、Timer2の割り込み時に設定時間経過後、再度押し下げ状態を読み取って変化がなければ確定するようにしています。

単純に一定時間INT0を無視するようにしても、ある程度効果はありそうですが(^q^?

A/Dコンバーターの読み取り


LFOのRateとパルス幅をPOTで設定してADCで読み取る様にしましたが(analogRead())、無事処理できているようです。

ADC読み取りはloop()内でポーリングしていて、上記割り込みベクタテーブルを見ると、ADCは22番で優先順位が低く、Timer2の割り込みのタイミングには影響しないと思います。

※Arduinoのソースを見ると、ADCの完了待ちはADCSRAレジスタを監視していて、割り込みは使用していないようです。

C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\wiring_analog.c
// start the conversion
sbi(ADCSRA, ADSC);
// ADSC is cleared when the conversion finishes
while (bit_is_set(ADCSRA, ADSC));

LEDの点灯はPORTで処理


複数のLEDの点灯はdigitalOut()で処理するとやたら煩雑です。ArduinoのD0~D7の実体はATMega328PのPORTDなので直接レジスタ操作するようにしました。

  // Write to LEDs (D3~D7)
  byte portd_bits = (1 << (waveshape_sel + 3)) | (PORTD & 0x07);
  PORTD = portd_bits;

しかし毎度のことながらビット演算はややこしくて混乱してしまいます。Serial.print()で状態を確認してプログラミングしました(^q^;;;

次回は動作状態や、DDSの波形テーブルのサイズが与える影響を調べてみたいと思います。

メモ:


矩形波のパルス幅は未実装

2018年1月21日日曜日

Arduino LFOの構想

KIK01ベースマシンのパラメータ設定用のPOTのかわりにLFOやエンベロープ・ジェネレーターをつないで変調することを考えてみました。

アナログLFOもおもしろそうですが、ぴゅんぴゅん2号をベースに、Arduinoでやってみることにしました。

仕様
波形: サイン波、三角波、ノコギリ波(上昇)、ノコギリ波(下降)、矩形波(パルス幅設定)
周波数: 0Hz~100Hz程度
振幅: 3.3Vp-p(単電源波形)

KIK01やベースマシンのPOT入力用のADCは3.3V系なので、0V~3.3Vの単電源波形とします。←できれば振幅を調節できるようにする。

受け側のADCは10~12bit精度、サンプリング周期1kHz程度ですが、アナログ回路でも使えるようにできるだけきれいな波形を出力することを目標とします。

今回はArduinoしばりをかけます(^q^;

ブロック図

波形選択はタクトスイッチ1個の順送りで行い、LEDで選択中の波形を表示します。LFOのRateはPOTで設定、矩形波のパルス幅もPOTで設定。

出力はMCP4922を使い、デジタル歪を除去するためにLPFをかけます。

Arduinoのテスト・スケッチ


Github:
https://github.com/ryood/ArduinoLFO/tree/a904798e7fd8e1f79e818c88adfa5c5905da3cc4

波形生成はDDSで行いました。DDSは波形の周期性が出たりデメリットもあるのですが、少ない演算量の整数演算で済むのでArduinoで任意波形を出すならこれ一択だと思います。

テスト用ブレッドボード配線図


出力波形


100Hzのサイン波を出力して測定しました。


拡大

相当ガタガタしています。ガタガタの幅は60us程度で、サンプリングレート15625Hz(64us)と合致します。

高さは40mV程度。 3.3V / 40mV = 82.5 step でかなり荒くなっています。

FFT


WaveSpectraでFFTしました。(窓関数:FlatTop)


上側のウィンドウの波形でもガタガタが確認できます。スペクトラムを見ると100Hzの整数倍の高調波歪は大きくないですが、その他の歪が猛烈に出ています。サンプリングレートの15kHz付近にピークが出ています。

処理時間


Pinの出力のH/Lで処理時間を計測しました。

割り込みとSPI処理

ch1:LDAC ch2:A4

ch1:LDACはMCP4922とのSPI通信の最初と最後でH/Lして、20usかかっています。ch2:A4はサンプリング周期ごとのTimer2の割り込みの最初と最後でH/Lしていて38.4usかかっています。

結構ギリギリですね(^q^;

割り込みとメインループ

ch1:A5 ch2:A4

ch1:A5はメインループのloop()の最初と最後でH/Lしています。現状ではパラメータ読み取り用のanalogRead()を無効化しています。Timer2の割り込み中はloop()内の処理は引き伸ばされているようです。

割り込み処理の時間がかかりすぎると、loop()に処理が戻ってこず、パラメータ読み取りが出来ない可能性があります。

SPI通信



ch1:MOSI ch2:SCK

SPIクロック(SCK)は、スケッチで指定している通り8MHzになっています。MCP4922は20MHzまでなので多少波形が汚くても大丈夫だと思います。←憶測(^q^;

メモ:


ぴゅんぴゅん2号の波形テーブルは、8bit長、要素数256にしている。これをMCP4922の12bit長にし、要素数も増やして出力波形が改善されるか見てみる。

ノコギリ波、三角波はDDSではなく線形補間でやってみる?

矩形波は一番簡単なH/Lで(^q^?

ノイズ(S&H)もできそう?

アナログLPFで必要な帯域外を削るにしても、かけすぎるとサイン波以外は波形がなまってしまってよくない。

割り込みの周期が揺れていないかオシロのトリガ(パルス幅)で確認できる?

2015年10月11日日曜日

固定小数点演算でDecay波形を作る

テーブル参照で波形は作れた(^q^/


固定小数点型の問題点

前Visual Studioで作ったリズム・マシンのコード(浮動小数点演算)をそのまま固定小数点演算にしようと思ったが、固定小数点演算を使うと問題が発生した。

固定小数点型というのは、32bitなら32bitのうち上位数桁を整数部、残りの下位を小数部として扱う方式だ。

32bitでQ8.24だと上位8bitを整数部として扱い、下位24bitを小数部として扱う。整数部が8bitだと最大で-128 .. +127までしか扱えない。

浮動小数点のつもりで、でかい整数を固定小数点型にキャストするととんでもないことになるし、実際そうなった。

整数演算で線形補完するアルゴリズムもどっかにあった気がするが、調べたりするのがめんどくさいのでDecay波形もテーブル参照でやることにした。

テーブル参照ならテーブルを作ってやれば線形補完だけではなく、疑似的にいろんな波形を使えるかも?という目論見もある。

Decay波形のテーブル


PSoC4 のFlash ROMは意外とサイズが制限されているので、128個のテーブルにした。演算は32bitの固定小数点でやるので固定小数点の中の値(32bit整数)をそのままテーブルにした。

波形テーブル
https://github.com/ryood/DDS_RhythmMachine_Test/blob/fixedpoint/DDS_Decay_Test/ModTableFp32.h


プログラム・コード

浮動小数点演算で結果を見ながら整数演算にに変換していった。

 // DDS_Decay_Test.cpp : Defines the entry point for the console application.  
 //  
 // Decay波形生成テスト  
 //  
 // 2015.10.11 Decayの長さに合わせてDecayの再生周波数に重み付け  
 // 2015.10.11 Created decayのindexの増加はperiodの終了で中断する  
 //  
 #include "stdafx.h"  
 #include <stdio.h>  
 #include <stdint.h>  
 #include <io.h>  
 #include <fcntl.h>  
 #include "fixedpoint.h"  
 #include "ModTableFp32.h"  
 #define SAMPLE_CLOCK     (48000u)  
 //#define MOD_LOOKUP_TABLE_SIZE (128u)  
 #define POW_2_32                (4294967296ull) // 2の32乗 (64bit整数)  
 // カウンター  
 int tick = -1;                    // 初回に0にインクリメント  
 int noteCount = 0;  
 // BPM  
 uint8_t bpm = 120;               // 1分あたりのbeat数 (beat=note*4)  
 int32_t ticksPerNote;          // noteあたりのサンプリング数  
 int period = 24000;  
 // Parameter  
 const fp32 *decayLookupTable;  
 uint8_t decayAmount = 127;  
 uint32_t decayPhaseRegister;  
 uint32_t decayTuningWord;  
 uint32_t decayPeriod;  
 uint8_t decayStop;  
 fp32 decayValue;  
 int _tmain(int argc, _TCHAR* argv[])  
 {  
      _setmode(_fileno(stdout), _O_BINARY);  
      decayLookupTable = modTableDown;  
      // BPMの計算  
      //  
      printf("bpm:\t%d\n", bpm);  
      ticksPerNote = SAMPLE_CLOCK * 60ul / (bpm * 4);  
      // ↑整数演算のため丸めているので注意  
      printf("ticksPerNote:\t%d\n", ticksPerNote);  
      // DDS変数の初期化------------------------------------------------------------------------  
      //  
      // 浮動小数点演算  
      //decayPeriod = (SAMPLE_CLOCK / (((double)bpm / 60) * 4)) * ((double)decayAmount / 256);  
      // 整数演算(64bit)  
      decayPeriod = ((uint64_t)SAMPLE_CLOCK * 60 * decayAmount) / ((uint64_t)bpm * 4 * 256);  
      // decay波形の周期は1note分  
      // 浮動小数点演算  
      //decayTuningWord = (((double)bpm / 60) * 4) * (uint64_t)POW_2_32 / SAMPLE_CLOCK;  
      // 整数演算(64bit)  
      //decayTuningWord = bpm * ((uint64_t)POW_2_32 / 60) * 4 / SAMPLE_CLOCK;  
      // decay波形の周期にdecayAmountで重み付け  
      // 浮動小数点演算  
      //decayTuningWord = ((((double)bpm / 60) * 4) / ((double)decayAmount / 256)) * (double)POW_2_32 / SAMPLE_CLOCK;  
      // 整数演算(64bit)  
      decayTuningWord = (bpm * ((uint64_t)POW_2_32 / 60) * 4 * 256 / decayAmount) / SAMPLE_CLOCK;  
      printf("decayAmount:\t%u\n", decayAmount);  
      printf("decayPeriod:\t%u\n", decayPeriod);  
      printf("decayTunigWord:\t%u\n", decayTuningWord);  
      decayPhaseRegister = 0;  
      decayStop = 0;  
      for (int i = 0; i < period; i++) {  
           tick++;  
           if (tick >= ticksPerNote) {  
                noteCount++;  
                //printf("%d\t%d\n", tick, noteCount);  
                // noteの先頭でtickをリセット  
                tick = 0;  
                // noteの先頭でdecay波形生成の再開  
                decayPhaseRegister = 0;  
                decayStop = 0;  
           }  
           printf("%d\t%d\t", noteCount, tick);  
           // DDS  
           // decayPeriodでdecay波形の生成を終了  
           if (!decayStop) {  
                decayPhaseRegister += decayTuningWord;  
           }  
           if (tick == decayPeriod - 1) {  
                decayStop = 1;  
           }  
           // 32bitのphaseRegisterをテーブルの7bit(128個)に丸める  
           int decayIndex = decayPhaseRegister >> 25;  
           printf("%d\t%d\t", decayPhaseRegister, decayIndex);  
           decayValue = *(decayLookupTable + decayIndex);  
           printf("%f\n", fp32_to_double(decayValue));  
      }  
      return 0;  
 }  

32bitの固定小数点型で0 .. +1.0の範囲で波形を作れたので、これと元波形を乗算すれば振幅変調できるかな?

バグはかなりありそうだが。

メモ:

snareやhihatで使うつもりのノイズ波形はどうすればいい? random()関数で生成してDecay波形と乗算すればいいかな?→random()関数の出力を固定小数点の内部値にそのままつっこむ?

2015年10月8日木曜日

固定小数点の四則演算のテスト

波形に振幅変調をかけようと思うと小数点演算が必要になりそうなので、Visual Studioで固定小数点演算のテストをしてみた。「音遊び!BlackfinDSP基板でディジタル信号処理初体験 2015年 04月号[雑誌]インターフェース増刊」の記事を参考にした。Amazonではまた転売価格になってますが、マルツにはまだ在庫ありそうです。

<fixedpoint.h>
 #ifndef _FIXEDPOINT_H_  
 #define _FIXEDPOINT_H_  
 #include <stdint.h>  
 typedef int32_t fp32;  
 typedef int64_t fp64;  
 #define FIXQ                    (24u)  
 #define double_to_fp32(x)     ((fp32)((1u<<FIXQ)*((double)x)))  
 #define fp32_to_double(x)     (((double)(x))/(1u<<FIXQ))  
 // 四則演算  
 // オーバーフローに注意  
 // 特に除算の除数  
 #define fp32_add(x,y)          ((x)+(y))       
 #define fp32_sub(x,y)          ((x)-(y))  
 #define fp32_mul(x,y)          ((fp32)(((fp64)(x)*(fp64)(y))>>FIXQ))  
 #define fp32_div(x,y)          ((fp32)(((fp64)(x)<<FIXQ)/(y)))  
 #endif //_FIXEDPOINT_H_  

PSoCでは関数呼び出しのコストが高そうなので、マクロ関数で定義してみた。

テストプログラム
 #include "stdafx.h"  
 #include <stdio.h>  
 #include <stdlib.h>  
 #include "fixedpoint.h"  
 #define CALC_DIV     1  
 #define CALC_REPEAT     0xffff     // Excelの最大行  
 #define     FP32_MAX     (1.0f)  
 double dbl_rand()  
 {  
      double dr = (double)rand() / RAND_MAX;  
      return (dr - 0.5) * FP32_MAX;  
 }  
 void calc(double dx, double dy)  
 {  
      fp32 fpx = double_to_fp32(dx);  
      fp32 fpy = double_to_fp32(dy);  
      double x_v = fp32_to_double(fpx);  
      double y_v = fp32_to_double(fpy);  
      double add_v = fp32_to_double(fp32_add(fpx, fpy));  
      double sub_v = fp32_to_double(fp32_sub(fpx, fpy));  
      double mul_v = fp32_to_double(fp32_mul(fpx, fpy));  
 #if CALC_DIV  
      double div_v = fp32_to_double(fp32_div(fpx, fpy));  
 #endif  
      printf("x\t%lf\t%lf\t%lf\n", dx, x_v, dx - x_v);  
      printf("y\t%lf\t%lf\t%lf\n", dy, y_v, dy - y_v);  
      printf("x+y\t%lf\t%lf\t%lf\n", dx + dy, add_v, (dx + dy) - add_v);  
      printf("x-y\t%lf\t%lf\t%lf\n", dx - dy, sub_v, (dx - dy) - sub_v);  
      printf("x*y\t%lf\t%lf\t%lf\n", dx * dy, mul_v, (dx * dy) - mul_v);  
 #if CALC_DIV  
      printf("x/y\t%lf\t%lf\t%lf\n", dx / dy, div_v, (dx / dy) - div_v);  
 #endif  
 }  
 int _tmain(int argc, _TCHAR* argv[])  
 {  
      printf("expr\tdouble\tfp32\terr\n");  
      calc(1.0f, -1.0f);  
      for (int i = 0; i < CALC_REPEAT; i++) {  
           double dx = dbl_rand();  
           double dy = dbl_rand();  
           // 除算エラー回避  
           if (dy == 0.0f)  
                continue;  
           calc(dx, dy);  
      }  
      return 0;  
 }  

Q8.24の固定小数点と浮動小数点で、-1.0~1.0の範囲で乱数出力して誤差を出力した。

除算あり

E列、F列はExcelの関数で誤差の最大値と最小値を抽出した。かなりごつい誤差がでているが、これは除算の時に除数が小さい時に発生する。

除算なし

プログラムを修正して除算を省いてみると誤差はなくなった。ただし出力桁数が少ないのでもっと細かい誤差はあると思うが、DDSで使うならこれぐらいの精度で十分だと思う。

Visual Studioのプロジェクト
https://github.com/ryood/FixedPoint_Test

2015年2月7日土曜日

PSoC 4 Pionner Kit 外付けDACでサイン波生成

DDSを使ってサイン波を生成してMCP4922で出力してみた

PSoC Creator TopDesign


使うのはSPIコンポーネントとTimer(TCPWM)コンポーネントだ

48kHzのサンプリングレートなのでSPIのクロックはできるだけ上げたかったが
1,500KHzまでしか設定できなかった

<追記>今気づいたが、Oversamplingの値を下げればData rateを1,500Hz以上にあげられるようです。

→Acutual data rageのところ

1,500kHz / 48kHz = 31.25なので

1サンプル送るのに16bit(最低16クロック)必要なので割とぎりぎりだ(^q^;

<main.c>
 #include <project.h>  
 #include <math.h>  
 #include "wavetable.h"  
 #define SAMPLE_CLOCK (48000.0f)  
 volatile uint32 phaseRegister;  
 volatile uint32 tuningWord;  
 void DACSetVoltage(uint16 value)  
 {  
   LDAC_PIN_Write(1u);  
   SPIM_SpiUartWriteTxData((value >> 8) | 0x30);  
   SPIM_SpiUartWriteTxData(value & 0xff);  
   LDAC_PIN_Write(0u);  
   while(0u == (SPIM_GetMasterInterruptSource() & SPIM_INTR_MASTER_SPI_DONE))  
   {  
     /* Wait while Master completes transfer */  
   }  
   /* Clear interrupt source after transfer completion */  
   SPIM_ClearMasterInterruptSource(SPIM_INTR_MASTER_SPI_DONE);  
 }  
 CY_ISR(InterruptHandler)  
 {  
   // Caluclate Wave Value  
   phaseRegister += tuningWord;  
   // 32bitのphaseRegisterをテーブルの10bit(1024個)に丸める  
   uint32 index = phaseRegister >> 22;  
   uint16 waveValue = waveTableSine[index];  
   DACSetVoltage(waveValue);  
   Sampling_Timer_ClearInterrupt(Sampling_Timer_INTR_MASK_TC);  
 }  
 int main()  
 {  
   /* Place your initialization/startup code here (e.g. MyInst_Start()) */  
   // 変数の初期化  
   double waveFrequency = 1000.0f;  
   tuningWord = waveFrequency * pow(2.0, 32) / SAMPLE_CLOCK;  
   phaseRegister = 0;  
   // コンポーネントの初期化  
   Sampling_Timer_Start();   
   Timer_ISR_StartEx(InterruptHandler);  
   SPIM_Start();  
   CyGlobalIntEnable;  
   for(;;)  
   {  
     /* Place your application code here. */  
   }  
 }  

まずは1kHzの正弦波を出力してみた

wavetable.hにDDS用のWaveTableを記述しているが、普通に
uint16 waveTableSine[] = {
...
};
とするとメモリが足りないみたいなエラーメッセージが出る
const uint16 waveTableSine[] = {
...
};
とconst指定してやればflashメモリーに格納されるようです。

flashメモリにプログラムで書き込むにはどうすればいいんだろう?
書き込めないのかな?

ブレッドボードの配線図

<追記: 2015.11.22>

ピン接続表



</追記>

オシロで出力とSPIのクロックの波形を見てみた


上が出力波形、下がSPIのクロック

出力波形は左下の表示値の(1)F:994.0Hzでほぼ1kHz

波形を拡大するとかなり汚い(@@


クロックの波形がそのまま乗っている感じだ

クロックの波形を拡大すると


左下の表示値の(2)F:1.497MHzなのでSPIのクロックは設定通りだいたい1,500kHzだ

オーディオ・インターフェイス経由で取り込んでWaveSpectraでFFTしてみると
やはりかなり歪んでいる(^q^;


使うかどうかわからないがLPFを入れてどれぐらい改善されるかやってみるつもりです。

PSoC 5LPにはデジタル・フィルタのコンポーネントがあるが、PSoC 4にはない(^q^;
これは結構痛いなぁ

とりあえず外付でCRかオペアンプを使ってやってみようと思います

今回のプロジェクトはGithubで公開しています
https://github.com/ryood/PSoC-DDS-MCP4922

2014年9月1日月曜日

ぴゅんぴゅん2号のファームウェアを更新した

DDSの位相変調の実験をしていたのでぴゅんぴゅん2号のファームウェアに反映させた

https://github.com/ryood/ArduinoPyunPyun/tree/master/Arduino/PyunPyunNo2

パソコンを新調して
波形を録画するのと同時に録音する方法がわからなくなったので今回は音無しだ


最初に基本のサイン波を出力して周波数をスイープ

サイン波→三角波→ノコギリ波(上昇)→ノコギリ波(下降)→矩形波と
基本波形を変更

LFOの位相変調でまた同じように基本波形を揺らしている


2つほどわからないことがあった

ソースのコメントに書いてあるが、LFOの演算を分周しているところで比較対象が
浮動小数点少数ならちゃんと動くんだが、整数にするとダメっぽい

SAMPLE_CLOCK / LFO_CLOCK

は、#defineの定義にしたがって

5.0

になるので、即値で「5.0」にするとOK

なのに「5」だとアウト

全く動かないわけではなくてブチブチノイズが入る

なんなんだこれは?

シリアル通信とかLCDへの出力はそれなりにwaitがかかるので
Timer割り込み中のprintfデバッグはしにくい

いずれまじめにデバッグすることにする(予定)


もう一つは位相変調の係数だ

前回は変調の値をマイナスにするとNGっぽいと書いたが(http://dad8893.blogspot.jp/2014/07/dds.html)
マイナスにふられてる値でもなんとなく大丈夫そうだ

理屈としては時間をさかのぼることになるが
逆に面白いノイズとなっている気がする

これもちゃんと調べないといかんな



2014年7月21日月曜日

DDSの位相変調

Mixerの足りない部品が届くまで他のことをやることにした。

DDSの変調の効き具合がどうも弱いので考えた。

もうARMベースで32bit処理しようかと思ったが
まだ粘れそうなのでネチネチがんばった

課題としては

phaseRegister += tuningWord + lfoValue;

ここが負の値になると破綻する。

なので、tuningWordの値を大きくするためにSampling Rateを
Arduinoの割り込み処理でまあまあ現実的な15,625Hzに変更。

また変調の値(=lfoValue)を正負ではなくて
正の値にだけできるだけ大きく振れるように変更した。



https://github.com/ryood/DDSTest.git

https://github.com/ryood/DDSTest/blob/LFO%E3%82%92%E6%AD%A3%E3%81%AE%E5%80%A4%E3%81%AB/DDSTest-LFO/DDSTest-LFO.cpp
(2014.08.31追記)

みごとなクソ波形だ(^q^

もしかしたらコミケまでにピュンピュン2号のファームウェアを更新できるかもしれない

2014年6月15日日曜日

DDSでLFOを検証

とりあえず、Visual Cで検証用のコードを書いてみた





周波数変調と位相変調


VCOにかけるLFOの役目は、LFOの出力電圧でVCOの発振周波数を揺らしてやることだ
これをDDSでまじめにやるとすると

初期処理: 
LFOの発振周波数からTuning Wordを計算(浮動小数点演算) 
ループ: 
LFOのTuning WordをPhase Accumulatorに加算
Phase Accumulatorの値を元にテーブルを参照してLFOの出力値を取得
VCOの発振周波数とLFOの出力値を乗算(浮動小数点演算)
得られた周波数からTuning Wordを計算(浮動小数点演算)
        Phase Accumulatorの値を元にテーブルを参照してVCOの出力値を取得
ループ終わり:

と、演算量が少ないというDDSの利点がまったく生かせない事態になる

周波数変調と位相変調は微積の関係なので、位相変調で処理することにした
具体的にはTunig WordにLFOの出力値を加算している

数学的な検証はめんどくさいのでやってないが、DDSでの変調はこれが普通だと思う


LFOの最低周波数


DDSの出力周波数は

  f_out = ( M / 2 ^ n ) * f_smp
  
で求まる。(M: Tuning Word, n: Phase Accumulatorのビット長, f_smp: サンプリング周波数, f_out: 出力周波数)

( M / 2 ^ n )が0以下の場合の場合発振しない。しかも整数なので、M = 1 として
f_smp = 44100, n = 16, の場合を考えると

  f_out = ( 1 / 2^16 ) * 44100 = 0.673[Hz]
  
となる。 また、この1つ上の周波数は M = 2, 1.346[Hz] となりLFOとしてはよろしくない

なので、LFOのサンプリング周波数は波形生成の整数分の1で処理することにした


今後の予定


LFOの効き方をもっと良くするために係数を調整する必要がある。
が、オーバーフローとかビット数とかの計算がめんどさい

気が向いたらやることにして、基本的なエンジン部分はだいたいこんなもんでいいかと思う


シンセなら次はVCAやEGに手を付けるか迷うとこだが、ピュンピュンマシンなので
音色のキモになるフィルタに手を付けたい。

VCFやデジタルフィルタは理論的なことが難しすぎるので
可変抵抗でパラメータをコントロールするタダのアナログフィルタでも面白いかも

2014年6月3日火曜日

DDSのLookup Tableのbit長

前回はPhase Accumulatorのbit長の検証をした。

考えてみるとLookup Tableのbit長やサイズは8bit,16bit,32bitといった
キリの良い値に制限しなくてもよさそうだ。

値段が手頃なDACはだいたい12bit程度なのでLookup Tableは16bitのうち
12bitを使用することにする。

Arduino UnoのFlash Memoryのサイズは32KBなので



DDSのLookup Tableのサイズ 16bit長で取れる個数
512 31.25
1024 15.625
2048 7.8125

波形は、サイン波、ノコギリ波(上昇)、ノコギリ波(下降)、矩形波、三角波の
最低5種類として1024個ぐらいでよさそう

2Byte * 5 * 1024 = 10KB

PSocやARMでもまあまあこれぐらいが現実的かな?

で、Visual Cで波形を生成して比較してみた。

Accumulator: 16bit, Lookup Table: 8bit * 256

Accumulator: 32bit, Lookup Table: 8bit * 256

Accumulator: 16bit, Lookup Table: 12bit * 1024

Accumulator: 32bit, Lookup Table: 12bit * 1024

THDの値を比較するとAccumulatorを32bitにするよりもLookup Tableを
12bit * 1024に拡張したほうが良さそうだ。


2014年5月27日火曜日

DDSのフェイズアキュムレーターのbit長の検証

ピュンピュン2号はArduinoでDDSで波形を生成している。


↓動画
https://www.youtube.com/watch?v=nUqrsvC5NPo

DDSというのはソフトシンセで言うとWave Table方式のオシレーターの一種だ(と思う)
詳しいことはNational Instrumentsのドキュメントがわかりやすかった
http://www.ni.com/white-paper/5516/ja/

ピュンピュン2号はAVRで作ったシンセを公開してくださってるサイトをいろいろ参考にして
ソースを切り貼りしながら試行錯誤してプログラムを組んだのでかなり行き当たりばったりだ。

先人に習ってDDSのフェイズアキュムレーターは32bitで実装した。

でも16bitと32bitで、どれぐらい違いがあるのか調べてみたくなった。

Arduinoは16bitのCPUで、16bitまでの演算は掛け算も2クロックぐらいで済む。
逆に言うと32bit演算とか浮動小数点演算は極端に重くなる。

Arduinoで組んでもいいんだが、別にリアルタイムでやる必要もないので
普通のCで検証してみた。

32bitアキュムレーター

// DDSTest.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <io.h>
#include <fcntl.h>

#define _USE_MATH_DEFINES
#include <math.h>

/*********************************************************
phaseRegister   : 32bit
tunigWord       : 32bit
lookupTable     :  8bit
**********************************************************/

#define SAMPLE_CLOCK 44100 // 44.1kHz
#define TABLE_SIZE    0x100   // Lookup Table Size

uint32_t phaseRegister;
uint32_t tuningWord;
int16_t waveValue;
uint8_t *lookupTable;

double frequency = 440.0;  // 440Hz
int period = 1323000;      // 10s

int _tmain(int argc, _TCHAR* argv[])
{
_setmode(_fileno(stdout), _O_BINARY);

// Lookup Table の作成
lookupTable = (uint8_t*)malloc(TABLE_SIZE);
for (int i = 0; i < TABLE_SIZE; i++) {
lookupTable[i] = (sin(2.0 * M_PI * i / TABLE_SIZE) / 2 + 0.5) * 0xFF;
}
tuningWord = frequency * pow(2.0, 32) / SAMPLE_CLOCK;

phaseRegister = 0x00000000; 
for (int i = 0; i < period; i++) {
int index;

phaseRegister += tuningWord;
index = phaseRegister >> 24;
waveValue = (lookupTable[index] << 8) - 0x8000;

// printf("%d\n", waveValue);

// 16bit長の raw データとして出力
fwrite(&waveValue, sizeof(waveValue), 1, stdout);
}

free(lookupTable);

return 0;
}


16bitアキュムレーター

// DDSTest.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <io.h>
#include <fcntl.h>

#define _USE_MATH_DEFINES
#include <math.h>

/*********************************************************
phaseRegister   : 16bit
tunigWord       : 16bit
lookupTable     :  8bit
**********************************************************/

#define SAMPLE_CLOCK 44100 // 44.1kHz
#define TABLE_SIZE    0x100   // Lookup Table Size

uint16_t phaseRegister;
uint16_t tuningWord;
int16_t waveValue;
uint8_t *lookupTable;

double frequency = 440.0;  // 440Hz
int period = 1323000;       // 30s

int _tmain(int argc, _TCHAR* argv[])
{
_setmode(_fileno(stdout), _O_BINARY);

// Lookup Table の作成
lookupTable = (uint8_t*)malloc(TABLE_SIZE);
for (int i = 0; i < TABLE_SIZE; i++) {
lookupTable[i] = (sin(2.0 * M_PI * i / TABLE_SIZE) / 2 + 0.5) * 0xFF;
}

tuningWord = frequency * pow(2.0, 16) / SAMPLE_CLOCK;

phaseRegister = 0x0000;
for (int i = 0; i < period; i++) {
int index;

phaseRegister += tuningWord;
index = phaseRegister >> 8;
waveValue = (lookupTable[index] << 8) - 0x8000;

//printf("%d\n", waveValue);

// 16bit長の raw データとして出力
fwrite(&waveValue, sizeof(waveValue), 1, stdout);
}

free(lookupTable);

return 0;
}


rawデータで出力して、SOX (http://sox.sourceforge.net/)でwavに変換して
WaveSpectra(http://www.ne.jp/asahi/fa/efu/soft/ws/ws.html)でFFTして比較した。

(16bit)

(32bit)


ほとんど差がない。

静止画ではわからないが、16bitの方は-80dB以下のノイズが振動している。

が、音源としてはどっちでも一緒な気がする。


音源は、正弦波ではなくてめちゃくちゃな波形を出力した時にどういう聞こえ方をするのかが
大事なんである。

ピュンピュン2号のArduinoでは処理速度が追いつかなくなったので
次は32bitのARMベースにしようかと思っていたが
16Bit処理に抑えられればまだまだいける気がします

一応、rawデータのバイナリを直接比較したが2,584KB中61バイト違うだけだった。