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は兼用できないか?