2015年10月30日金曜日

矩形波だけのファンクションジェネレーター シンプルな出力部の実験

矩形波だけのファンクションジェネレーター メモ 」で考えた通り

  • AVRからのPWM出力をレベル調整(減衰)
  • 物理的なスイッチで、GNDレベルを矩形波の中点に持ってくる

という方針で実験した。

回路図


TLE2426(三本足のタイプ)でGND - VCCを分圧するだけの回路だ。

AVRからのPWM出力も一旦受けてそのまま出力するふうにしたが、これは別にこの基板を通さなくてもいいのか?あとでまた考えてみよう。

ブレッドボード図


変な図になってるがTLE2426のIN/OUTの向きが、OUT/COMMON/INなので逆向きという意味。

Fritzingで作図しておいてブレッドボードで組むときすっかり忘れていてTLE2426を逆向きに刺してテスタで計ったら出力電圧が微妙におかしくなった。電流が流れまくったのかな?触ったら熱くなっていた。

電源は矩形波だけのファンクションジェネレーターの5Vからとった

Cをいろいろつけたが1000pFから47uFぐらいまで差し替えてみたが少しずつ変わる

特に変わったところ


1kΩの負荷で測定して

Cなし

C3にだけ10uFのケミコン

パスコンを入れると波形は直角的になる。が、ノイズは増えるみたいだ。

TLE2426の入力側のC1は入れても入れなくてもあんまり差はなかった。

またピンソケットを使って実装しておこうかな

2015年10月27日火曜日

小ネタ実験: ウィーンブリッジ正弦波発振回路の実験

リズムマシンのコーディング(と仕様ぎめ)が途中なのでアナログ回路の実験。

「回路の素101」 を参考にしてウィーンブリッジ正弦波発振回路を組んでみた。

LTSpaiceのシミュレーション


LTSpiceでシミュレーションしてみようと思ったが、うまく発振してくれなかった。

発振回路の場合はSpiceでは初期値をなんかゴニョゴニョしないとダメらしいんだが、よくわからないのでブレッドボードで実験することにした。

ブレッドボードで実験

ブレッドボード図


LTSpiceの回路図で
D1,D2: 1n4148
R1: 4.7k
R4,R5: 15k
で実験した。

出力波形

