製作モジュール一覧

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

2017年7月24日月曜日

PSoC 5LP内蔵DACとMCP4922の処理能力を見てみる。


ブレッドボード配線図

デバッグ情報表示用にキャラクタ液晶を接続している。P2[6:0]。

80MHz駆動するために、20MHzの水晶と22pFのCを接続。水晶をつなぐPinは固定で、P15[0]、P15[1]。

MCP4922はSPIの信号線を割り当てた。SCK P12[0]、MOSI P12[2]、CS P12[3]。

処理タイミングを計測するPinを割り当てた。P0[0]。

内蔵DACの出力はP3[0]。

内蔵DAC(VDAC8)


TopDesign

PSoC 5LPの内蔵DACはVDAC8というコンポーネントを使う。8bit精度。VDAC8の出力は弱いのでOPAMP(Follower)で増強する。

<main.c>

/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include "project.h"
#include <stdio.h>
#include <math.h>

#define TITLE_STR1  ("VDAC Test")
#define TITLE_STR2  ("20170723")

#define PI        (3.141592653589793238462)
#define AMPLITUDE (1.0)    // x * 3.3V
#define PHASE     (PI * 1) // 2*pi is one period
#define RANGE     (0x7FFF)
#define OFFSET    (0x7FFF)

// Configuration for wave output
#define BUFFER_SIZE (360)
uint16_t buffer_sine[BUFFER_SIZE];

char strBuffer[80];
    
// Create the wave buffer
void calculate_sinewave(void){
    for (int i = 0; i < BUFFER_SIZE; i++) {
        double rads = (PI * i)/180.0; // Convert degree in radian
        buffer_sine[i] = (uint16_t)(AMPLITUDE * (RANGE * (sin(rads + PHASE))) + OFFSET);
    }
}

int main(void)
{
    uint8_t cnt = 0;
    
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    LCD_Char_Start();
    UART_Start();
    VDAC8_1_Start();
    Opamp_1_Start();
    
    LCD_Char_PrintString(TITLE_STR1);
    LCD_Char_Position(1, 0);
    LCD_Char_PrintString(TITLE_STR2);
    
    sprintf(strBuffer, "\r\n%s %s\r\n", TITLE_STR1, TITLE_STR2);
    UART_PutString(strBuffer);

    calculate_sinewave();
    
    for(;;)
    {
        /* Place your application code here. */
        for (int i = 0; i < BUFFER_SIZE; i++) {
            Pin_Check1_Write(1);
            VDAC8_1_SetValue(buffer_sine[i] >> 8);
            Pin_Check1_Write(0);
        }
        
        cnt++;
    }
}

/* [] END OF FILE */

プログラムはNucleo F303で試したものとほぼおなじ処理で、事前にサイン波テーブルを生成しておいて、値を連続してDACに出力している。

DACに出力する前後でPin_Check1をH/Lさせているので、Highの時間を見れば処理に要した時間がわかる。

浮動小数点演算を行う場合、math libraryをリンクする必要がある。

メニューのProjectから[Build Setting...]を選択。ダイアログでコマンドラインオプションに「-lm」を設定する。


MCP4922


TopDesign

<main.c>

/* ========================================
 *
 * Copyright YOUR COMPANY, THE YEAR
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF your company.
 *
 * ========================================
*/
#include "project.h"
#include <stdio.h>
#include <math.h>

#define TITLE_STR1  ("MCP4922 Test")
#define TITLE_STR2  ("20170723")

#define PI        (3.141592653589793238462)
#define AMPLITUDE (1.0)    // x * 3.3V
#define PHASE     (PI * 1) // 2*pi is one period
#define RANGE     (0x7FFF)
#define OFFSET    (0x7FFF)

// Configuration for wave output
#define BUFFER_SIZE (360)
uint16_t buffer_sine[BUFFER_SIZE];

char strBuffer[80];
   
// Create the wave buffer
void calculate_sinewave(void){
    for (int i = 0; i < BUFFER_SIZE; i++) {
        double rads = (PI * i)/180.0; // Convert degree in radian
        buffer_sine[i] = (uint16_t)(AMPLITUDE * (RANGE * (sin(rads + PHASE))) + OFFSET);
    }
}

