製作モジュール一覧

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

2017年11月29日水曜日

Nucleo(mbed)で電圧を監視する

MAU106±5V電源でDigital+5Vの出力を1/2に分圧して取り出せるようにしているので、Nucleoでその電圧を測定して電圧監視するClassを作ってみた。

Class内でTickerを使うには、attach()を
t.attach(this, &VoltageMonitor::blinkLed, 0.5);
と、第一引数に自分自身を指定する必要がある。(参考:「https://os.mbed.com/forum/mbed/topic/1964/」)
Warning: Function "mbed::Ticker::attach(T *, M, float) [with T=VoltageMonitor, M=void (VoltageMonitor::*)()]" (declared at <a href="#" onmousedown="mbed_doc_goto('/VoltageMonitor_Test//extras/mbed_e7ca05fa8600/drivers/Ticker.h', '102'); return false;">/extras/mbed_e7ca05fa8600/drivers/Ticker.h:102</a>) was declared "deprecated" in "VoltageMonitor/VoltageMonitor.h", Line: 67, Col: 24
というWarningが出るが、mbed-srcのdirvers/Ticker.hに
    template<typename T, typename M>
    MBED_DEPRECATED_SINCE("mbed-os-5.1",
        "The attach function does not support cv-qualifiers. Replaced by "
        "attach(callback(obj, method), t).")
    void attach(T *obj, M method, float t) {
        attach(callback(obj, method), t);
    }
と宣言されていて、cv-qualifiers(constやvolatileの修飾子)をサポートしていないので(mbed-os-5.1以降?)非推奨というような事が書いてある。

mbed OS 5はなんだかイマイチこなれていない感があるので今回はmbed 2で使うことにして、
void attach(T *obj, M method, float t)
をそのまま使うことにした。そもそも
attach(callback(obj, method), t);
の「callback()」が何なのかよくわからない。←そのうち調べるかも?

ブレッドボード配線図

Test Program
https://os.mbed.com/users/ryood/code/VoltageMonitor_Test/ Revision:1

VoltageMonitor Class
https://os.mbed.com/users/ryood/code/VoltageMonitor/ Revision:1

VoltageMonitor Classのコンストラクタで
    // パラメータ
    // _ain: 電圧監視用ADC
    // _vdd: VDDの電圧値(実測値を指定)
    // _loThreshold: 電圧低下閾値
    // _hiThreshold: 過電圧閾値
    // _pLed: 警告用LED (NULL: LEDを使用しない)
VoltageMonitor(AnalogIn* _pAin, float _vdd, float _loThreshold, float _hiThreshold, DigitalOut* _pLed=NULL)
とパラメータを指定し、check()を呼び出す。
    // 返り値
    // -1: 電圧低下
    //  0: 正常
    //  1: 過電圧
    int check()
Nucleo自体には内部レファレンスがあるが、3.3VのVDDを実際にテスタ等で測定して基準電圧として使うことにした。

動作としては電圧が低下した場合はLEDがゆっくり点滅、過電圧の場合ははやく点滅、正常時は常時点灯としている。

内蔵基準電圧


Nucleoをmbedで使って内蔵基準電圧を使いたい場合は
AnalogIn   vrefint(ADC_VREF);
と指定すれば使えるようだ。

参考「https://os.mbed.com/questions/75910/How-to-get-the-value-of-inbuilt-vref-int/

内蔵基準電圧は1.21Vなので今回は使用していない。(入力が0V~2.7V以上を想定)

2017年11月13日月曜日

Nucleo F401REの3.3V電源を破壊してしもうた(T_T;

製作中の±5V電源にNucleoをつないでテストしているときに、NucleoのE5Vピンに+5Vかけるのを、間違えて±5Vの両極(10V分)につないでしまい、NucleoがPCから認識しなくなった。


Nucleoをつないで認識されるストレージに、FAIL.TXTというのが現れ
The interface firmware FAILED to reset/halt the target MCU
と書いてある。

MCUを壊したかと思ったが、基板をチェックすると3.3V LDOを破壊してしまったようだ。

Nucleoボード全体では、3.3V LDOは2個あり、


本体側の3.3V LDOの回路図

ST-LINK側の3.3V LDOの回路図

テスタで電圧を測ると、U1の「+3V3_ST_LINK」は3.29V、「Vin」は4.43V。U4の「+3V3」は0.303V、「+5V」は4.72V。

どうやらU4が壊れてしまったようだ。

試しにU4の近くのJP6のVDD側に外部から3.3V供給してUSB接続してみると、FAIL.TXTは出なくなり、Lチカのプログラムが動作した(^q^/

ST_LINK側のU1から3.3Vを供給するとか3V3 LDOを外付けすれば復旧できそうですが…

2017年5月19日金曜日

Nucleo(mbed OS5)のSPI通信を検証してみる。

ベースマシンではNucleo(mbed)のSPIでIC間の通信を行っている。UI系とシンセの制御系でNucleoボードを分けてみたが、ボード間のSPI通信でエラーが発生してしまい、分離するのをあきらめた。(参考:このブログの2016年10月あたり)

mbed OS 5がリリースされた時期も重なって何かと混乱してしまった。

検証のため、mbed OS 5を使ってNucleo同士でSPI通信させてみてみた。

Master側プログラム
https://developer.mbed.org/users/ryood/code/mbed-os_SPITest_Master/

#include "mbed.h"

SPI spiM(D11, D12, D13);
DigitalOut cs(D10);

int main()
{
    printf("SPIMaster Test\r\n");

    spiM.format(8, 0);
    spiM.frequency(1000000);

    uint8_t sdata = 0;

    for (;;) {
        cs = 0;
        uint8_t rdata = spiM.write(sdata);
        cs = 1;

        printf("Master: sdata:%d rdata:%d ", sdata, rdata);

        uint8_t err = sdata - rdata;
        if (err != 1) {
            printf("err:%d", err);
        }
        printf("\r\n");

        sdata++;
        
        //wait_ms(100);
    }
}

Slave側プログラム
https://developer.mbed.org/users/ryood/code/mbed-os_SPITest_Slave/

#include "mbed.h"

#define UART_TRACE  (1)

SPISlave spiS(D11, D12, D13, A2); // mosi, miso, sclk, ssel

int main()
{
    printf("SPISlave Test\r\n");
   
    spiS.format(8, 0);
    spiS.frequency(1000000);
   
    uint8_t pdata = 0;
   
    for (;;) {
        if (spiS.receive()) {
            uint8_t rdata = spiS.read();
           
           
            #if (UART_TRACE)
            printf("Slave: rdata:%d", rdata);
           
            uint8_t err = rdata - pdata;
            if (err != 1) {
                printf(" err: %d", err);
            }
           
            printf("\r\n");
            #endif
           
            pdata = rdata;
           
            uint8_t sdata = rdata;
            spiS.reply(sdata);
        }
    }
}

NucleoのArduino Header同士をジャンパ線で直結すると通信はほぼほぼうまくいく。


UART出力

ブレッドボードを使って少し意地悪な状態にするとエラーが多発


UART出力

信号波形

直結

ch1:MOSI ch2:SCK

意地悪

ch1:MOSI ch2:SCK

意地悪のほうはSCKとMOSIの波形がずれているように見えるが、よくわからない(@@;
←MOSIは無通信時(CSがアクティブになっていない時)の状態は不定なので、クロック、信号線、CSを同時に測定しないとはっきりしない。

メモ:


まだわからないことだらけだが、SPI通信は配線の状況によって通信エラーが発生することはわかった。

ベースマシンではNucleoからシンセに回しているSPI信号をバス・バッファICのTC74HC541を入れて補強しているが、ケーブルが長いので通信エラーが発生している可能性は大きい。

できることならシンセを制御しているMPUはシンセの近くにしてUI系のMPUとは分けた方がいいのかも(@@?

2017年1月11日水曜日

Nucleo F446REとNucleo F401REの浮動小数点演算比較

以前F401REでやった浮動小数点演算のサイン波テーブル生成のプログラムでF401REとF446REの実行時間を比較してみた。

F446REの駆動クロックが180MHzで、F401REの駆動クロックが84MHzなので単純に割り算すると

2.142857143

になる。

プログラムは前回と同じもので比較。

main.cpp
#include "mbed.h"  
   
 //------------------------------------  
 // Hyperterminal configuration  
 // 9600 bauds, 8-bit data, no parity  
 //------------------------------------  
 #include <math.h>  
   
 #define M_PI      (3.14159265358979323846) /* pi double*/  
 #define M_PI_f     (3.1415926f) /* pi float */  
 #define TABLE_LENGTH  (8192)  
   
 Serial pc(SERIAL_TX, SERIAL_RX);  
 Timer timer0;  
 uint8_t waveTable_0[TABLE_LENGTH];  
   
 void genSineTable_double(uint8_t *table, int length)   
 {   
   int i;   
   int16_t v;   
   int8_t* p8;   
   for (i = 0; i < length / 2; i++) {   
      v = (int16_t)(sin(2.0 * M_PI * i * 2 / length) * 32767);   
      p8 = (int8_t *)&v;   
      table[i*2] = *(p8+1);   
      table[i*2+1] = *p8;   
   }   
 }  
   
 void genSineTable_float(uint8_t *table, int length)   
 {   
   int i;   
   int16_t v;   
   int8_t* p8;   
   for (i = 0; i < length / 2; i++) {   
      v = (int16_t)(sinf(2.0f * M_PI_f * i * 2 / length) * 32767);   
      p8 = (int8_t *)&v;   
      table[i*2] = *(p8+1);   
      table[i*2+1] = *p8;   
   }   
 }  
     
 int main() {  
   timer0.start();  
     
   // 単精度浮動小数点演算 (with FPU)  
   pc.printf("start float: TABLE_SIZE:%d\r\n", TABLE_LENGTH);  
   timer0.reset();  
   genSineTable_float(waveTable_0, TABLE_LENGTH);  
   pc.printf("%d\r\n", timer0.read_us());  
     
   // 倍精度浮動小数点演算  
   pc.printf("start double: TABLE_SIZE:%d\r\n", TABLE_LENGTH);  
   timer0.reset();  
   genSineTable_double(waveTable_0, TABLE_LENGTH);  
   pc.printf("%d\r\n", timer0.read_us());  
 }  

単精度浮動小数点演算


TABLE_LENGTH F401RE float (us) F446RE float (us) F401RE vs F446RE
512 372 181 2.055248619
1024 743 359 2.069637883
2048 1486 718 2.069637883
4096 2972 1433 2.073970691
8192 5943 2866 2.073621773




倍精度浮動小数点演算


TABLE_LENGTH F401RE double (us) F446RE double (us) F401RE vs F446RE
512 6753 3275 2.061984733
1024 13550 7473 1.813194166
2048 27129 14968 1.812466595
4096 54356 29957 1.814467403
8192 108877 59936 1.816554325



F401REは、単精度浮動小数点演算(float)は前回と同じ結果だが、倍精度浮動小数点数演算(double)はかなり結果が良くなっている。mbedのNucleoの浮動小数点数演算のライブラリがチューニングされているのかも?

float vs double


TABLE_LENGTH F401RE F446RE F401RE vs F446RE
512 18.15322581 18.09392265 1.003277518
1024 18.23687752 20.81615599 0.87609247
2048 18.256393 20.84679666 0.875740926
4096 18.28936743 20.90509421 0.874876107
8192 18.32020865 20.91277041 0.876029732

単精度と倍精度の比較では、F401REで約18倍、F446REで約21倍、単精度のほうが高速のようだ。

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にすると波形の歪は改善される。

2016年12月9日金曜日

Raspberry Pi 3とNucleo F401REでSPI通信させてみる。

Sparkfunのチュートリアルhttps://learn.sparkfun.com/tutorials/raspberry-pi-spi-and-i2c-tutorialを参考にした。

配線図




3.3V系同士なので信号レベルは気にせず接続できる。

Raspberry Pi 3B(Master)


Raspberry PiのOSはNOOBSで、SPI通信のライブラリにはWiringPiををインストールして使った。

sparkfunの記事にある通り、
>sudo raspi_config
で、SPIを有効化した。

spitest2.cpp

#include <iostream>
#include <errno.h>
#include <wiringPiSPI.h>
#include <unistd.h>

using namespace std;

static const int CHANNEL = 1;

int main()
{
   int fd, result;
   unsigned char buffer[100];

   cout << "Initializing" << endl ;

   fd = wiringPiSPISetup(CHANNEL, 500000);

   cout << "Init result: " << fd << endl;

   unsigned char cnt = 0;
   while (1) {
           buffer[0] = cnt;
           cnt++;
           result = wiringPiSPIDataRW(CHANNEL, buffer, 1);
           cout << "result: " << result << " recieve: " << int(buffer[0]) << endl;
           usleep(10000);       // wait 10ms
   }
}

オシロで信号を捕捉しやすくするために、while(1)の無限ループでSPI通信をさせたが、ラズパイのCPUが発熱してPCとのTCP/IP通信のエラーが発生したのでusleep()を使ってウェイトをかけるようにした。


コンパイル


>g++ spitest2.cpp -lwiringPi -o spitest2


Nucleo F401RE (Slave)


参考「Arduino UnoをSPI Master、Nucleo F401REをSPI Slaveにして通信してみる。

今回はタイミングがシビアではないのでprintfで受信結果を表示するようにした。

main.cpp

#include "mbed.h"

#include "SPISlave.h"


SPISlave SpiS(D11, D12, D13, A2);    // mosi, miso, sclk, ssel


int main()

{

    printf("*** SPI Test: Raspberry Pi 3:Master Nucleo:Slave ***\r\n");

   

    SpiS.format(8, 0);

    SpiS.frequency(500000);

   

    uint8_t cnt = 0;

    SpiS.reply(cnt);

    while (1) {

        if (SpiS.receive()) {

            int v = SpiS.read();

            printf("%d\r\n", v);

            SpiS.reply(cnt++);

        }

    }

}

実行


Raspberry Piで

./spitest2

とコマンドを打って実行すると、ラズパイ用のコンソールと、Nucleoからのシリアル出力用のコンソールから送受信結果が
出力される。



SPI信号波形


MOSI


ch1:MOSI ch2:SCK

MISO


ch1:MISO ch2:SCK

CS


ch1:CS ch2:SCK


MISOとMOSIは1kΩのRでプルダウンしているが、これを入れないとHi-Zになったとき(?)に信号波形が崩れるため。SCKとCSはプルダウンしなくても平気な感じだ。

MOSI(プルダウンなし)


MISO(プルダウンなし)


2016年11月25日金曜日

I2C EEPROM(AT24C1024B)をArduino UnoとNucleo F401RE(mbed)で使う。

1MbitのI2C EEPROMのAT24C1024Bの手持ちがあったのでテストしてみた。

仕様としては、I2C接続で電源電圧は2.5V~5.5VなのでArduinoでもNucleoでも気軽に使える。

書き込みはバイト単位、ページ単位、読み込みはランダムリードとシーケンシャルリードができる。

メモリの構成は、512Page × 256Byteになっていてアドレス指定は17bitで行う。

バイト単位の書き込み


1byte目 デバイス・アドレス ( 0x1010 | A2 | A1 | P0 | W )
2byte目 上位アドレス
3byte目 下位アドレス
4byte目 書き込みデータ

となっていて、1byte目のデバイスアドレスは、0x1010は固定、A2/A1はピンの接続と合わせて指定する(1個だけ使う場合はGNDに落として0を指定すると楽)。アドレス17bitのうち最上位1bitをP0で指定する。

バイト単位の読み込み


読み込みの場合は書き込みの1~3byte目と同じものをWモードで送信してアドレスを指定。4byte目のデータは送らずに、再びSTARTコンディションにしてRモードでデバイスアドレスを送信するとデータが返ってくる仕様。

Arduino


配線図


デバイスアドレスのA1/A2はGNDに落とし、I2CのSCL、SDAは1kΩでプルアップした。

ライブラリはArduinoのPlaygroundの「Arduino AT24C1024 I2C EEPROM Library」を使わせてもらった。(バイト単位の読み書きのみに対応)

http://playground.arduino.cc/Code/I2CEEPROM24C1024
https://github.com/jwhiddon/AT24C1024

ExampleのAT24C1024_EEPROM_Benchmarkをそのまま動作させることができた。全アドレスを読み書きするのでシリアル・コンソールにしばらく何も表示されないが、少し待てば「.」が表示されていく。

シリアル出力結果

AT24C1024 EEPROM Library Benchmark Sketch
--------------------------------
Write By Byte Test:
Writing data:...........................DONE
Total Time (seconds): 722
Write operations per second: 181
--------------------------------
--------------------------------
Read By Byte Test:
Reading data:..
Address: 9265 Should be: 25 Read val: 255
.........................DONE
Total Test Time (secs): 82
Read operations per second: 1598
Total errors: 1
--------------------------------

書き込みは181Byte/s、読み込みは1,598Byte/sで、1バイト読み書きに失敗したようだ。

I2C通信のようす

書き込み

ch1:SDA ch2:SCL

読み込み

Nucleo F401RE(mbed)

配線図


こちらも1kΩでプルアップ。

ライブラリはKenji Araiさんの「AT24C1024」ライブラリ(https://developer.mbed.org/users/kenjiArai/code/AT24C1024/)を使わせてもらった。こちらはページ単位の読み書きにも対応している。

テスト・プログラム main.cpp

#include "mbed.h"
#include "AT24C1024.h"

I2C         i2c(D14, D15);      // SDA, SCL
AT24C1024   at24c1024(i2c);     // Atmel 1Mbit EE-PROM

DigitalOut led1(LED1);

int main()
{
    printf("\r\n*** AT24C1024 Test ***\r\n");

    // Byte Read/Write
    // 
    uint8_t dt = 0x55;
    printf("Byte Write: %02x\r\n", dt);
    at24c1024.write(0, dt);     // write addr=0 data=dt
    wait_ms(5);
    dt = at24c1024.read(0);     // read addr=0
    printf("Byte Read: %02x\r\n", dt);

    // Page Read/Write
    // 
    uint8_t eep_buf[258];
    for (int i = 0; i < 256; i++) {
        eep_buf[i] = i;
    }
    printf("Page Write: 0..255\r\n");
    AT24C_STATUS status = at24c1024.write_page(0x1ff00, eep_buf, sizeof(eep_buf));
    wait_ms(5);
    printf("Status: %d\r\n", status);
    printf("Page Read:\r\n");
    status = at24c1024.read_page(0x1ff00, eep_buf, sizeof(eep_buf));
    printf("Status: %d\r\n", status);
    for (int i = 0; i < sizeof(eep_buf); i++) {
        printf("%d ", eep_buf[i]);
    }
    printf("\r\n");

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

AT24C1024のWriteはDATASHEETを見ると5msかかるようなのでwait_ms(5)を入れている。waitをかけないと読み込みに失敗する。また、ページ単位の読み書きのバッファ、はメンバ関数内部でI2C通信用に2バイト使うので258バイト確保する必要がある。データはインデックス0から埋める。

<追記:2016.11.25>

write_page()とread_page()はエラーステータス(AT24C_STATUS)を返しますが、I2C通信がうまくいったかどうかは見ない仕様のようです。AT24C_OK = 0が返ってきても通信が失敗している場合もあります。

</追記>

I2C通信のようす


ch1:SDA ch2:SCL

メモ:


SDカードに比べるとR/Wの速度は遅いが、実装面積が節約できるし値段も安い。1Mbitの品種を使っているが、ベースマシンのデータ保存用ならそんなには必要ない。

2016年11月22日火曜日

Nucleo F401RE(mbed)でSDカードを使う

Nucleo F401REでSDカードを動作させてみた。以前、Arduinoで動かせたので(「ArduinoでSDカードを使ってみる。74HC4050でレベルシフト」)軽く考えていたが結構ハマってしまった。

まず、mbedオフィシャルの「SDFileSystem https://developer.mbed.org/users/mbed_official/code/SDFileSystem/」はうまく動かせなかった。SPI信号は出力されるようだが、カードを認識してくれなかった。

かなりあ~だこ~だやった末、Neil Thiessenさんがre-writtenされたバージョンのSDFileSystem (https://developer.mbed.org/users/neilt6/code/SDFileSystem/)で動作させることができた。

配線図



Nucleoは3.3V駆動なので信号線は直結した。

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

main.cpp

/*
 * SDFileSystem Test
 * 
 * Library
 * SDFileSystem: https://developer.mbed.org/users/neilt6/code/SDFileSystem/ Revision:26
 * mbed: Revision: 124
 *
 * 2016.11.22 created
 *
 */

#include "mbed.h"
#include "SDFileSystem.h"

//Create an SDFileSystem object
SDFileSystem sd(D11, D12, D13, D10, "sd");

int main()
{
    //Mount the filesystem
    sd.mount();

    //Perform a write test
    printf("\nWriting to SD card...");
    FILE *fp = fopen("/sd/sdtest.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "We're writing to an SD card!");
        fclose(fp);
        printf("success!\n");
    } else {
        printf("failed!\n");
    }

    //Perform a read test
    printf("Reading from SD card...");
    fp = fopen("/sd/sdtest.txt", "r");
    if (fp != NULL) {
        char c = fgetc(fp);
        if (c == 'W')
            printf("success!\n");
        else
            printf("incorrect char (%c)!\n", c);
        fclose(fp);
    } else {
        printf("failed!\n");
    }

    //Unmount the filesystem
    sd.unmount();
}

APIドキュメントに載っているExampleから

SDFileSystem sd(D11, D12, D13, D10, "sd");

とNucleoのArduino HeaderのPinNameに変更した。

I/OエキスパンダーのMCP23S17とSPIバスを共用するテスト


製作中のベースマシンで使う予定だが、3つあるSPIをすでに全部使ってしまっている。

SPI1: シンセの制御
SPI2: Graphic LCD
SPI3: I/Oエキスパンダー

SPI1はシンセ本体用なのであまり他と干渉させたくないし、SPI2のLCDも処理が重いので専用にしたい。I/Oエキスパンダーが一番余裕がありそうなのでSPI3を共用することにした。

が、SDFileSystemは

SDFileSystem (PinName mosi, PinName miso, PinName sclk, PinName cs, const char *name, PinName cd=NC, SwitchType cdtype=SWITCH_NONE, int hz=1000000)

とPinNameを渡してSPIオブジェクトをコンストラクタ内で生成し、privateなメンバ変数として持つ仕様になっているのでそのままでは共用(アクセス)できない。

しかたがないので、

    SPI* SpiPointer() { return &m_Spi; }

というアクセサを追加して、外部からSPIオブジェクトを参照出来るようにした。ホントは人様の作ったライブラリに手を入れるのは好ましくないが、慎重に使うことにする。

配線図


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

main.cpp

/*
 * SDFileSystem Binary R/W Test
 *
 * Library
 * SDFileSystem: https://developer.mbed.org/users/neilt6/code/SDFileSystem/ Revision:26
 * mbed: Revision: 124
 * mbed-rtos: Revision: 117
 *
 * 2016.11.22 created
 *
 */
 
#include "mbed.h"
#include "rtos.h"
#include "SDFileSystem.h"
 
#include "ExioMcp23s17.h" 
#include "ExioInBuffer.h"
#include "ExioBufferedIn.h"
#include "ExioBufferedDebounceIn.h"
 
typedef struct {
    uint8_t x;
    uint8_t y;
    uint8_t z;
} DataT;
 
//SPI Spi(PC_12, PC_11, PC_10); // SPI3: mosi, miso, sclk
SDFileSystem sd(PC_12, PC_11, PC_10, PA_14, "sd"); // SPI3: mosi, miso, sclk, cs
 
void writeSD(DataT* data)
{
    //Mount the filesystem
    sd.mount();
 
    //Perform a write test
    printf("\r\nWriting binary data to SD card...");
    FileHandle* file = sd.open("Test File.bin", O_WRONLY | O_CREAT | O_TRUNC);
    if (file != NULL) {
        if (file->write(data, sizeof(*data)) != sizeof(*data)) {
            error("write error!\r\n");
        }
        if (file->close()) {
            printf("failed to close file!\r\n");
        } else {
            printf("done!\r\n");
        }
    } else {
        printf("failed to create file!\r\n");
    }
 
    //Unmount the filesystem
    sd.unmount();
}
 
void readSD(DataT* data)
{
    //Mount the filesystem
    sd.mount();
 
    //Perform a read test
    printf("\r\nReading binary data from SD card...");
    FileHandle* file = sd.open("Test File.bin", O_RDONLY);
    if (file != NULL) {
        if (file->read(data, sizeof(*data)) != sizeof(*data)) {
            error("read error!\r\n");
        }
        if (file->close()) {
            printf("failed to close file!\r\n");
        } else {
            printf("done!\r\n");
        }
    } else {
        printf("failed to open file!\r\n");
    }
 
    //Unmount the filesystem
    sd.unmount();
}
 
int main()
{
    DataT data, rdata;
 
    data.x = 0xff;
    data.y = 0x55;
    data.z = 0xaa;
 
    printf("*** Test SDFileSystem & ExioBufferedDebounceIn ***\r\n");
    
    // ExioMcp23s17(int hardwareaddress, SPI& spi, PinName nCs, PinName nReset);
    ExioMcp23s17 Exio(0x00, *sd.SpiPointer(), PD_2, PA_13);
    
    // Reset MCP23S17 (初期化時にreset()が必要)
    Exio.reset();
 
    ExioInBuffer inBufferB(&Exio, ExioPortB);
    ExioBufferedDebounceIn inB[] = {
        ExioBufferedDebounceIn(&inBufferB, 0),
        ExioBufferedDebounceIn(&inBufferB, 1),
        ExioBufferedDebounceIn(&inBufferB, 2),
        ExioBufferedDebounceIn(&inBufferB, 3),
        ExioBufferedDebounceIn(&inBufferB, 4),
        ExioBufferedDebounceIn(&inBufferB, 5),
        ExioBufferedDebounceIn(&inBufferB, 6),
        ExioBufferedDebounceIn(&inBufferB, 7)
    };
 
    // Start Timers
    inBufferB.run(10);
    for (int i = 0; i < 8; i++) {
        inB[i].set_debounce_us(10000);
    }
 
    // SDカードの読み書きの前にExioのSPIアクセスを停止
    inBufferB.stop();
      
    writeSD(&data);
    readSD(&rdata);
    
    // ExioのSPIアクセスを再開
    inBufferB.run(10);
 
    printf("data: x:%02x y:%02x z:%02x\r\n", rdata.x, rdata.y, rdata.z);
    
    while(1) {
        uint8_t x = 0;
        for (int i = 0; i < 8; i++) {
            int vb = inB[i].read();
            x |= (vb << i);
        }
        if (x != 0 && data.x != x) {
            data.x = x;
            printf("Write to SD: x:%02x y:%02x z:%02x\r\n", data.x, data.y, data.z);
            // SDカードの読み書きの前にExioのSPIアクセスを停止
            inBufferB.stop();
            writeSD(&data);
            readSD(&rdata);
            // ExioのSPIアクセスを再開
            inBufferB.run(10);
            printf("Read from SD: x:%02x y:%02x z:%02x\r\n", rdata.x, rdata.y, rdata.z);
        }
    }
}

MCP23S17は通信コストを下げるためにバッファリングするようにした自作のExioBufferedControllerライブラリを使った。ExioBufferedDebounceInクラスはチャタリング対策したもの。

オブジェクトの生成がかなりめんどくさいことになっているが、SPIオブジェクト(の参照)を渡して初期化できるようになっている。

SPIバスを共用するのでSDカードを使う場合は、

inBufferB.stop();

としてSPI経由のMCP23S17への定期的な問い合わせを停止する。

また、SDカードの読み書きは低レベルなファイルハンドルを使ってバイナリの読み書きをしている。

SDカードとMCP23S17のCS

ch1: MCP23S17 ch2:SD

波形が込み入っているが、SDカードに読み書きしている間はMCP23S17のCSはhiになっていて問い合わせは停止している。

メモ:


SPIバスを共用しているのでSDカードの読み書き中はI/Oエキスパンダー経由の入力は受け付けなくなる。

SDカードの読み書きはファイルを変えていくつかのパターンを保存できるようにしたい。(ファイル名は単純に番号を振ればいいか?)

2016年11月9日水曜日

ベースマシン UIとSequencerを再び統合

UIとSequencerでNucleo Boardを分けてSPI通信させていたのを再び統合した。I/Oピンが足りないのはI/OエキスパンダのMCP23S17を使う目論見。

配線図


NucleoからのSPI出力は(MISOを使わないので出力のみ)、バスバッファの74HC541でバッファリングして信号を補強した。

mbed repository:
https://developer.mbed.org/users/ryood/code/BaseMachine/ revision:2

UIとSequencerのハードウェアを分けたときに互いの結合度を下げたので、プログラムも変更してUI関係の処理はUIController Classとして分離した。

普通のGPIOピンを使ったUI部とシーケンサー部のプログラムの結合はまあまあうまく行ったようだが、I/Oエキスパンダを使ったUI部のプログラミングはまだ。機能としては9月下旬の「ベースマシンの音出し動画」まで戻った感じだ(^q^;

この間を振り返ってみると

  • SPIでプロセッサ間の通信。UARTは遅いし信頼性が低い。I2Cは遅い上に難解。
  • SPI信号の波形を整えた。バスバッファの74HC541を使う実験。ダンピング抵抗を入れる実験。
  • I/Oエキスパンダを使う実験。


というようなことをやっていたようだ。

ToDo:


BaseMachineUIController Class内でI/Oエキスパンダを使えるようにコーディング。

配線、パラメータ等のドキュメント整理。

74HC541を使ったSPIバスバッファをユニバーサル基板で実装。http://dad8893.blogspot.jp/2016/10/spi-tc74hc541.html

PSoC4 DCOにリセット線をつける。電源の立ち上げ時にSPI信号が入るタイミングで起動に失敗するようだ。オンボードの青色LEDが点滅する。これはPSoC 4 Prototyping Kitがプログラミングモードに入っていると思う。←未確認。

<追記:2016.12.26>

SPIの信号線のCSの初期値がLo(0)になっていたのが原因だったようです。対処療法でちゃんとは把握できていないですが、初期値をHi(1)にすると起動時に青色LEDがチカチカして停止する現象はなくなりました。

SPIのCSはReset信号とうと同じく負論理で、Active Lowです。一方CやC++では0で初期化するのが普通なのでプログラミングするときはCSを1で初期化するように気をつけたほうがいいと思います。

</追記>


電源ボードを検討。±9V(安定化なし)、±5V(7805/7905で安定化?)、+3V(安定化)


メモ:


よく言われることだと思うが、規模が大きくなると掛け算でエラー発生要因が増える。ハードウェアもプログラムもユニット・テストを今まで以上に慎重にやらないといけないようだ。

完成しても、DCOやDCFをいろいろ作って遊びたいので、今回はあまり妥協はしたくない。

2016年11月5日土曜日

Nucleo F401RE(mbed)でMCP23S17を使う。 バッファリングしてみる。

使っているライブラリがTickerで一定間隔で状態を問い合わせる仕組みになっているので毎回MCP23S17にSPIで問い合わせると通信コストが高そうなので、バッファリングしてみることを考えてみた。

mbedのソースの中までは見ていないが、SPI通信中は値が取得できるまで待ち状態になってそうな気がする。必要な値を毎回MCP23S17にぶら下がっているデバイスに問い合わせているとその間他の処理ができなくなる可能性が高いと思う。

MCP23S17のGPIOを全部読み取りとして使えば定期的にSPI経由でポーリングして2Byte(MCP23S17はGPIOがPortAx8bit、PortBx8bit)を本体のNucleoにバッファリングしてやれば、SPI通信のコストは下げられそうだ。

4×4のキーパッドを使うとGPIOのうち4本は書き込みに使わないといけないので、バッファリングの仕組みが複雑になる。キーパッドはあきらめてタクトスイッチとロータリーエンコーダーだけにしてテストしてみた。


mbed repogitory:
https://developer.mbed.org/users/ryood/code/ExioBufferdController_Test/

SPI通信のようす


PortBにつなげているタクトスイッチx8を適当に押しながら測定


ch1:sck ch2:MISO

拡大


Nucleo F401RE(mbed)でMCP23S17経由でキーパッド(4×4)を使えるようにする。

Classのコンストラクタでハードウェアに結びついているオブジェクトをポインタ渡しに変更した。

他のクラスと一緒にキーパッドが使えなかった原因は、クラスの内部で使っているTicker(タイマー)が原因だったようだ。キーパッド用のクラスではMCP23S17とのSPI通信を普通のメンバ関数で行っていたが、他のクラスはチャタリング対策としてTickerの割込み処理ハンドラ内で行っていた。

キーパッド用のクラスのメンバ関数内でSPI通信を行っている途中で、他のクラスからタイマー割り込みがかかってSPI通信がおかしくなっていたのかも?

また、Tickerのスタートのタイミングを初期化後にまとめて行えるように変更した。内部PullUpの指定等(これもSPI経由で行う)を行う前に、Tickerがスタートしてしまうと動作がおかしくなる。

ちょこちょこ直して、ひとまず、DebouncerIn(DigitalInのチャタリング対策)、PinDetect(ピンの状態の変化検出)、RotaryEncoder、Keypad4x4をMCP23S17経由で同時に動かすことができた。

ベースマシンのシーケンサー本体はRTOS::Timerでタイミングをとっているので、コンフリクトした場合はUI系のタイマーもTickerではなくRTOS::Timerを使うように修正した方がいいかもしれない。(参考:「mbedのRtosTimerをClassの中で使う。」)なんか自信ないなあ(@@;


mbed repogitory:
https://developer.mbed.org/users/ryood/code/ExioController_Test/ Revision:15

SPI通信のようす



ch1:MOSI ch2:SCK

拡大


2016年11月2日水曜日

Nucleo F401RE(mbed)でMCP23S17経由でロータリーエンコーダーを使えるようにする。

配線図



MCP23S17のPA0..PA4はロータリーエンコーダー、PA6、PA7はタクトスイッチ。PortBは4×4のキーパッドをつないだ。重なって見づらいが、MCP23S17のPin5..8(GPB4..GPP7)は10kΩのRでPullDownしている。

mbed repogitry:
https://developer.mbed.org/users/ryood/code/ExioController_Test/ Revision:10

main.cpp

#include "mbed.h"
#include "rtos.h"
 
#include "ExioMcp23s17PinDetect.h"
#include "ExioMcp23s17RotaryEncoder.h"
#include "ExioMcp23s17Keypad4x4.h"
 
SPI Spi(PC_12, PC_11, PC_10); // SPI3: mosi, miso, sclk
//SPI Spi(PA_7, PA_6, PA_5); // SPI1:  mosi, miso, sclk
 
// MExioMcp23s17(int hardwareaddress, SPI& spi, PinName nCs, PinName nReset);
ExioMcp23s17 Exio(0x00, Spi, PD_2, PA_13);
 
bool state = false;
uint8_t count = 0;
 
// トグル動作
void pa6Falled() {
    state = !state;
}
 
// インクリメント
void pa7Falled() {
    count++;
}
 
int main()
{
    //ExioMcp23s17Keypad4x4Test();
    
    printf("\r\n\n*** ExioMcp23s17Controller test ***\r\n");
    
    Exio.reset();
 
    // set PORTA[6:7] as inputs
    ExioMcp23s17PinDetect PinA6(Exio, ExioPortA, 6);
    ExioMcp23s17PinDetect PinA7(Exio, ExioPortA, 7);
    
    // assign callback functions
    PinA6.attach_asserted(&pa6Falled);
    PinA6.setAssertValue(0);
    PinA6.setSampleFrequency();
    
    PinA7.attach_asserted(&pa7Falled);
    PinA7.setAssertValue(0);
    PinA7.setSampleFrequency();
    
    // set PORTA[0:1], PORTA[2:3] as a Rotary Encoder
    //    ExioMcp23s17RotaryEncoder(
    //    ExioMcp23s17& device, ExioPort port, int pin1, int pin2,
    //    int min = 0, int max = 100, int val = 50
    //);
    ExioMcp23s17RotaryEncoder RotEnc1(Exio, ExioPortA, 0, 1, 0, 100, 50);
    RotEnc1.setInterval(500);
    ExioMcp23s17RotaryEncoder RotEnc2(Exio, ExioPortA, 2, 3, 0, 100, 50);
    RotEnc2.setInterval(500);
   
    // set PORTB as Keypad 4x4
    //ExioMcp23s17Keypad4x4 keypad(Exio, ExioPortB);
  
    while (true) {
        int reVal1 = RotEnc1.getVal();
        int reVal2 = RotEnc2.getVal();
        printf("state:%d count:%d RE1:%d RE2:%d \r\n", state, count, reVal1, reVal2);
        //Thread::wait(10);
    }
}

メモ:


4x4のキーパッドも単体では動作できたが、他の機能と同時に動作させるとうまく動かない。Classを適当に作ったのでRTOS等のタイミングがらみか。

ハードウェアに結びついているオブジェクトをコンストラクタで参照渡ししているのも問題かも(^q^; ポインタ渡しに直しておくか。

今日は疲れたので一旦キーパッド入力はあきらめて、ベースマシンのパターン切替えにはタクトスイッチを8個並べる方向で考えてベースマシン本体と結合してみる。

2016年10月31日月曜日

mbedのPinDetectをHackしてMCP23S17で使えるようにする。

mbedのチャタリング対策用のライブラリのPinDetectのソースをよく見てみると割込みではなくてポーリングで処理していた。

中で使っているのはDigitalInなのでこれをMCP23S17経由で読み取れるようにしてみた。

MCP23S17のPortB:0だけトグル動作。

mbed repository:
https://developer.mbed.org/users/ryood/code/ExioController_Test/ Revision:4

ExioController_Test/main.cpp
#include "mbed.h"
#include "rtos.h"
#include "ExioMcp23s17.h"
#include "ExioMcp23s17DigitalIn.h"
#include "ExioMcp23s17PinDetect.h"

SPI Spi(PC_12, PC_11, PC_10); // SPI3: mosi, miso, sclk
//SPI Spi(PA_7, PA_6, PA_5); // SPI1:  mosi, miso, sclk

// MExioMcp23s17(int hardwareaddress, SPI& spi, PinName nCs, PinName nReset);
ExioMcp23s17 Exio(0x00, Spi, PD_2, PA_13);

// トグル動作
bool pb0state = false;
void pb0Falled() {
    uint8_t data = Exio.readPort(ExioPortA);
    pb0state = !pb0state;
    if (pb0state) {
        data |= (1 << 0);
    } else {
        data &= ~(1 << 0);
    } 
    Exio.writePort(ExioPortA, data);
}

void pb1Falled() {
    Exio.writePort(ExioPortA, (1 << 1));
}

void pb2Falled() {
    Exio.writePort(ExioPortA, (1 << 2));
}

void pb3Falled() {
    Exio.writePort(ExioPortA, (1 << 3));
}

void pb4Falled() {
    Exio.writePort(ExioPortA, (1 << 4));
}

void pb5Falled() {
    Exio.writePort(ExioPortA, (1 << 5));
}

void pb6Falled() {
    Exio.writePort(ExioPortA, (1 << 6));
}

void pb7Falled() {
    Exio.writePort(ExioPortA, (1 << 7));
}

void ExioMcp23s17PinDetectTest()
{
    printf("\r\n\n*** ExioMcp23s17PinDetect test ***\r\n");
    
    Exio.reset();
    
    // set PORTA as output
    Exio.ioDirection(ExioPortA, 0x00);
    
    // create PORTB pins as inputs via ExioMcp23s17PinDetect
    ExioMcp23s17PinDetect PinB0(Exio, ExioPortB, 0);
    ExioMcp23s17PinDetect PinB1(Exio, ExioPortB, 1);
    ExioMcp23s17PinDetect PinB2(Exio, ExioPortB, 2);
    ExioMcp23s17PinDetect PinB3(Exio, ExioPortB, 3);
    ExioMcp23s17PinDetect PinB4(Exio, ExioPortB, 4);
    ExioMcp23s17PinDetect PinB5(Exio, ExioPortB, 5);
    ExioMcp23s17PinDetect PinB6(Exio, ExioPortB, 6);
    ExioMcp23s17PinDetect PinB7(Exio, ExioPortB, 7);

    // assign callback functions
    PinB0.attach_asserted(&pb0Falled);
    PinB0.setAssertValue(0);
    PinB0.setSampleFrequency();
    
    PinB1.attach_asserted(&pb1Falled);
    PinB1.setAssertValue(0);
    PinB1.setSampleFrequency();
    
    PinB2.attach_asserted(&pb2Falled);
    PinB2.setAssertValue(0);
    PinB2.setSampleFrequency();
    
    PinB3.attach_asserted(&pb3Falled);
    PinB3.setAssertValue(0);
    PinB3.setSampleFrequency();
    
    PinB4.attach_asserted(&pb4Falled);
    PinB4.setAssertValue(0);
    PinB4.setSampleFrequency();
    
    PinB5.attach_asserted(&pb5Falled);
    PinB5.setAssertValue(0);
    PinB5.setSampleFrequency();
    
    PinB6.attach_asserted(&pb6Falled);
    PinB6.setAssertValue(0);
    PinB6.setSampleFrequency();
    
    PinB7.attach_asserted(&pb7Falled);
    PinB7.setAssertValue(0);
    PinB7.setSampleFrequency();
    

    // LED Check (PortA)
    for (int i = 0; i < 8; i++) {
        Exio.writePort(ExioPortA, 1 << i);
        wait(0.1);
    }
    Exio.writePort(ExioPortA, 0x00);
    
    while (true) {
    }
}

int main()
{
    ExioMcp23s17PinDetectTest();
}

2016年10月30日日曜日

I/OエキスパンダーのMCP23S17をNucleo F401RE(mbed)で使ってみる。 その2

MCP23S17からの割込みが効かないとプログラミングに制約が出るのでもう少し。

前回割り込みが効かなかったのはプログラミングのミスだったと思う。mbedのMCP23S17用のライブラリもhttps://developer.mbed.org/users/stjo2809/code/MCP23S17/に変更して割込みのテストをしてみた。こちらのLibraryはhttps://developer.mbed.org/users/romilly/code/MCP23S17/より低レベルというかレジスタ操作のラッパーという感じだ。

配線図


割込み線(MCP23S17 -> Nucreo)をPA_14に割り当て、ResetをPA_13に割り当てた。Resetは別に使わなくてもいい。

(追記:2016.11.09)

起動時にResetを掛けておかないとMCP23S17がうまく動作しないようです。

(/追記)

mbed repository:
https://developer.mbed.org/users/ryood/code/Nucleo_MCP23S17_Test/ Revision:3

/*
 * MCP23S17 Test
 *
 * MCP23S17 Library:
 * https://developer.mbed.org/users/stjo2809/code/MCP23S17/
 *
 * mbed:  rev 121
 * mbed-rtos: rev 117
 *
 * Created: 2016.10.28
 *
 */

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

SPI Spi(PC_12, PC_11, PC_10); // SPI3: mosi, miso, sclk
//SPI Spi(PA_7, PA_6, PA_5); // SPI1:  mosi, miso, sclk

InterruptIn Mcp23s17Int(PA_14);

// MCP23S17(int hardwareaddress, SPI& spi, PinName nCs, PinName nReset);
MCP23S17 Mcp23s17(0x00, Spi, PD_2, PA_13);

void dataChanged()
{
    char data = Mcp23s17.intcapb();
    wait_us(1);
    Mcp23s17.gpioa(data);
}

int main()
{
    printf("\r\n\n*** MCP23S17 Test ***\r\n");

    // Reset MCP23S17
    Mcp23s17.reset();
    
    // PORTA output
    Mcp23s17.iodira(0x00);

    // PORTB input
    Mcp23s17.iodirb(0xFF);
    // PORTB pull-up
    Mcp23s17.gppub(0xFF);
    // PORTB invert polarity
    Mcp23s17.ipolb(0xFF);
    // PORTB enable on change interrupt
    Mcp23s17.gpintenb(0xFF);
    
    // Attach the callback function
    Mcp23s17Int.rise(&dataChanged);
    
    // LED Check
    for (int i = 0; i < 8; i++) {
        Mcp23s17.gpioa(1 << i);
        wait(0.1);
    }
    Mcp23s17.gpioa(0x00);
    
    while (true) {
    }
}

SPI信号


MOSI

ch1:sck ch2:mosi

拡大

2byte目と3byte目の間で一瞬MOSIがLになっている。SPI Mode 0の場合SCKの立ち上がりで評価するので問題ないが、高速な場合誤動作するかも?

MISO

ch1:sck ch2:miso

拡大


MISOが無出力のとき、Lレベルがふらふらしているので、Pull-Downしておいた方が良さそう。

MISO 10kΩでPull-down

MISO 3.3kΩでPull-down

3.3kΩの方が多少きれい。3.3V駆動でロジック・レベルがHの時3.3V/3.3kΩ=1mA消費することになる。

CS

ch1:sck ch2:cs
char data = Mcp23s17.intcapb();
Mcp23s17.gpioa(data);
と読み込み、書き込みを連続した状態で測定。(Read:3byte、Write:3byte)誤動作の可能性があるので間にウェイトを入れた方が良さそう。

Interrupt

ch1:sck ch2:intb

Active-highに設定しているのでPinの状態に変化があった時INTBがHになり、INTCAPBを読み込んだ時Lに戻るのでこれで良さそう。

メモ:


ベースマシンはNucleo2台使いをやめてI/Oエクスパンダ―でI/Oピンを増やす方向で。SPI通信の割込みが効かなくなる原因追及に疲れたし(^q^; 細かい実験用にNucleoを1台空けたい。

mbedのクラスが使えなくなるので、タクトスイッチ(トグル動作)、ロータリーエンコーダー、キーパッドの読み取りを自前で書かないとダメか(@@;