製作モジュール一覧

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

2019年3月23日土曜日

STM32: SPI DACのMCP4922を使う(Nucleo-F446RE)

SPI DACのMCP4922でノコギリ波を2ch出力しました。

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

配線

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_MCP4922_Test1

STM32CubeMXの設定


MCP4922はMISOが必要ないのでSPIは「Transmit Only Master」でOKです。


GPIOは、SPIのNSS(CS)をソフトウェアで制御するのに1つ、2ch分ラッチして出力するのでLDAC1つで計2本です。


SW4STM32でコードを追加(一部)


/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

// param
//   channel: 0, 1
//   val: 0 .. 4095
void MCP4922_Write(uint8_t channel, uint16_t val)
{
 channel = channel & 0x01;
 val = val & 0x0fff;

 uint8_t cmd[2];
 cmd[0] = (channel << 7) | (val >> 8) | 0x30;
 cmd[1] = val & 0xff;

 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
 if (HAL_SPI_Transmit(&hspi1, cmd, 2, 100) != HAL_OK) {
  Error_Handler();
 }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
}

/* USER CODE END 0 */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint16_t cnt = 0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
 HAL_GPIO_WritePin(LDAC_GPIO_Port, LDAC_Pin, GPIO_PIN_SET);
 MCP4922_Write(0, cnt);
 MCP4922_Write(1, 4095 - cnt);
 HAL_GPIO_WritePin(LDAC_GPIO_Port, LDAC_Pin, GPIO_PIN_RESET);

 cnt++;
 if (cnt > 4095) {
  cnt = 0;
 }
  }
  /* USER CODE END 3 */

ノコギリ波の非反転、反転を出力しました。

出力結果


ch1:VOUTA ch2:VOUTB

2019年3月22日金曜日

STM32: SPIをPollingとDMAで使う(Nucleo-F446RE)

SPI通信を、ポーリングとDMA経由でテストしました。

参考にした記事

STM32CubeのExample「SPI_FullDuplex_ComPolling」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComPolling

STM32CubeのExample「SPI_FullDuplex_ComDMA」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComDMA

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

SPI通信(ポーリング)


STM32CubeのExampleはボードを2枚使って互いに通信させていますが、SPI送信波形を見るだけにして一番シンプルな形でテストしました。

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_SPI_SoftNSS_Test1

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Transmit Only Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

SPI1はデフォルトでは、PB3がSYS_JTDO-SWOに割り当てられていて有効化できませんが、Pinout viewでPB3にSPI1_SCKを割り当ててやれば、有効化できます。

モードは送信のみの「Transmit Only Master」にしました。MISOが割り当てられず、SCKとMOSIの2線です。

CSはソフトウェア制御にしてPinout viewでPB4をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MOSI PB5 D4
CS PB4 D5

クロック設定


SPIクロックをちょうど1MHzにしたかったので、HCLKを2のN乗の128MHzにしました。またクロック源(PLL Source Mux)を内蔵RCのHSIではなく、NucleoボードのST-Link側に乗っている8MHzの水晶から取るために、HSEに切り替えています。

SW4STM32でコードを追加(一部抜粋)

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
 uint8_t data[2] = { 0x55, 0xAA };
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
 if (HAL_SPI_Transmit(&hspi1, data, 2, 100) != HAL_OK) {
  Error_Handler();
 }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
  }
  /* USER CODE END 3 */

0x55, 0xAAを送信しています。ビット列にすると「01010101:10101010」になります。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)

SPI通信(DMA)


こちらも送信のみですが、設定は全二重(Full-Duplex Master)にしました。

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_SPI_DMA_Test1

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Full-Duplex Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

Transmit Only Masterの場合より1ピン増えて、MISOが追加されます。CSはPinout viewでPB10をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MISO PB4 D5
MOSI PB5 D4
CS PB10 D6

DMA Settings


SPIとDMAを紐付ける設定です。

[Add]ボタンを押して、[DMA Request]で[SPI1_TX][SPI1_RX]を追加します。

DMA Request Settings
  Mode: Normal

クロックの設定はポーリングの場合と同じです。

SW4STM32でコードを追加(一部抜粋)

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BUFFERSIZE (2)
enum {
 TRANSFER_WAIT,
 TRANSFER_COMPLETE,
 TRANSFER_ERROR
};
/* USER CODE END PD */