// Write to MCP4922
// v: 0..4096
void MCP4922_write_u16(uint16 v)
{
    SPIM_WriteTxData((v >> 8) | 0x30);
    SPIM_WriteTxData(v & 0xff);
    while (! (SPIM_ReadTxStatus() & SPIM_STS_SPI_DONE))
        ;
}

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */

    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    LCD_Start();
    SPIM_Start();
    UART_Start();
   
    LCD_Position(0u,0u);
    LCD_PrintString(TITLE_STR1);
    LCD_Position(1u,0u);
    LCD_PrintString(TITLE_STR2);
   
    sprintf(strBuffer, "%s %s \r\n", TITLE_STR1, TITLE_STR2);
    UART_PutString(strBuffer);
   
    CyDelay(1000u);
    //LCD_ClearDisplay();
   
    calculate_sinewave();
   
    /*
    for (int i = 0; i < BUFFER_SIZE; i++) {
        sprintf(strBuffer, "%u\r\n", buffer_sine[i]);
        UART_PutString(strBuffer);
    }  
    UART_PutString("Dump end\r\n");
    */

    for(;;)
    {
        for (int i = 0; i < BUFFER_SIZE; i++) {
            Pin_Check1_Write(1);
            MCP4922_write_u16(buffer_sine[i] >> 4);
            Pin_Check1_Write(0);
        }
        //CyDelay(1);
    }
}

/* [] END OF FILE */

処理時間の比較


VDAC8

ch2:Pin_Check1の出力

周波数は1.702MHzで1ループは約587.5ns。
DAC出力の処理時間は350.0ns

MCP4922

ch2:Pin_Check1の出力

周波数は491.0kHzで1ループは約2037ns。
DAC出力の処理時間は1800ns

DAC LOOP(ns) DAC(ns) LOOP-DAC
VDAC8 587.5 350.0 237.5
MCP4922 2037 1800 237

PSoC 5LPはCoretex-M3でCortex-M4のNucleo F446と比較すると、
Nucleo F446の内蔵DAC(12bit精度)は700nsだったので、PSoC 5LPの内蔵DACは8bit精度ではあるが処理が速い。

SPI接続のMCP4922はNucleo F446でSPIクロックが22.44MHzのとき6.74us(6740ns)だったので、こちらもかなり速い。

PSoC Creatorはグラフィカルで使い易いが、ネイティブなので高速なコードを生成してくれるようだ。

出力波形

VDAC8

サイン波の周波数:4.726kHz

MCP4922

サイン波の周波数:1.336kHz

WaveSpectraで測定

VDAC8

MCP4922

周波数が違うので単純に比較はできないが、VDAC8は高調波歪がはっきり出ている。

MCP4922のSPI信号


SCK : MOSI

ch1:MOSI ch2:SCK

SPI Masterコンポーネントのクロックに40MHzを入れているがSCKは8.676MHzになっている。

SCK : CS

ch1:CS ch2:SCK

CSがアクティブ(L)の区間は目盛りから読み取って約840ns。1周期は約2026nsでPin_Check1で測定した周期とほぼ同じ。

Github:
VDAC8
https://github.com/ryood/PSoC-Creator-4.1-Test/tree/master/VDAC_Test.cydsn

MCP4922
https://github.com/ryood/PSoC-Creator-4.1-Test/tree/master/MCP4922_Test.cydsn

<追記:2017.07.26>

SCKを拡大して測定し直したら、20MHzだった。


ch1:SCK ch2:CS

8.676MHzになっている画像も、SCKは200ns×4マス=800nsで16クロックなので

800ns / 16clk = 50ns
1 / 50ns = 20MHz

になり、たまたま少ない周波数が表示されていただけだと思う。

また、SPI Masterコンポーネントに「Enable High Speed Mode」というプロパティがあり、これをチェックしない場合最大クロック9MHz、チェックすると最大クロック18MHzまで対応する。
←したがって、SCK 20MHzは仕様外。

</追記>

2017年6月23日金曜日

Nucleo F303K8(mbed)の内蔵DACとSPI DACのMCP4922の速度を比較する

サイズの小さいNucleo F303K8でも内蔵DACとMCP4922の処理時間を比較してみた。

ブレッドボード配線図


プログラムはNucleoF446REの場合と同じです。

