2016年6月26日日曜日

NucleoシーケンサーとPSoC4 DCOとDCAの結合テスト(UIなし)

NucleoシーケンサーのUIを省いてDCO->DCAのテスト

配線図


MCP4922+NJM13700のDCAのエンベロープ波形をコントロールするPOTはつないでいる。

 Level | Duration | Decay | Sustain

Nucleoシーケンサー:
https://developer.mbed.org/users/ryood/code/SpiSequenceSender_Test/

PSoC4 DCO:
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_NucleoF401RE_Interrupt_Test.cydsn

SpiSequenceSender_Test
main.cpp

#include "mbed.h"
#include "rtos.h"

#define UART_TRACE  (0)
#include "SpiSequenceSender.h"
#include "EnvelopeGenerator.h"
#include "SpiAmpController.h"

#define SEQUENCE_N  (16)
#define SPI_RATE    (8000000)

const int samplingPeriod = 1;   // ms
const int bpm = 120;
const int envelopeLength = (60 * 1000 / (bpm * 4)) / samplingPeriod;

AnalogIn levelIn(A0);
AnalogIn durationIn(A1);
AnalogIn decayIn(A2);
AnalogIn sustainIn(A3);

SPI spiMaster(SPI_MOSI, SPI_MISO, SPI_SCK);

Sequence sequence[SEQUENCE_N];
SpiSequenceSender sequenceSender(&spiMaster, D9, sequence, SEQUENCE_N, samplingPeriod, bpm);

Envelope envelope(4095, envelopeLength, envelopeLength*3/4, envelopeLength/2, 2047);
EnvelopeGenerator envelopeGenerator;
SpiAmpController ampController(&spiMaster, D8, D7);


class TestClass {
public:
    void callbackFunction(int ticks)
    {
        if (ticks == 0) {
            envelopeGenerator.init(envelope);
        }
        uint16_t level = envelopeGenerator.getModLevel();
        //printf("m,%d\r\n", level);
        ampController.outDca(level);
        envelopeGenerator.update();
    }
} testClass;

void callbackFunction(int ticks)
{
    if (ticks == 0) {
        envelopeGenerator.init(envelope);
    }
    uint16_t level = envelopeGenerator.getModLevel();
    //printf("g,%d\r\n", level);
    ampController.outDca(level);
    envelopeGenerator.update();
}

int main()
{
    spiMaster.format(8, 0);
    spiMaster.frequency(SPI_RATE);
    
    // Test SequencerSender Run
    //
    sequenceSender.setBpm(bpm);
    for (int i = 0; i < SEQUENCE_N; i++) {
        Sequence& seq = sequenceSender.getSequences()[i];
        seq.setPitch(i);
        seq.setOctave(-1);
        seq.tie = true;
    }

    sequence[3].setOctave(0);
    sequence[7].setOctave(0);
    sequence[11].setOctave(0);
    sequence[15].setOctave(0);
    
    envelopeGenerator.init(envelope);
    
    sequenceSender.attachUpdate(&testClass, &TestClass::callbackFunction);
    //sequenceSender.attachUpdate(&callbackFunction);
    sequenceSender.setWaveShape(SpiSequenceSender::WAVESHAPE_SAW);
    sequenceSender.run(0);
    
    for (;;) {
        /*
        sequenceSender.setPulseWidth(sequenceSender.getPulseWidth() + 4);
        Thread::wait(500);
        sequenceSender.setWaveShape(SpiSequenceSender::WAVESHAPE_SAW);
        Thread::wait(500);
        sequenceSender.setWaveShape(SpiSequenceSender::WAVESHAPE_SQUARE);
        */
        envelope.setLevel(levelIn * 4095);
        envelope.setDuration(durationIn * envelopeLength);
        envelope.setDecay(decayIn * envelopeLength);
        envelope.setSustain(sustainIn * 4095);
    }
}

プログラムはCallbackのテストも兼ねているのでTestClass::callbackFunction(int ticks)とcallbackFunction(int ticks)を定義しているが、呼び出しているのはcallbackFunction(int ticks) TestClass::callbackFunction(int ticks)。

入出力波形



ch1:DCA出力波形 ch2:PSoC4 DCO出力波形

PSoC4 DCOの出力を100mV(p-p)以下にしているので、ノイズ(おそらくSPI信号)がかなり重畳している。


ch1:DCA出力波形 ch2:MCP4922の出力波形(エンベロープ)