/* USER CODE BEGIN PV */
/* transfer state */
__IO uint32_t wTransferState = TRANSFER_WAIT;
/* USER CODE END PV */

DMA転送の状態を示すフラグです。

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    uint8_t aTxBuffer[BUFFERSIZE] = { 0x55, 0xAA };
 uint8_t aRxBuffer[BUFFERSIZE];
 wTransferState = TRANSFER_WAIT;

    /*##-2- Start the Full Duplex Communication process ########################*/
    /* While the SPI in TransmitReceive process, user can transmit data through
    "aTxBuffer" buffer & receive data through "aRxBuffer" */
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
    if(HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)aTxBuffer, (uint8_t *)aRxBuffer, BUFFERSIZE) != HAL_OK)
    {
   /* Transfer error in transmission process */
   Error_Handler();
    }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);

    /*##-3- Wait for the end of the transfer ###################################*/
    /*  Before starting a new communication transfer, you must wait the callback call
     to get the transfer complete confirmation or an error detection.
     For simplicity reasons, this example is just waiting till the end of the
     transfer, but application may perform other tasks while transfer operation
     is ongoing. */
    while (wTransferState == TRANSFER_WAIT)
    {
    }

    printf("SPI RX: %d\t%d\r\n", aRxBuffer[0], aRxBuffer[1]);
  }
  /* USER CODE END 3 */

こちらも0x55、0xAAを送信しています。

ソースのコメントにもありますが、DMA転送の完了をWhileループでフラグを見て待機していますが、本来はうまいことやってDMA転送完了待ち中は他の処理をするべきです。

/* USER CODE BEGIN 4 */
/**
  * @brief  TxRx Transfer completed callback.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report end of DMA TxRx transfer, and
  *         you can add your own implementation.
  * @retval None
  */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_COMPLETE;
}

/**
  * @brief  SPI error callbacks.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report transfer error, and you can
  *         add your own implementation.
  * @retval None
  */
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_ERROR;
}
/* USER CODE END 4 */

DMA転送完了の割り込みハンドラです。wTransferStateに状態を代入しているだけです。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)


ch1:MOSI(D4) ch2:SCK(D3)

printf()でUART通信しているので(ブロッキングされる)、SPI通信ごとの間隔が広がっています。

Puttyで受信しているようす

スレーブがつながっていないので受信データは0:0です。

メモ:


NSS(CS)をハードウェア制御できるようですが、マスター側ではソフトウェアでGPIO Writeしてやれば用は済みます。NSSのハードウェア制御の使い方はしばし保留。

2017年8月26日土曜日

Arduino UnoをSPI Slaveとして使う。

なぜだか分からないが、ArduinoのライブラリはSPIのSlaveに対応していない。I2CはSlaveでも使えるのに何故なんだろう?

今まで素のAVRも含めてSPIのスレーブとして使ったことが無かったので、直接レジスタを叩いてやってみた。

Arduino UnoをSlaveにする



配線図

写真のオフィシャルのArduino Uno Rev3(緑色)をMaster、aitendoのびんぼうでいーの(黄土色)をSlaveとして使っている。

SPIの信号線の間にブレッドボードを入れているのはオシロで測定しやすくするためで、必要がなければArduinoのPin同士を直結した方がいい。以前mbedでSPIの接続を悪条件にしてテストした時エラーが多発した。(参考「Nucleo(mbed OS5)のSPI通信を検証してみる。」)

動作としては、Masterから1ビットずつシフトした8bit値をSlaveに送り(MOSI)、Slaveは受信したデータを8個のLEDに表示する。LEDは流れるように点灯する。

Slaveは1づつインクリメントした8bit値をMasterに送り(MISO)、Master側は受信したデータをSerialで出力する。←Arduino IDEのシリアルモニタでMasterが受信したデータを表示できる。