SPIクロックに16MHzを指定した場合



ch1:D2 ch2:SPI_SCK

MCP4922に出力完了するまで、21.04usかかっている。


ch1:SPI_MOSI ch2:SPI_SCK

SPIクロックに16MHz指定した場合、SCKの周波数は表示では15.98MHzとなっているが測定誤差で16MHzだと思う。

SPIクロックの指定を変えて測定


SPIクロック設定値 SPIクロック実測値 D2(MCP4922の処理時間) D3(内蔵DACの処理時間)
1MHz 1.0MHz 34.8us 2.8us
2MHz 2.0MHz 27.3us 2.8us
4MHz 4.0MHz 23.5us 2.8us
8MHz 8.0MHz 21.04us 2.8us
16MHz 16.0MHz 21.04us 2.8us
32MHz 32.0MHz 19.8us 2.8us
64MHz 32.0MHz 19.8us 2.8us

32MHzまでは設定値通りのSPIクロックが出力される。やはりNucleo F303は64MHz駆動で、SPIクロックはマスタークロックの1/2まで設定できるんだと思う。

MCP4922の仕様ではSPIクロックは20MHzまでだが、32MHzでも動作した。

NucleoF446REと比べると、SPIクロックを上げてもSPI通信の処理時間はあまり短縮されない。

2017年6月22日木曜日

Nucleo F446RE(mbed)の内蔵DACとSPI DACのMCP4922の速度を比較する

結論から言うと、内蔵DACの方が圧倒的に速かった。

mbedでプログラムを書いて、SPI DACのMCP4922とNucleo F446RE内蔵DACの処理時間を比較してみた。

どちらも12bit。

/*
 * 内蔵DACとMCP4922に出力するテスト
 *
 * 2017.06.21
 *
 */

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

#define SPI_CLOCK   (24000000)

SPI SpiM(SPI_MOSI, SPI_MISO, SPI_SCK);  // mosi, miso, sclk
DigitalOut Cs(D10, 1);

AnalogOut InternalDac(PA_4);

DigitalOut CheckPinD2(D2);
DigitalOut CheckPinD3(D3);

// v: 0..0x0fff
void OutMCP4922(uint16_t v)
{
    //v &= 0x0fff;

    // Channel A
    Cs = 0;
    SpiM.write(v >> 8 | 0x30);    // 0x30: DAC_A(0) | Vref Unbuffered(0) | Vout 1x(1) | !SHDN(1)
    SpiM.write(v & 0xff);
    Cs = 1;
}


// v: 0..0xffff
void OutInternalDac(uint16_t v)
{
    InternalDac.write_u16(v);
}

int main()
{
    SpiM.format(8, 0);
    SpiM.frequency(SPI_CLOCK);

    uint16_t count = 0;
    for (;;) {
        uint16_t v12 = count >> 4;
        uint16_t v16 = count;
        
        CheckPinD2 = 1;
        OutMCP4922(v12);
        CheckPinD2 = 0;

        CheckPinD3 = 1;
        OutInternalDac(v16);
        CheckPinD3 = 0;
        
        count += 0x100;

        Thread::wait(1);
    }
}

Nucleoをmbedで使って、SPIの周波数設定をするとちょうどの周波数にはならない。おそらくマスタークロックの分周比なのだと思う。

こういうハードウェア寄りの使い勝手はmbedの弱点ではあります。

SPI::frequency()でSPIクロックの周波数を変えて測定してみた。

ブレッドボード配線図


MCP4922にはLDAC pinがあって、これをH→LするとA ChannelとB Channelが同期して
同時に電圧が出力されるが、LDACをL(GND)に落としておくと、CSのアクティブ/インアクティブで制御できるようだ。配線や処理時間のコストがシビアな場合は役に立つと思う。(A Channelだけしか試してないです)

1MHz

ch1:MCP4922 ch2:内蔵DAC

MCP4922から出力すると、27.3usかかる。一方内蔵DACは700ns(0.7us)で済む。


ch1:MCP4922 ch2:SPI_SCK

1MHzを設定した場合はMCP4922との通信に使っているSPIのクロック周波数は643kHz

24MHz

ch1:MCP4922 ch2:内蔵DAC