振幅3.280V、周波数103.9Hzで発振してくれた(^q^/

理論値では1kHzあたりになるはずだがまあいいや。またおいおい実験していきたい。

拡大

デジタル回路と違って拡大してもなめらかですなあ。

周波数をちゃんと50Hzとかあたりにして振幅変調させればTR-808っぽいkickになるかな?

リズムマシンならオシレーターもVCOである必要はないのです。

<追記:2015.10.29>
LTSpiceのOPAMPをUniversalOpamp2ではなくて特定の品種のものを指定したら発振した。
発振用の初期設定は.tranにStartupをつければいいようだ。
→電圧・電流源を開始から20usかけて電圧を上げる指定



また、ブレッドボードでの実験でCを0.01uFではなく、0.1uFを使っていた。なので100Hz前後の発振周波数でOKみたい。
</追記>

2015年10月25日日曜日

ブログのレイアウト模索します

去年書いた記事とか探しづらくなってきたので、ブログのレイアウトをいじります。

これが一番探しやすいかなあ?

リズムマシン とりあえず結合(メモ)

動作はするようだが、8kHzまでサンプリング周波数を落とさないとI2Cに制御が回らない。

ブレッドボード図



PSoC Creatorのプロジェクト
https://github.com/ryood/RhythmMachine/tree/master_test/Master_Test/PSoC/Sequencer_Master/Sequencer_Master_AQM1602_MCP4922_DDS.cydsn

リズムマシン関連のGitリポジトリを整理した
https://github.com/ryood/RhythmMachine/tree/master_test

そろそろ仕様を固めないとだめか。

前途多難の予感(^q^;

Arduino Pro Mini 328 3.3Vを秋月のFT232RL USBシリアル変換モジュールで使う

Arduino Pro Miniは3.3Vで動作するので3.3V系の回路の実験をするのに便利そうなので仕入れてみた。

普通のArduinoボードと違って書き込み用にUSB-Serial変換アダプタが必要だ。

SparkFunからほとんど専用みたいなボードも出ているが、手持ちに同じFT232RLを使った秋月のFT232RL USBシリアル変換モジュールキットがあったので使ってみた。

配線図


接続表

(色はブレッドボードのケーブルと合わせてあります)

FT232RLキットのジャンパーは
J1: 1-2ショート VIO 3.3V
J2: 1-2ショート USBバスからVCCに5V供給
にした。

LEDは別につけなくてもいいけど、UART通信の状態が見れるのでつけた方が安心かな?sinkなのでFT232RL側はLEDのカソードをつないでRを介してVCCにつなぐ。

Arduino IDE(1.6.4)の方も
ボード: Arduino Pro or Arduino Pro Mini
プロセッサ: ATMega328 (3.3V 8MHz)
ポート: <Arduino Pro Miniに割り当てられたポート>
に変更した。

ExampleのBlinkは動作した(^q^/

書き込み時のUART通信を見ると信号レベルも3.3Vになっている。J1の設定で変更されるのかな?(←未確認)

メモ:

秋月のFT232RLのキット(たぶん1年以上前に買った)にはんだ不良があって最初動かなくてハマってしまった。ピンヘッダと基板のはんだ付けが導通していなかった。はんだ付けはやっぱ電子工作の基本だ。昔のはんだ付けを見ると汚くて笑ってしまいます(^q^;;

2015年10月22日木曜日

リズムマシンの全体構想 Ver.2

仕事でアジャイル開発というのはなかなか難しいが、ひとりアジャイルは楽しい。

会社


開発「こういうのできるかもしれません(がんばって提案)」

顧客「(え?まあこれぐらいできそう)ん~それいいかもですね(おせじ)」

開発「え~っと、少し難しいかもしれないですが宿題とぃてぃあさfさksdlf;あ」

顧客「にっこり(おもしろいけど、できたとして上を説得して予算取れるかな・・・自信なし)」

開発「次回までにお見せでる形に仕上げてきます(わりと本気)」

顧客「ええ、期待してますよ!(安牌として社内調整しとくか・・・やれやれめんどくさ)」

1週間後元に戻る


仕事以外(ひとりアジャイル)


こういうの面白いかも→やってみるか→やっぱだめか(^q^;→いやまてまだ手はあるか?→考えてみるともっと面白いことできそう(^q^/)→元に戻る



リズムマシンの構想 Ver.2

I2Cバスにデバイスを複数載せるのはなにかとしんどそうだ。理屈の上ではできそうでもしんどそうだと思ったらやめたほうがいい。

最初に考えた「リズムマシンの全体の構想」でしんどそうなのは、「3) SPI Graphic LCD」と「2) Ext Board」だ。

できてることで整理してみると



「1) PSoC 4 Prototyping Kit」と「1')I2C Graphic or Charcter LCD」は同一基板に載せるつもりだ。(まずはキャラクタLCDかな?)

入力デバイスが少ないのでロータリー・エンコーダを2個ぐらい(これはパラレルで直に制御する?)

PSoC4のSCB I2Cは2個使えるので、LCD制御用とシーケンサー基板制御用の2系統のI2Cを使える。

DAC出力のSPIはUDBのコンポーネントを使えばリソースは足りそう。

3) SPI DAC & LPFはデジタル→アナログの境目なので別基盤にするか、DACはデジタル側の基板にしたほうがいいのか・・・

見た目DACとLPF用のOPAMPとは信号線とGNDの2本で行けそうか

どうせ耳には聞こえないけどクロックノイズは気分が悪い

PSoC 4でリズムマシンの音出しテスト

サンプリング周波数48kHzはムリだった。

32kHzでも処理が追いつかなくて、16kHzでギリギリセーフだ。コードを最適化していけばもうちょっと頑張れると思うが、I2Cで入出力処理したり、トラックを増やしたりしたいので16kHzでやっていくことにする。

SPI通信に時間が食われているのかも?と思ってPSoC4の内蔵DACでもやってみたが、大して変わらなかった。

MCP4922を使った場合のタイミング

PSoC CreatorのTopDesign

PSoC Creatorのプロジェクト
https://github.com/ryood/RhythmMachine/tree/master/PSoC/PSoC_Fixedpoint_DDS_RhythmMachine/PSoC_Fixedpoint_DDS_ThythmMachine_MCP4922.cydsn

DACにSPI送信する前後でPin_LDACをH/Lしているので、DACにデータを送信した場合としない場合を比較してみた。

DACへの送信なし(Sampling Rate: 48kHz)

ch1:Pin_LDAC ch2:Pin_Sampling_OV

DACへの送信あり(Sampling Rate: 48kHz)

DACへ送信すると処理がサンプリング間隔に間にあっていない。

PSoC 4内蔵のDACを使った場合のタイミング

PSoC ReatorのTopDesign

PSoC Creatorのプロジェクト
https://github.com/ryood/RhythmMachine/tree/master/PSoC/PSoC_Fixedpoint_DDS_RhythmMachine/PSoC_Fixedpoint_DDS_RhythmMachine_IDAC.cydsn

DACに値を設定する前後で「Pin_IDAC8_Check」をH/Lした。


ch1:Pin_Sampling_OV ch2:Pin_IDAC8_Check

ありゃ?なんか波形がおかしいかな?気が向いたらもう一回チェックしてみよう。

MCP4922でサンプリング周波数を下げる

16kHz

ch1:Pin_LDAC ch2:Pin_Sampling_OV

ギリギリ間に合ってる感じだ。

12kHz

8kHz

8kHzまで下げるとかなり余裕がある。

音出し

MCP4922はPin14の出力をそのまま、PSoCのIDACは1kΩのRでI-V変換してオーディオインタフェースに入力して、PC上のGoldWave(波形編集ソフト)で録音した。DACの出力にはLPFを入れていない。


8kHzではさすがに変な音になるが、逆にエフェクトかけたみたいで面白いかもしれない。

MCP4922の16MHzのキックの裏拍が歪っぽいが、スネアとかぶっていてオーバーした値に強制的にリミットをかけているのでどうとも言えない。(波形は頭打ちの矩形波的になっている)


メモ:


  • ProperaheadのRebirth338(TB-303、TR-808、TR-909のソフト音源)で遊んでいたのがPentiumの200MHzとかそのあたりのCPUだったような気がするので、いくらARMとはいえFPUやMMX(だっけか?)の載っていない48MHzのCPUで音を作って合成するのはそれなりに真剣にやらないといけないようだ。
  • Cortex-M3のPSoC 5LPならもう少し余裕がありそうだがなんか悔しいのでPSoC4でできる範囲で粘ろうと思う。
  • サンプリング周波数を実行時に設定できるようにする?
  • シーケンサー基板とPSoC4でほんとに単にシーケンサーとして使い、アナログ発振回路やノイズ生成回路の振幅を制御することも妄想中。


2015年10月19日月曜日

PSoC 4でSPI Masterコンポーネントを使う MCP4922編

結論として、SPI MasterコンポーネントはSCBコンポーネントより高速に動作させることができた。

ブレッドボード図

SCBモードと比べてMOSI(オレンジの線)だけがピンの位置が異なっている。

ピン接続表

PSoC Creator TopDesign

SPI Masterコンポーネントのclockに入力したクロック周波数の1/2がSPIのクロックになる。

PSoC Creatorのプロジェクト
https://github.com/ryood/PSoC_SPI_Master_Component_Test/tree/master/PSoC_SPI_Master_Test

出力波形


main.c: メインルーチン

 int main()  
 {  
   CyGlobalIntEnable; /* Enable global interrupts. */  
   SPIM_Start();  
   uint16 i = 0;  
   for(;;)  
   {  
     DACSetVoltage16bit(i);  
     //DACSetVoltage(i);  
     i += 8;  
     if (i == 4096)   
       i = 0;  
   }  
 }  

として、ノコギリ波を出力した。

SPI Master Component
input clock:48MHz(SPI clock: 24MHz)

(2)F: 564.2Hz

MCP4922はMax:20MHzなので仕様外だが、動作した。

SPI Master Component
input clock:24MHz(SPI clock: 12MHz)

(2)F: 431.7Hz

SCB Component
SPI clock: 8MHz(最大)

(2)F: 349.4Hz

タイミング制御なしで連続して出力しているので周波数が高い方が処理速度が速い。

SPI信号


SPI Master Component
input clock:48MHz(SPI clock: 24MHz)

ch1: LDAC ch2: SCLK

(1)T: 3.460us

ch1のHの時間は500ns * 4.5div = 2.25us

「T:」は波形の周期

SPI Master Component
input clock:24MHz(SPI clock: 12MHz)

(1)T: 4.530us

ch1のHの時間は500ns * 6.5div = 3.25us

SCB Component
SPI clock: 8MHz(最大)

(1)T: 4.940us

ch1のHの時間は500ns * 7.4div = 3.7us

ch2はSPIのSCLKだが、間欠的なので「(2)F:」は正確な周波数が表示されていないと思う。

SPIのデータ送信の前後でLDAC(ラッチ)をH/Lしているのでこの間隔がSPI通信の実行時間になる。

SPI Masterコンポーネントの24MHzはMCP4922の方の仕様外なので、12MHzを使うとしても、SCBコンポーネントの8MHzより速い。

サンプリング区間との比較

サンプリング周波数を48,000Hzにすると
1 / 48,000Hz = だいたい20.83us
20.83us - 3.25us = 17.58us
なので、このスキマ時間で波形生成の演算+αをやらないといけないようだ。

前回「PSoC 4 Pioneer Kitでリズムマシンのプログラムの処理時間を計測」で一番早い結果を見ると、「Releseモードでコンパイル」で0.332sだったので
0.332s / 48,000 = だいたい6.7us
なのでなんとかいけるかな?

メモ:


  • 足りなかたらサンプリング周波数を32,000Hzに落とす。あるいは仕様を無視してSPIのSCLKを24MHzでやる。
  • SPI MasterコンポーネントはMOSI、MISOをまとめて3Wireでやるモードもあるので配線の具合では試してみる。
  • PSoC 4にはDMAがない。なのでSPI MasterのExampleプロジェクトはやれなかった。DMAが使えればもうちょっとCPU時間を節約できるかも?やっぱそろそろPSoC 5LPも使っていこうか

2015年10月16日金曜日

オーディオ・インタフェースの出力特性の劣化?(TASCAM US-144MKII)

WaveGene & WaveSpectraで周波数特性を測定したいのだが、ループバックした時の特性が怪しい。

使い方は「WaveGeneとWaveSpectraで周波数特性の測定」の通りにしたんだが、間違ってるのだろうか?

WaveGeneの設定

WaveSpectraの設定&解析結果

めちゃくちゃハイ落ちしている。

オーディオインターフェイスに入れるLimiterの構想」でやった時よりも劣化してる気がする。

念のためWaveGeneでサイン波を出力してオシロ(OWON SDS7102)で手作業で周波数ごとの出力レベルを見てみた。


Hi落ちのカーブはWaveGene & オシロでもWaveGene & WaveSpectraの測定でもあまりかわらない。

US-144MKIIはいつ買ったか忘れたが、出力段に電解コンデンサが入っていると仮定するとこれが劣化しているような気がする。

もう保証期間も過ぎてることだしガワを開けて観察してみようかな?

周波数特性を見る手段


ちゃんとしたサイン波を出せるファンクションジェネレーターを作れば、オシロと組み合わせてひとつひとつ調べればある程度周波数特性を見れる気がします。

ハイレゾ系のオーディオ・インタフェースとかDigilentのAnalog Discoveryも欲しいけどここはじっとがまん

今日はがんばってハンダ付けしていなかった部品のハンダ付けをした。


右下のD020のDIP変換基板に載せているのがPCM5102というDACで384kHz/32bitのスペックらしい。→ということは162192kHzまで出力できる(理屈の上では)

入力はI2SということなのでPSoC4ではI2Sは使えないみたいなので(←ちゃんと確認してない)
ARM PSOC増刊付録のPSoC5LPの基板も実験に使えるようにピンソケットをハンダ付けしてみた。

PSoC 4 Pioneer Kitでリズムマシンのプログラムの処理時間を計測

Visual C++で作ったリズムマシンのプログラムをPSoC 4 Pioneer Kitに移植して処理時間を計測してみた。

PSoC Creatorのトップデザイン


時間の計測には16bitタイマー(TCPWMコンポーネント)を使って処理の開始時と終了時にCounter値を取得して調べた。

取得値はUARTを使ってPCで表示。

SPI通信自体は行っていない。

PSoC Creatorのプロジェクト
https://github.com/ryood/RhythmMachine/tree/master/PSoC/PSoC_Fixedpoint_DDS_RhythmMachine

48000回ループさせて、サンプリング周波数48kHzでちょうど1秒の間にどれだけ実行時間を消費するか見てみた。

変更点 start end end - start 処理時間(s) 比率
初期 2 7792 7790 0.779 100.0%
generateDDSWaveをinlineに 2 7432 7430 0.743 95.4%
generateNoiseをinlineに 2 7245 7243 0.7243 93.0%
hihatの波形生成をDDSに 2 6282 6280 0.628 80.6%
シーケンスを見て処理を省略 2 3967 3965 0.3965 50.9%
Releseモードでコンパイル 2 3322 3320 0.332 42.6%

表は上から順に追加変更していったものだ。

比率(初期プログラムに対する短縮率)を見ると、「シーケンスを見て処理を省略」するとかなり速くなっている。シーケンスが0(音符で言う休符)のとき、処理を省略するというもので、かなり早くなるが、音数が増えると処理落ちする可能性がある。

「hihatの波形生成をDDSに」も結構効いている。Hihatの波形をrand()関数で作っているのをテーブル参照にした。rand関数ですらコストが高いらしい。

inline指定やコンパイルオプションの変更は多少効く感じだ。

SPI通信との兼ね合い

波形生成とSPI通信と順番に処理するとかなりカツカツっぽい。

PSoC 4 Pioneer KitでMCP4922を使う DDSで波形生成」で調べたSPI通信のタイミング


ざっくり見てサンプリング区間の50%以下で波形生成の処理を終わらせないとだめっぽい?入出力系のI2C通信の時間もあるので。

PSoC 4のSPIにはSCB(Serial Communication Block)以外にもUDB(内蔵のCPLDみたいなもの)を使ったコンポーネントもあるようなので、これもテストしてみたいと思います。

2015年10月13日火曜日

固定小数点演算でリズムマシンのプログラミング(Visual C++)

Visual C++でリズムマシンのコードを書いた。

12bitのデータを数値出力してGoldwaveで再生したものをYoutubeにアップした。


Visual Studio 2013のプロジェクト
https://github.com/ryood/DDS_RhythmMachine_Test/tree/fixedpoint/DDS_RhythmMachine_Test

ソースコード
https://github.com/ryood/DDS_RhythmMachine_Test/blob/fixedpoint/DDS_RhythmMachine_Test/DDS_RhythmMachine_Test.cpp

浮動小数点演算が出てこないようにしたが、波形の出力周波数の設定値だけはdouble型のまま。

32bit固定小数点型にすると2^32と掛け算するときにオーバーフローするので保留。
→リズムマシンなので音程の精度は必要ないので問題があれば整数型にするつもりだ。toneAmountで位相変調して周波数の微調整をすればいいかな?

スネアの音にスナッピーを入れたり、クラップやその他も作りたいけど、一旦PSoCに落とし込んで様子を見たいと思います。

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

PSoC 4 Pioneer KitでMCP4922を使う DDSで波形生成

以前「PSoC 4 Pionner Kit 外付けDACでサイン波生成」でもMCP4922でサイン波を出力してみたが、もう少し詳しくテストしてみた。

2ch出力


少しがんばって2ch出力させてみた。


1Hzと0.5Hzのサイン波を生成して、LEDを点滅させている。

SPIクロックとサンプリング周波数

System Clockは48MHzに設定。

TopDesign

TCPWM_P4のConfigureダイアログ

TC_Sampling_TimerのPeriodを249に設定した。

0..249でカウントアップしてオーバーフローしたらISR_Sampling_Timerの割り込みがかかる。
12MHz / 250 = 48kHz
今まで(ぴゅんぴゅん3号など)250に設定していたので、間違っていたかも。

割り込みと同時にOVからPin_Sampling_ClockにH/Lの信号が出力される。
→オシロをあてればサンプリング周波数が測定できる。

main.c
 /* ========================================  
  *  
  * Copyright YOUR COMPANY, THE YEAR  
  * All Rights Reserved  
  * UNPUBLISHED, LICENSED SOFTWARE.  
  *  
  * CONFIDENTIAL AND PROPRIETARY INFORMATION  
  * WHICH IS THE PROPERTY OF your company.  
  *  
  * 2015.10.07 2ch同時出力  
  * 2015.10.06 2ch DACに対応(2ch同時出力は不可)  
  * 2015.10.06 TC_Sampling_TimerのOVをPINに出力  
  * 2015.10.03 MCP4922に12bitデータを送信  
  * ========================================  
 */  
 #include <project.h>  
 #include <math.h>  
 #include "wavetable.h"  
 #define SAMPLE_CLOCK (48000.0f)  
 #define WAVE_FREQUENCY_A (1000.0f)  
 #define WAVE_FREQUENCY_B (2000.0f)  
 // ERROR CODE  
 #define ERR_DAC_CHANNEL_OUT_OF_RANGE 0x01  
 volatile uint32 phaseRegisterA;  
 volatile uint32 phaseRegisterB;  
 volatile uint32 tuningWordA;  
 volatile uint32 tuningWordB;  
 void DACSetVoltage(uint16 value, int channel)  
 {  
   uint16 txData;  
   switch (channel) {  
   case 0:  
     // Highバイト(0x30=OUTA/BUFなし/1x/シャットダウンなし)  
     txData = (value & ~0xF000) | 0x3000;  
     break;  
   case 1:  
     // Highバイト(0xB0=OUTB/BUFなし/1x/シャットダウンなし)  
     txData = (value & ~0xF000) | 0xB000;  
     break;  
   default:  
     ;  
     // error(ERR_DAC_CHANNEL_OUT_OF_RANGE);  
   }  
      Pin_LDAC_Write(1u);  
   SPIM_DAC_SpiUartWriteTxData(txData);  
      while(0u == (SPIM_DAC_GetMasterInterruptSource() & SPIM_DAC_INTR_MASTER_SPI_DONE))  
      {  
           /* Wait while Master completes transfer */  
      }  
   Pin_LDAC_Write(0u);  
      /* Clear interrupt source after transfer completion */  
      SPIM_DAC_ClearMasterInterruptSource(SPIM_DAC_INTR_MASTER_SPI_DONE);  
 }  
 CY_ISR(ISR_Sampling_Timer_Handler)  
 {  
   uint32 index;  
      // Caluclate Wave Value A  
   //  
      phaseRegisterA += tuningWordA;  
      // 32bitのphaseRegisterをテーブルの10bit(1024個)に丸める  
      index = phaseRegisterA >> 22;  
   uint16 waveValueA = waveTableSine[index];  
   // Caluclate Wave Value B  
   //  
   phaseRegisterB += tuningWordB;  
   index = phaseRegisterB >> 22;  
   uint16 waveValueB = waveTableSine[index];  
   // Output  
   //  
      DACSetVoltage(waveValueA, 0);  
   DACSetVoltage(waveValueB, 1);  
   TC_Sampling_Timer_ClearInterrupt(TC_Sampling_Timer_INTR_MASK_TC);  
 }  
 int main()  
 {   
   // 変数の初期化  
      tuningWordA = WAVE_FREQUENCY_A * pow(2.0, 32) / SAMPLE_CLOCK;  
   phaseRegisterA = 0;  
   tuningWordB = WAVE_FREQUENCY_B * pow(2.0, 32) / SAMPLE_CLOCK;  
   phaseRegisterB = 0;  
   // コンポーネントの初期化  
   TC_Sampling_Timer_Start();  
   ISR_Sampling_Timer_StartEx(ISR_Sampling_Timer_Handler);  
   SPIM_DAC_Start();  
   CyGlobalIntEnable;  
   for(;;)  
   {  
   }  
 }  
 /* [] END OF FILE */  

MCP4922のchannelAから1kHz、channelBから2kHzのサイン波を出力するプログラムだ。

オシロで出力をチェック


ch1:TimerのOVの出力 ch2:DACのchannelBの出力
(1)F:47.94kHz
(2)F:998.0Hz
channelBは2kHzのはずなのでおかしい。


ch1:TimerOV ch2:LDAC(DACのラッチ)

よく見てみるとサンプリング区間の2回に1回しかラッチ信号が出力されていない。(ラッチ信号は2ch分あるので連続で2回出力される)

SPIのクロックを上げる

SPIMのConfigureダイアログ

SPIクロックを4MHzから8MHzにあげてみた。

また、TX data bitsを16に変更(デフォルトは8)。これで16bitまとめて出力できる。main.cもこれに合わせて変更した。

4MHzのSPI信号

ch1:MOSI ch2:SCK

SCKは4MHzになっている

8MHzに変更したSPI信号

SCKはだいたい8MHzになっている

オシロで出力をチェック


ch1:TimerOV ch2:LDAC

サンプリング区間ごとにラッチ信号が2回ずつ出力されている。


ch1:TimerOV ch2:DACのchannelB

出力周波数も無事2kHzに(^q^/)

メモ:

  • MCP4922の2ch同時出力はできたが、SPIの信号出力で相当時間が食われている。SPIの出力が終わるまで非同期で別の処理ができればいいが、波形生成の演算はSPI出力の前に終わらせる必要があるし、2ch出力は努力目標ということにしようかな。
  • リズムマシンはDecayやLevel、トラックの合成など結構計算量がありそうだし、無理そうならサンプリング周波数を32kHzに落とすとか。
  • DACの片チャンネルは仮想GNDの電位を出力する?
  • 出力段のLPFは必須


PSoC Creatorのプロジェクト
https://github.com/ryood/PSoC-DDS-MCP4922