※Arduinoを2個使うとArduino IDEで操るのがそこそこややこしいです(^q^;

Masterのスケッチ
SPI_Master_LEDx8.ino

#include <SPI.h>
#define SPI_CS_PIN  (10)

uint8_t cnt = 1;

void setup()
{
  Serial.begin(9600);
  Serial.println("SPI_Master_LEDx8 Test");
  
  pinMode(SPI_CS_PIN, OUTPUT);
  digitalWrite(SPI_CS_PIN, HIGH);
  
  SPI.begin();
}

void loop()
{
  digitalWrite(SPI_CS_PIN, LOW);
  uint8_t rdata = SPI.transfer(1 << cnt);
  digitalWrite(SPI_CS_PIN, HIGH);

  cnt++;
  if (cnt > 8) {
    cnt = 0;
  }

  Serial.println(rdata);
  
  delay(100);  
}

Master側のSPIの設定はデフォルトで、Clock:4MHz、MSB First、Mode:0になる。

Slaveのスケッチ
SPI_Slave_LEDx8

volatile uint8_t sdata = 0;

void setup()
{
  // LEDs output
  DDRD = 0xFF;
  PORTD = 0xFF;
  _delay_ms(100);

  // LEDs check
  for (int i = 0; i < 8; i++) {
    PORTD = 1 << i;
    _delay_ms(100);
  }
  PORTD = 0x00;

  // SPI slave
  DDRB = 0x10;  // MISO(PB4)をoutputに設定

  // SPCRのビット設定
  // SPI有効:1 | SPI割り込み有効:1 | MSBから送信:0 | スレーブ:0
  // クロック極性:0 | クロック位相:0 | SPR1:0 | SPR0:0
  SPCR = (1 << SPIE) | (1 << SPE);
}

ISR (SPI_STC_vect)
{
  // 受信データをLEDに出力
  PORTD = SPDR;

  // 次に送信するデータをセット
  SPDR = sdata;
  sdata++;
}

void loop()
{
}

Slave側のスケッチは、まぎれが無いように基本的にArduinoの関数は使っていない。

「//LED check」の部分は、起動時に接続しているLEDを順番に全点灯して配線ミスがないか確認できるようにしている。

ATMega328PのハードウェアSPIは

SCK: PB5 (D13)
MISO: PB4 (D12)
MOSI: PB3 (D11)
CS(SS): PB2 (D10)

を使うようになっていて、Slaveの場合MISOだけはPinの機能をOUTPUTに指定する必要があるので、

  // SPI slave
  DDRB = 0x10;  // MISO(PB4)をoutputに設定

としている。

「ISR (SPI_STC_vect){}」は割り込みハンドラで、SPCRレジスタのSPIEビットを1に設定すると、SPI通信が完了したときに呼び出されるようになる。

SlaveからMasterに送信するデータはSPDRレジスタに設定すればよく、「ISR (SPI_STC_vect){}」内で、受信データをSPDRレジスタから読み出した後、SPDRに書き込んでいる。このデータは、次回SPI通信が行われたときにMISOから出力されてMasterに送信される。

SPI通信は割り込みで処理しているので、メインループの「loop()」では何もしていない。

SPI通信の状態


MOSI

ch1:MOSI ch2:SCK

SPIクロックは4MHz

MISO

ch1:MISO ch2:SCK

ATMega88VをSlaveにする



同じことをATMega328Pとピン互換で、安価なATMega88Vでもやってみた。

配線図

配線図ではatemega168となっているが、実際はATMega88Vを使って内蔵RC/8MHzで動作させている。

Fuse Bit
hFuse: DFh
lFuse: A2h (動作確認のためPB0からクロック出力)
eHuze: 01h

Slave(ATMega88V)のソースコード

Atmel Studio7 (Version: 7.0.1417)でビルド

/*
 * SPI_Slave_LEDx8.c
 *
 * Created: 2017/08/26 9:16:53
 * Author : gizmo
 */

#define F_CPU   (8000000UL)

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

volatile uint8_t sdata = 0;

ISR (SPI_STC_vect)
{
    // 受信データをLEDに出力
    PORTD = SPDR;
    
    // 次に送信するデータをセット
    SPDR = sdata;
    sdata++;
}

int main(void)
{
    // LEDs output
    DDRD = 0xFF;
    PORTD = 0xFF;
    _delay_ms(100);

    // LEDs check
    for (int i = 0; i < 8; i++) {
        PORTD = 1 << i;
        _delay_ms(100);
    }
    PORTD = 0x00;

    // SPI slave
    DDRB = 0x10;  // MISO(PB4)をoutputに設定

    // SPCRのビット設定
    // SPI有効:1 | SPI割り込み有効:1 | MSBから送信:0 | スレーブ:0
    // クロック極性:0 | クロック位相:0 | SPR1:0 | SPR0:0
    SPCR = (1 << SPIE) | (1 << SPE);

    sei();  // 割り込み許可
    
    while (1)
    {
    }
}

基本的にはArduinoのスケッチと同じだが、ハマってしまった点。

#include <interrupt.h>

これを指定しなくても、Atmel Studio 7ではWarningは出るがBuild出来てしまう。「int ISR (SPI_STC_vect){}」という普通の関数と解釈してコンパイルしてしまうからだろう。プログラムの中で「ISR()」は呼び出されていないのでリンクも出来てしまう。

sei();

Arduinoでは(全体の)割り込みはデフォルトで許可されているが、素のAVRのプログラムでは明示的にsei()を呼び出して、割り込みを有効化しないとダメ。

またSPIクロックも問題があって、Arduino同士で使ったMasterのスケッチではLEDの点灯動作がおかしくなった。

SlaveはATMega88Vを8MHz駆動させていて、SPIクロックの最大周波数はマスタークロックの1/2なので、Masterが送ってくるSPIクロックが4MHzだと処理が追いつかないんだと思う。

SPIクロックを1MHzにしたMasterのスケッチ

#include <SPI.h>
#define SPI_CS_PIN  (10)

uint8_t cnt = 1;

void setup()
{
  Serial.begin(9600);
  Serial.println("SPI_Master_LEDx8 Test");

  pinMode(SPI_CS_PIN, OUTPUT);
  digitalWrite(SPI_CS_PIN, HIGH);

  SPI.begin();
}

void loop()
{
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  digitalWrite(SPI_CS_PIN, LOW);
  uint8_t rdata = SPI.transfer(1 << cnt);
  digitalWrite(SPI_CS_PIN, HIGH);
  SPI.endTransaction();

  cnt++;
  if (cnt > 8) {
    cnt = 0;
  }

  Serial.println(rdata);

  delay(100);
}

どこが違うかというと、

  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));