SPIのクロック周波数を上げるとMCP4922でも6.8us(≒147kHz)で済む。


ch1:MCP4922 ch2:SPI_SCK

SPIのクロック周波数は22.44MHz。クロックが高周波数になるとダンピング抵抗を入れていても波形が怪しくなる。

MCP4922の最大周波数は20MHzなので仕様外だが、VOUTAから波形は出力される。

Zoom Out

ch1:MCP4922 ch2:SPI_SCK

高クロックになると、8bitずつのSPI通信の間隔が開くようだ。

<追記:2017.06.25>
間隔が開くと言うより、SPI::write()のオーバーヘッドが2~3us程度ある?
</追記>

設定値と出力周波数


設定値 SPIクロック 処理時間
1MHz 643.8kHz 27.3us
2MHz 1.154MHz 16.76us
4MHz 2.810MHz 11.32us
6MHz 5.762MHz 8.76us
12MHz 11.29MHz 7.36us
24MHz 22.44MHz 6.74us

2017年6月14日水曜日

NucleoF303K8を買ってみた。


(左)Nucleo F401RE、(右上)Arduino Pro Mini、(右下)Nucleo F303K8

画像の通りかなりコンパクトで、USBシリアル変換基板も不要。基板は両面実装でメインMPUのSTM32F303のほかに裏面にSTM32F103が載っている。

STMF303はCorete-M4ベースでDACも2つ(3ch)搭載されている。

内蔵DAC


mbedでプログラムを書いてDACの出力を見てみた。

main.cpp

#include "mbed.h"

AnalogOut my_output1(PA_4);
//AnalogOut my_output2(PA_5);
//AnalogOut my_output3(PA_6);

#define PI        (3.141592653589793238462)
#define AMPLITUDE (1.0)    // x * 3.3V
#define PHASE     (PI * 1) // 2*pi is one period
#define RANGE     (0x7FFF)
#define OFFSET    (0x7FFF)

// Configuration for sinewave output
#define BUFFER_SIZE (360)
uint16_t buffer[BUFFER_SIZE];

void calculate_sinewave(void);

int main() {
    printf("Sinewave example\n");
    calculate_sinewave();
    while(1) {      
        // sinewave output
        for (int i = 0; i < BUFFER_SIZE; i++) {
            my_output1.write_u16(buffer[i]);
            //my_output2.write_u16(buffer[i]);
            //my_output3.write_u16(buffer[i]);
        }
    }
}

// Create the sinewave buffer
void calculate_sinewave(void){
  for (int i = 0; i < BUFFER_SIZE; i++) {
     double rads = (PI * i)/180.0; // Convert degree in radian
     buffer[i] = (uint16_t)(AMPLITUDE * (RANGE * (cos(rads + PHASE))) + OFFSET);
  }
}

mbed repository:
https://developer.mbed.org/users/ryood/code/Nucleo_sinewave_output/ revision:0

3chあるということで、3つAnalgOutを作ってみたがmbedでは同時に2つ以上使えないようだ。いろいろ組み合わせてみたがうまくいかなかった。それぞれ1chだけなら利用可能。

出力波形

PA_4

F:922.5Hz

PA_5

F:932.8Hz

PA_6

F:924.2Hz

各chで出力された周波数が若干異なる。

PA_4では922.5Hzのサイン波が出力された。以前Nucleo F446REでテストした時は、6.4kHzのサイン波が出力されたので、比較するとかなり遅い。

1周期のサンプル数(BUFF_SIZE)が360なので、サンプリング周波数は約332kHz(3u秒)となる。

F446REの時に見られた波形のつぶれはなく、フルレンジできれいなサイン波が出力されている。

単精度浮動小数点数点演算


駆動クロックははっきりしないが、回路図と基板を見比べてみるとF303用の外付けのクリスタル(X1)が実装されていない。内蔵RCで動作しているとすると、PLLで逓倍して最大64MHzとなっているので、64MHzとして比較してみた。

No Device No2 Op time(us) clock(MHz) period(us) clock:op
3 Nucleo F303K8 1 div 0.357 64 0.015625 22.848
3 Nucleo F303K8 2 sinf 2.011 64 0.015625 128.704
3 Nucleo F303K8 3 cosf 1.922 64 0.015625 123.008
3 Nucleo F303K8 4 expf 3.202 64 0.015625 204.928
3 Nucleo F303K8 5 logf 2.506 64 0.015625 160.384
3 Nucleo F303K8 6 sqrtf 0.739 64 0.015625 47.296