エンベロープを変化させたようすをYoutubeにあげた。


メモ:


DCOの出力波形が0V~+100mV程度になっているのを-50mV~0V~+50mVにしたほうがいいか?
→AC結合すればなんとかなる(@@?

シーケンスのnoteOn/Offは未実装。DCOではなくDCAで処理する。

エンベロープのパラメータをいじるPOTx4はNucleoのArduino HeadersではなくMorpho Headersに繋げるようにする?

MCP4922とNJM13700のDCAの整理

この間作ったAmpControllerクラスでMCP4922とNJM13700のDCAを制御する実験をしてみた。

配線図


mbed repository:
https://developer.mbed.org/users/ryood/code/AmpController_Test/

入出力のようす



ch1:波形出力 ch2:MCP4922からの出力


ch1:波形出力 ch2:波形入力

拡大

入力波形を100mV(p-p)以内にしておけば、MCP4922からの出力は0V~3.3V(MCP4922のGND-VDDのフルスケール)でも出力波形は歪まないようだ。

NJM13700の使ってないチャンネルの処理


NJM13700のチャンネルBを開放のまま使うとすぐ発振したりするのでOPAMPの使っていない回路と同じように、OUTPUTとINPUT-を直結してボルテージフォロアにし、INPUT+をGNDに落とした。

また、OUTPUTはBUFFER INPUTにも接続。BIAS INPUTは電流を流し込まなければ良いと思うので開放のままとした。

これでいいのかどうかわからないが安定度はだいぶ増した感じだ。

DIODE BIASはいまだよくわかっていないので両チャンネルとも開放のままとした。

※LM13700のDATASHEETを見るとRを介して正電源に接続して電流を流してやればリニアリティが改善するようなことが書いてある。

NJM13700のチャンネルBを処理しても、まだ発振する場合がある。条件は不明。

波形入力には自作のPCM5102を使ったファンクションジェネレータを使っていて、この出力が発振する。


(右端にちょこっと見えてるのがファンクションジェネレータ)

出力に4次バターワースLPF を入れていて、これが発振してしまっているのかもしれない?ファンクションジェネレータのスイッチをOFF→ONすれば直るが。

NJM13700のチャンネルBを処理する前は全体の電源を入れなおさないと発振が止まらなかった。

また、波形のGNDレベルが片側に張り付いてしまうこともあった。


ch1:波形入力 ch2:波形出力

これも直ったような感じだがもうちょっとテストした方が良さそう。

メモ:


電源をちゃんと考える。
 Nucleo: 5V
 PSoC4: 5V (PSoC4 Prototyping Kitを使う場合は3.3V)
 DCF: 5V
 DCA: 3.3Vと±5V

基板に実装する前にOPAMP+PNPトランジスタの電圧→電流変換回路をもう一度シミュレーションしてみる。

NJM13700のDIODEバイアスの実験。リニアリティはそんなに要求されないので気が向いたら(^q^;

MCP4922からの出力波形をなまらすために1次CR LPFを入れているが、調節出来るようにRを可変にしたほうがいい?

2016年6月24日金曜日

PSoC5LP Prototyping KitでEEPROMを使ってみる。

シーケンサーで作ったシーケンスを永続化したいのだが、何が一番簡単か考えてみるとマイコンに内蔵されているflashメモリーに書いてしまえれば話が早そうだ。

だがしかし、ノイマン型ではない、なんだっけ名前忘れたが(^q^; プログラムをflashメモリにおいてSRAMを作業領域として分離して使うタイプのCPUだと、実行時にそうは簡単にflashメモリーに書き込めない仕組みになっているようだ。

flashメモリへの実行時書き込みは宿題として、簡単にアクセスできそうなメモリは

SDカード
EEPROM

ぐらいだと思う。

SDカードはハンダ付けしたり配線するのがめんどくさそうなのでEEPROMが内蔵されているPSoC 5LPでEEPROMを使ってみることにした。

試した見たのはPSoC CreatorのexampleのEEPROM_Designというプロジェクト。

基本的には「PSoC 5LP Prototyping KitでChar LCD(HD44780)を使う まとめ」と同じ配線で、プロジェクトのTopDesignに書いてあるように


Procedure:

2. Connect LED1 to pin P0[0], LED4 to P0[1] and SW1 to P0[2].

のとおり配線した。(LEDの1とか4の意味は分からないがCY8CKIT-001がそうなってるのかな?)


そのままではコンパイルが通らなかったので、PSoC CreatorのメニューからProject/Debice SelectorでPSoC 5LP Prototyping KitのCY8C5888-LP097を指定。

USBを引っこ抜いて挿しなおしても表示値は追随していたので、RST回数やERASE回数はEEPROMに保持されていると思う。

メモ:

PSoC5LPの型番は覚えにくいが、Prototyping KitはLP097、CQ出版のはLP035。

PSoCにはEm_EEPROMというコンポーネントがあって、EEPROMが実装されていないPSoC4てもflashメモリーでエミュレートできるようだ。

→製作中のベースマシンではmbedで使うNucleoF401REが親なのでSDカードで保持する方向で考えるべきか?


mbedでMCP4922にエンベロープ波形を送るクラスを作る。

エンベロープ波形の生成とSPI送信をmbedでクラス化してみた。

ブレッドボード図


MCP4922の出力でNJM13700を使ったVCAのコントロールもしてみたかったが、配線がぐちゃぐちゃしてきたのでまずは単体でテストした。

mbed repository:
https://developer.mbed.org/users/ryood/code/AmpController_Test/

main.cpp

/*
 * EnvelopeをMCP4922に出力するテスト
 *
 * 2016.06.23
 *
 */

#include "mbed.h"
#include "rtos.h"
#include "Envelope.h"
#include "SpiAmpController.h"

#define AMP_VREF    0x0fff
#define SPIM_RATE   1000000

const int envLength = 25;

AnalogIn levelIn(A0);
AnalogIn durationIn(A1);
AnalogIn decayIn(A2);
AnalogIn sustainIn(A3);

SPI spiM(SPI_MOSI, SPI_MISO, SPI_SCK);

int main()
{
    spiM.format(8, 0);
    spiM.frequency(SPIM_RATE);
       
    // Envelope(level, length, duration, decay, sustain)
    Envelope envelope(4095, envLength, envLength*3/4, envLength/2, 2047);
    SpiAmpController ampController(&spiM, D8, D7);
    ampController.setVref(AMP_VREF);
        
    printf("\r\n\n*** Amp Controller Test ***\r\n");
    envelope.init();
    int cnt = 0;
    int vref = 0;
    while (true) {
        ampController.outDca(envelope.getModLevel());
        envelope.update();
        
        cnt++;
        if (cnt == envLength) {
            cnt = 0;
            envelope.setLevel(levelIn * 4095);
            envelope.setDuration(durationIn * envLength);
            envelope.setDecay(decayIn * envLength);
            envelope.setSustain(sustainIn * 4095);
            
            /*
            printf("%d\t%d\t%d\t%d\t%d\t\r\n",
                envelope.getLevel(), envelope.getLength(), envelope.getDuration(), envelope.getDecay(), envelope.getSustain());
            */
            envelope.init();
            ampController.setVref(vref);
            vref += 0x10;
            if (vref > 0x0fff) {
                vref = 0;
            }
        }
        Thread::wait(5);
    }
}

プログラム(クラス構成)もなんかぐちゃぐちゃしてきた(^q^;

一度クラス図を書いて整理したほうが良さそうな気もする。

vrefを0x07ff0x0fff(Vdd)にした時のエンベロープ波形

vrefを0x07ff(Vdd/2)にした時のエンベロープ波形

メモ:

Arduino Headerのアサイン(SPI関連)

D13  SPI SCK
D12  SPI MISO
D11  SPI MOSI
D10  DCF CS
D9    DCO CS
D8    DCA CS
D7    DCA LDAC

2016年6月23日木曜日

mbedのコールバック FunctionPointerを使うテスト

mbedにはコールバックを簡単に記述するためにFunctionPointerというクラスが用意されているようだ。

APIはドキュメントに載っていないようだが、mbed-src/api/FunctionPointer.hで定義されている。ちょっとややこしいのでよく読んでいない(^q^; いつものようにとりあえず動かしてみた。

FunctionPointerテストプログラム

#include "mbed.h"

class Foo {
public:
    void attach(void (*function)(void)) {
        callback.attach( function );
    }
    
    template<typename T>
    void attach(T *object, void (T::*member)(void)) {
        callback.attach( object, member );        
    }
    
    void update() {
        callback.call();
    }
    
protected:
    FunctionPointer callback;
};

class Bar {
public:
    Bar() : cnt(0) {}
    
    void bar_func() {
        printf("in Bar::bar_func\t%d\r\n", cnt);
        cnt++;
    }
    
private:
    int cnt;
};

void my_func() {
    static int cnt = 0;
    printf("in my_func\t%d\r\n", cnt);
    cnt++;
}

int main()
{
    printf("Callback function test\r\n");
    
    Foo foo1;
    foo1.attach(&my_func);
    
    Foo foo2;
    Bar bar;
    foo2.attach(&bar, &Bar::bar_func);
    
    while(1) {
        foo1.update();
        foo2.update();
        wait(1);
    }
}

foo1はコールバックに普通の関数、foo2はBarクラスのメンバ関数をコールバック関数として登録している。

コールバック関数をFunctionPointer::attach()で登録して、FunctionPointer::call()で呼び出す。

FunctionPointerを使うとコールバック関数は引数、返値とも取れない。

FunctionPointer.hにはint型の引数を一つだけとるevent_callback_tというクラスもtypedefされているのでこちらも同じようにテスト。

event_callback_tテストプログラム

#include "mbed.h"

class Foo {
public:
    void attach(void (*function)(int)) {
        callback.attach( function );
    }
   
    template<typename T>
    void attach(T *object, void (T::*member)(int)) {
        callback.attach( object, member );      
    }
   
    void update(int cnt) {
        callback.call(cnt);
    }
   
protected:
    event_callback_t callback;
};

class Bar {
public:
    void bar_func(int cnt) {
        printf("in Bar::bar_func\t%d\r\n", cnt);
    }
};

void my_func(int cnt)
{
    printf("in my_func\t%d\r\n", cnt);
}

int main()
{
    printf("Callback function test\r\n");
   
    Foo foo1;
    foo1.attach(&my_func);
   
    Foo foo2;
    Bar bar;
    foo2.attach(&bar, &Bar::bar_func);
   
    int cnt = 0;
    while(1) {
        foo1.update(cnt);
        foo2.update(cnt);
        cnt++;
        wait(1);
    }
}

2016年6月21日火曜日

NucleoシーケンサーとPSoC4 DCOとVCVS DCFの結合テスト

NucleoシーケンサーとPSoC4 DCO(「NucleoシーケンサーとPSoC4 DCOのテスト(その4)」)にVCVS DCFシールドを追加してテストしてみた。

配線図

<追記:2016.06.23> Nucleo→PSoC4へのSPI:CSのピンが違っていたので修正(D10→D9) </追記>


ぴゅんぴゅんコントローラー2号のジョイスティックでVCVS DCFのcutoffとresonanceをコントロールする。ジョイスティックの値はNucleoシーケンサーで受けて、Nucleoに載せたVCVS DCFシールドにSPIで値を送っている。

PSoC4 Pioneer KitとVCVS DCFシールドは同一のSPIバス(SPI1)を使用。グラフィックLCD(Nokia 5110)は別のSPIバス(SPI2)を使用。

Nucleoシーケンサー
https://developer.mbed.org/users/ryood/code/Nucleo_SPI_Sequencer/

PSoC4 DCO
https://github.com/ryood/PSoC4_DCO/tree/master/PSoC/PSoC4_DCO_for_NucleoF401RE_Interrupt_Test.cydsn

入出力波形


ch1:PSoC4 DCOからの出力 ch2:VCVS DCFからの出力

ジョイスティックがニュートラルの状態。cutoff:124、resonance:130


cutoff、resonanceを上げて発振させた状態。cutoff:255、resonance:232


cutoffだけ上げて発振させた状態。cutoff:255、resonance:140

PSoC4 DCOからの出力はIDACの出力を1kΩの抵抗で電流電圧変換していて、振幅は0V~612mV。

VCVS DCFからの出力は発振させると電源電圧の5V(p-p)付近になるが、AC結合しているのでパルス幅によっては0Vからの振幅になる。

->OTAはの信号入力は差動で100mV以内にしないといけないので1/50~1/5程度に減衰。もともと歪んでいる場合は除外して値を決める?

2016年6月18日土曜日

Tube Screamer 808をシミュレーションしてみる。

Google+の電子工作部でTubeSceamerというエフェクタの回路を教えてもらったのでLTSpiceでシミュレーションしてみた。

回路図

Driveと書いてあるところがダイオードを使ったリミッターで、R5:{Rdrive}の値とR6の値で増幅率を決め、D1/D2のダイオードで増幅しすぎた電圧をクリップするのでダイオードの特性で歪む。

Toneと書いてあるところは
増幅率 = (1 + ((R9 + R10) / R12) = 21倍 
の非反転増幅回路で(R9+R10)の途中のC7で高周波数をGNDに逃がしてLPFの働きをする。

過渡解析(drive)

R5:{Rdrive}の値を変化させてみた。


±20mVの正弦波を入力して歪み方を見てみた。R5:{Rdrive}の値を上げると歪みだすが、なかなかおもしろい歪み方をする。この定数だと矩形波近くまでガチガチに歪むことはないようだ。

過渡解析(tone)

R9/R10の値を変化させてみた



BOSS OD-1


ダイオードを1本追加するとOD-1の歪み方になるそうなのでこれもシミュレーション。

回路図

<追記:2016.06.21>

OD-1の回路図を見たら、Driveの回路の3本目のダイオードは並列ではなくて、直列に入れるようです。並列だと非対称にならないか(^q^;

</追記>

追加するダイオードの方向はよくわからないが、1kHzの正弦波の入力ではどちらでも大差ないようす。

過渡解析(dirve)


過渡解析(tone)


メモ:


1kHzの正弦波のシミュレーションではよくわからないが、このヘンテコな歪み方はリアルタイムにデジタル処理して再現するのはなかなか難しいのではないだろうか。

ギターは弾けないので(弾けないわけではなくて偉そうなこと言えない)、ギターのエフェクタとして評価はできないが、ダイオードを使ったリミッタの歪はなかなかおもしろそうな気がする。

Nucleo(mbed)のPwmOutで波形を出力してみる。

MCP4922とNJM13700でSPI制御のDCA(Nucleo編)」でSPI DACのMCP4922を使ってVCAをコントロールしてみたが、外付けDACを使わないでPWMでできるかどうかやってみた。

mbedにはAnalogOutというDACを使うようなClassもあるが、Nucleo F401REでは使えないようだ(←実際に試したわけではないです。)

参考:https://developer.mbed.org/questions/4255/AnalogOut-problem-on-Nucleo-F401RE/

PWMなら使えるのでLPFを通してモジュレーション波形が出せるかどうか試してみた。

main.cpp

#include "mbed.h"

PwmOut mypwm(PWM_OUT);
DigitalOut led1(LED1);
Ticker ticker;

const float carrier_freq   = 100000.0f;              // 100kHz
const float carrier_period = 1.0f / carrier_freq;

const float update_period  = 0.00001f;               // 10us

volatile uint8_t cnt = 0;

void update()
{
    cnt++;
    float phi = cnt / 256.0f;
    mypwm.pulsewidth(phi * carrier_period);
}

int main()
{   
    mypwm.period(carrier_period);
    mypwm.pulsewidth(carrier_period / 2);
  
    printf("pwm set to %.2f %%\n", mypwm.read() * 100);
    
    ticker.attach(&update, update_period);

    while(1) {
        led1 = !led1;
        wait(0.5);
    }
}

キャリアを可聴帯域の外の100kHzにして10usごとにデューティ比を変化させてみた。サンプリングレートは10usの逆数の100kHz。256段階で変化させているので8bit相当になる。


PWM波形のままだとどうしようもないので2次CR LPFを通した。
fc = 1 / (2 * π * C * R) = 1 / (2 * π * 0.01uF * 1kΩ) ≒ 15.9kHz
の2段積み(とりあえずの値)。

出力波形



ch1:PWM出力 ch2:2次CR LPF通過後

拡大

PWM出力も拡大してみると波形が崩れているようだ。LPFを外すとPWM波形はちゃんと出力される。(オシロの仕様(?)で何回か分の波形が重ねて表示されるようです。)


メモ:


PWM出力にすればMCP4922を省略できそうだが、PWM出力だと何かいろいろ問題が出てきそうなので保留。

Nucleo-F446REという上位バージョンだとDACも搭載されているようだ。

Google+の電子工作部でPDM(Pulse Density Modulation)のマイクを使っている方がおられたので粗密波のサンプリングレートとPCMのサンプリングレート/ビット数の関係を少し考えてみたがやはりよくわからない(^q^;

1bit DACを最初に製品化したのはシャープだったと思うが、ついにホンハイに買収されてなんとも悲しい話。

今、WebでPDMで検索したらシャープのΔΣの技報がひっかかった。あとで読んでみよう(^q^/

NucleoでVCVS DCFシールドの動作確認

以前作ったVCVS DCFシールドをNucleo F401REで動作確認してみた。


シールドを作ったとき、Nucleoが3.3V駆動なのに気付いていなくて、シールドは5V電源で動作するように作った。Arduino互換のピンソケットはちゃんと5Vのピンが出ているのでシールドはそのまま5V駆動できる。問題となるのはAD8403の信号線のレベルで、Data Sheetを見るとVDD=5Vのとき
Input Logic Hight(VIH) | Min 2.4V
となっているので、3.3Vで駆動のNucleoから送っても問題ないようだ。助かった(^q^;

Nucleo_DigitalPotControl_AnalogRead
main.cpp

/*
  Digital Pot Control

  AD8403

  2つのPOTの出力電圧を読み取って
  Digi-Potのチャンネル1, 2, 4の抵抗値を可変

  Pinの接続
  A0 POT1 cutoff
  A1 POT2 resonance
  10 CS
  11 MOSI
  13 SCK
  
  Digi-Pot
  A1 - connect this to voltage
  W1 - this is the pot's wiper, which changes when you set it
  B1 - connect this to ground.
*/

#include "mbed.h"
 
Serial pc(SERIAL_TX, SERIAL_RX);

SPI spi(SPI_MOSI, SPI_MISO, SPI_SCK);
DigitalOut CS(D10);

AnalogIn cutoffIn(A0);
AnalogIn resonanceIn(A1);

void digitalPotWrite(int address, int value)
{
    CS = 0;
    spi.write(address);
    spi.write(value);
    wait_us(1);
    CS = 1;
}

int main() {
    pc.printf("\r\n*** Digital Pot Control\r\n");
    while(1) {
        uint16_t cutoff = cutoffIn.read_u16() >> 8;
        uint16_t resonance = resonanceIn.read_u16() >> 8;
        
        pc.printf("%u\t%u\r\n", cutoff, resonance);
        
        digitalPotWrite(0, 255 - resonance);
        digitalPotWrite(1, 255 - cutoff);
        digitalPotWrite(3, 255 - cutoff);
    }
}


A0、A1につないだPOTでフィルターのcutoffとresonanceをコントロールする。

入出力のようす



ch1:入力 ch2:出力

Resonanceをあげない状態だと、普通のLPFとしても動作する。Resonance(Q)と増幅率が連動しているのでResonanceを下げると出力レベルは下がる。


出力は-300mVぐらいが下限の様だ。

メモ:


NJM13700を使ったVCAの入力振幅は±100mV以下にしないと歪むので、うまくレベル調整できるようにしたい。←まずはなるべく歪まないように設計。

5Vを分圧して±2.5V程度でフィルターに使うOPAMPを駆動すると入出力レベルの調整が厳しい。LTC1144という負電圧を作り出すICがあるのでこれを使う手もありそう。(アナログ系の素子を±5Vの両電源で駆動できる)

cutoff/resonanceの設定によってはファンクションジェネレータが異常発振してしまう。Filterの発振が逆流(?)しているのか?

2016年6月16日木曜日

ArduinoとNucleoでPCとのSerial(UART)通信の最高速度を調べてみる。

SPIやI2Cの通信状態を解析しようと思うと補足した信号をPCに送ってから見やすいように整形・グラフ化するのが良さそうだ。

Arduinoでは、I2Cはデフォルトで100kbps、SPIはよく覚えていないが数Mhzぐらいだった気がする。

ArduinoのSerialはデフォルトが9600bps(9.6kbps)なので、I2Cと比べても明らかに遅い。

PCとの通信にはUSB HIDも使えそうなのでWebで調べてみたがUARTより特に速い感じでもない。

SDカードに一回保存して…とか、TCP-IP/Wi-fiを使ってみるとか一瞬思ったがかなりめんどくさそう。

なのでPCとArduino Uno/NucleoとのSerial通信をどれぐらいの速度でできるか調べてみた。


Arduino Uno


最高1Mbpsで送信できたが、PC(putty)で受信すると取りこぼしが結構ある。

Arduinoのスケッチ(SerialSpeedTest.ino)

#define BAUD_RATE 1000000
#define LOOP_N    1000
#define SEND_CHR  (0x55)

void setup() {
  Serial.begin(BAUD_RATE);
  Serial.println("***Serial Speed Test Start***");
}

void loop() {
  char strBuffer[80];

  Serial.print("\r\n\nBaud Rate: ");
  Serial.println(BAUD_RATE);
  
  unsigned long startTime = micros();
  for (int i = 0; i < LOOP_N; i++) {
    Serial.write(SEND_CHR);
  }
  unsigned long duration = micros() - startTime;
  unsigned long bps = (unsigned long)(8 * 1000000.0f * (float)LOOP_N  / (float)duration);
  
  Serial.print("\r\n");
  sprintf(strBuffer, "\r\n%lu bits/second\r\n", bps);
  Serial.print(strBuffer);
}


1Mbpsを指定した場合はおおよそ1Mbpsで出力できた。


ch1:D0(RX) ch2:D1(TX)

送信するバイトは #define SEND_CHR で指定した0x55なので、ビットにすると0b01010101になる。したがってオシロの表示値の499.3KHzの倍の998.6kHzが送信レートになる。

puttyの受信のようす

’U'がいっぱい並んでいるが、0x55をASCIIコードで文字にすると'U'になる。

ボーレートが1Mbpsで実際送信できたのは800kbps弱。しかも時々読みこぼしてしまうようだ。

Nucleo F401RE


main.cpp(Nucleo_Serial_Speed_test)

#include "mbed.h"

#define BAUD_RATE 2000000
#define LOOP_N    1000
#define SEND_CHR  (0x55)

Serial pc(SERIAL_TX, SERIAL_RX);
Timer timer;

int main()
{
    pc.baud(BAUD_RATE);
    pc.printf("***Serial Speed Test Start***");
   
    timer.start();
   
    while(1) {
        pc.printf("\r\n\nBaud Rate: %d\r\n", BAUD_RATE);
       
        timer.reset();

        for (int i = 0; i < LOOP_N; i++) {
            pc.putc(SEND_CHR);
        }
       
        uint32_t bps = (uint32_t)(8.0f * (float)LOOP_N / timer.read());
       
        pc.printf("\r\n\n%u bits/secon\r\n", bps);
    }
}


表示値が1MHzなので、同じく倍にして2Mbps。Arduino互換のD0、D1には信号が出ていなくて、分割できるようになっている通信用(?)の基板のRX、TXと書いてあるところでUART信号を捕捉できた。

F401REをmbedで使うと2Mbpsが最大のようだ。それ以上を指定すると信号が出ないか、めちゃくちゃ遅くなってしまう(1kHz以下)

メモ:


Nucelo F401REは最大84MHz駆動となっているが、ほんとにそのクロックで動作してるのだろうか?mbedだとクロックの計測方法がわからない。


Nucleoのボード上ではX3と書いてあるところにクリスタルを刺すような表示になっているが、実装されていない。

PSoC 5LPでもやりたかったが、UARTコンポーネントのinternal clockだと921,600bpsまでしか指定できない。External Clockを指定すればもっとSpeedを上げられそうだがまた気が向いたらやるかも(DataSheetには4Mbpsまで、みたいな記述がある)

Arduinoで1Mbpsはムリそう、Nucleo F401REで2Mbps。←データの取りこぼしがあるかどうかは要確認。

I2Cなら2信号、SPIでもSCKとMOSIに限れば2信号なので、ランレングス圧縮とかして通信コストを下げてやれば、なんちゃってLogic Analyzerはなんとか出来そうもするがPC側のプログラムを作るのもしんどそうだなあ(^q^;

フルスピードのUSBを使うにはどうすればいいんじゃろ。

2016年6月15日水曜日

MCP4922とNJM13700でSPI制御のDCA(Nucleo編)

Nucleo F401REで動作させてみた。

回路図


ブレッドボード図


Nucleoは3.3V駆動なので、MCP4922も3.3V駆動させた。I-V変換回路とNJM13700は別に±5Vの電源を使用。

mbed Repository:
https://developer.mbed.org/users/ryood/code/Nucleo_MCP4922_DCA_Test/

main.cpp

#include "mbed.h"
#include "rtos.h"
#include "AverageAnalogIn.h"
 
#define SPIM_TIMER_PERIOD   (5)         // 5ms
#define SPIM_RATE           (8000000)   // 8MHz
#define SPIM_WAIT           /*(wait_us(1))*/
 
// ピン・アサイン
#define PIN_CHECK       PA_10
#define PIN_DAC_CS      PA_9
#define PIN_DAC_LDAC    PA_8
 
SPI SpiM(SPI_MOSI, SPI_MISO, SPI_SCK); 
DigitalOut DacCs(PIN_DAC_CS);
DigitalOut DacLdac(PIN_DAC_LDAC);
 
DigitalOut CheckPin(PIN_CHECK);
 
AverageAnalogIn DurationIn(A0);
AverageAnalogIn DecayIn(A1);
AverageAnalogIn SustainIn(A2);
 
int16_t beatLen = 25;
 
int16_t level = 4095;
int16_t duration = 400;
int16_t decay = 100;
int16_t sustain = 2000;
 
int16_t decay_delta;
int16_t release_delta = 1000;
int16_t mod_value;
 
int16_t tick;
 
// DAC A Channelに出力
// parameter: v: 出力値(0 .. 4095)
void writeToDacA(int16_t v)
{
    // Channel A
    DacLdac = 1;
    DacCs = 0;
    SpiM.write((v >> 8) | 0x30);    // 0x30: DAC_A(0) | Vref Unbuffered(0) | Vout 1x(1) | !SHDN(1)
    SpiM.write(v & 0xff);
    SPIM_WAIT;
    DacCs = 1;
    DacLdac = 0;
}
 
// DAC B ChannelにA ChannelのVrefを出力
void outVref()
{
    // Channel B
    DacLdac = 1;
    DacCs = 0;
    SpiM.write(0x08 | 0xB0);    // 0xB0: DAC_B(1) | Vref Unbuffered(0) | Vout 1x(1) | !SHDN(1)
    SpiM.write(0x00);  
    SPIM_WAIT;
    DacCs = 1;
    DacLdac = 0;
}
 
// ADSR波形を出力
void outADSR(void const* arg)
{
    tick++;
 
    if (tick > beatLen) {
        tick = 0;
        // モジュレーション波形を初期化する
        mod_value = level;
        decay_delta = (level - sustain) / decay;
    }
 
    // 出力値補正
    if (mod_value < 0) {
        mod_value = 0;
    }
    writeToDacA(mod_value);
 
    if (tick < decay) {
        mod_value -= decay_delta;
    }
    /*
    if (tick >= duration) {
        mod_value -= release_delta;
    }
    */
    if (tick == duration) {
        mod_value = 0;
    }
}
 
int main()
{
    RtosTimer SpiM_timer(outADSR, osTimerPeriodic);
        
    SpiM.format(8, 0);
    SpiM.frequency(SPIM_RATE);
    
    outVref();
    
    SpiM_timer.start(SPIM_TIMER_PERIOD);
    
    while(true) {
        duration = DurationIn.read() * beatLen;
        decay    = DecayIn.read() * beatLen;
        sustain  = SustainIn.read() * 4095;
        
        printf("%d\t%d\t%d\r\n", duration, decay, sustain);
        
        Thread::wait(10);
    }
}


MCP4922の余っているchannel Bをchannel AのVrefとして使うようにしてみた。0x0800(2048)を出力しているので、channel AのVrefはVDD/2になり、channelAの出力は0V~VDD/2、3.3V駆動させているので0V~1.65Vの振幅になる。

プログラムで後から振幅を調整できるのがメリット。

入出力のようす



ch1:MCP4922の出力 ch2:WaveOut


ch1:WaveIn ch2:WaveOut

拡大

WaveInの振幅

差動入力電圧100mV(p-p)以下

ch1:WaveIn ch2:WaveOut

差動入力電圧100mV(p-p)以上

波形の振幅は±100mV(p-p)以内におさえないと激しく歪む。歪ました方が面白い場合もあるのでトリマーで調節できるようにしようか。

ADSR波形にLPFを入れてみる


MCP4922で出力した波形がカックカクなので、出音がブツブツ言う。なのでLPFを入れてアナログチックな波形にしてみた。

LTSpiceでシミュレーション

過渡解析

125m秒と言うのは、120BPMの四分音符の長さ。これぐらいのなまり方ちょうど良さそう。


ch1:MCP4922の出力 ch2:WaveOut

メモ:


いまだに割と簡単に発振してしまう。意図しない発振なのでつぶす必要がある。デカップリングコンデンサは入れているので、NJM13700の使ってないB側をGND入力のボルテージフォロワーにして安定させてみるとか。オーディオ信号が発振するのでNJM13700の周辺だと思う。

ADSR波形のLPFもなまり方を可変できるようにRを10kΩ程度のトリマーにしてみる?

回路図のR1とR2は兼用できないか?