として、SPIクロックを1MHzにしている点。SPI.beginTransaction()しているので、SPI通信の終了時にSPI.endtransmission()している。

SPIの通信状態


MOSI

ch1:MOSI ch2:SCK

SPIクロックは1MHz

MISO

ch1:MISO ch2:SCK

Github:
https://github.com/ryood/Arduino_SPI_Slave

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とは分けた方がいいのかも(@@?

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月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月12日土曜日

SPIバス・バッファ(TC74HC541)基板の製作

ブレッドボードで実験しているとケーブルが抜けたりしてややこしいので、以前考えていた基板を作ってみた。(ほんとはプログラミングするのに疲れたので息抜き(^q^;)

回路図

基板図

使いやすいようにピンヘッダを変更。バス・バッファと状態表示用のLEDx8のGNDを分離。

Github:
https://github.com/ryood/BaseMachine/tree/master/SPI%20Bus%20Buffer

部品面

TC74HC541の出力に入れるダンピング抵抗は値を変更できるようにピンヘッダを使って実装した。写真はブレッドボード用のジャンパを使ってショートした状態。

出力は4系統のピンソケットと測定用のピンヘッダ。

ハンダ面

ジャンパ配線がないのではんだ付けは楽ちん。ピンソケットが格子状になっているところはハンダブリッジだけでつなぐのは至難の技なので抵抗とうの切った足を中に仕込んでいる。

ダンピング抵抗のテスト



NucleoからSPI信号を出力してArduinoで受信するようにテストプログラムを書いて実験してみた。赤色LEDはSPIバス・バッファとは全く関係なくて基板面積が余ったのでベースマシンのデバッグ用に載せてみた。

ダンピング抵抗の値を変えつつ、SPIのSCK信号を拾ってみた。(東芝製TC74HC541AP使用)

0Ω (ショート)

ch1:74HC541の入力 ch2:ダンピング抵抗からの出力

22Ω

33Ω

47Ω

68Ω

100Ω

TC74HC541を通すとダンピング抵抗を入れなくても波形の暴れは抑えられるが、20ns秒程度遅れるようだ。

100Ωは波形のなまりが顕著だが、その他は評価が難しい。とりあえずショートさせた状態で様子見しようかな?

東芝製TC74HC541APと日立製HD74HC541Pの比較


同じ74HC541でも型番によってDATASHEETのスペックが少し違う。aitendoでHD74HC541Pを仕入れてみたので比較。どちらもダンピング抵抗は0Ωにして測定した。

東芝製TC74HC541AP (50円@秋月)


日立製HD74HC541(100円@aitendo)

これも評価が難しい(@@;