単精度浮動小数点数の割り算


1処理あたりの実行時間

1処理あたりのクロック数

単精度浮動小数点数のsinf()


1処理あたりの実行時間

1処理あたりのクロック数

FPUが載っているので単精度浮動小数点演算は十分速いと思う。

メモ:


NucleoF303K8の駆動クロックはわからないが、STM F4もSTM F3もFPUの割り算の性能が変わらないとすると64MHzなのかも?

2017年1月9日月曜日

Nucleo F446RE 内蔵DACのテスト

Nucleo F446REを仕入れたのでテストしてみた。F401REのパワーアップ版という感じで、駆動クロックが180MHzになっている(F401REは84MHz)。またDACが内蔵されているのでmbedのAnalogOutが使える。

mbedのオンライン・コンパイラでPlatformにNucleo F446REを選ぶとDACを使ったテンプレートが選択できるようになる。「Outoput a sinewave using DAC.」というテンプレートをもとにテストしてみた。



#include "mbed.h"

AnalogOut my_output1(PA_4);
AnalogOut my_output2(PA_5);

#define PI        (3.141592653589793238462)
//#define AMPLITUDE (1.0)    // x * 3.3V
#define AMPLITUDE (0.8)    // x * 3.3V
#define PHASE     (PI * 1) // 2*pi is one period
#define RANGE     (0x7FFF)
#define OFFSET    (0x7FFF)

// Configuration for sinewave output
#define BUFFER_SIZE (360)
uint16_t buffer[BUFFER_SIZE];

void calculate_sinewave(void);

int main() {
    printf("Sinewave example\n");
    calculate_sinewave();
    while(1) {     
        // sinewave output
        for (int i = 0; i < BUFFER_SIZE; i++) {
            my_output1.write_u16(buffer[i]);
            my_output2.write_u16(buffer[i]);
            //wait_us(10);
        }
    }
}

// Create the sinewave buffer
void calculate_sinewave(void){
  for (int i = 0; i < BUFFER_SIZE; i++) {
     double rads = (PI * i)/180.0; // Convert degree in radian
     buffer[i] = (uint16_t)(AMPLITUDE * (RANGE * (cos(rads + PHASE))) + OFFSET);
  }
}


bufferにサインテーブルを作って、メインループでDACからサイン波を出力するというもの。F446REでDAC出力として選択できるPinは

DAC1: PA_4
DAC2: PA_5

で両方同時に使えた。

Amplitudeを1.0にしてPA_4から出力


ch1:PA_4

約6.4kHzで出力できているが、サイン波の上下で波形がつぶれて歪んでいる。

BUFFER_SIZEが360なので、6.4kHzのサイン波が出力できているということはサンプリング・レートは約2.3MHz(434ns)で、mbedで使った場合このあたりが上限。


Amplitudeを0.9にしてPA_4から出力



ch1:PA_4

Amplitudeを0.9にして出力すると歪はほとんどなくなった。解像度はわるくなるが、レール電圧付近は使わない方が良さそう。


Amplitudeを0.9にしてPA_4とPA_5から出力




ch1:PA_4 ch_2:PA_5

2つのDACから同時に波形を出力できたが、Amplitudeを0.9にしてもPA_5の方の上がつぶれてしまった。PA_5はNucleoボード上でLEDがつながっているのでこれが影響してるのかも?出力電圧が上がった時に、LEDに電流がとられて電圧を十分にスイングできていない可能性がある。

また、2つのDAC同時に出力するとサイン波の周波数が約3.3kHzに低下。1つだけ使った場合のおおよそ1/2なので、DAC1とDAC2はインターリーブなのかも。


Amplitudeを0.9にしてPA_5単体で出力



ch2:PA_5

PA_5単体で出力しても同様。周波数は約6.4kHzに上がっている。


Amplitudeを0.8にしてPA_5単体で出力



ch2:PA_5

振幅の最大値が3V程度になるようにAmplitudeを0.8にすると波形の歪は改善される。