LogiClover開発ブログ

LogiCloverは、趣味でFPGAを使った電子工作をしているサークルです。主に開発中の出来事や技術メモを投稿します

SPI Flashの使い方 (2) - Arduinoから読み書き

組み込みでデータを保持したいときに使用するSPI Flashを実際にArduinoから制御してみます。

偶然手元にあった構成で、Arduino MicroとCypress製のSPI FlashであるS25FL032*1を使用します。

この記事は以下の記事の続きとなるので、部品の機能やコマンドについての詳細はこちらをご参照願います。

logiclover.hatenablog.jp

結線方法

基本的には前記事を参照していただければピン機能はわかると思います。

ArduinoなどでI/O電圧が5Vである場合、レベル変換が必要になります。

Arduino SPI Flash 備考
D10 CS# digitalWrite可能なピンであればどこでも良い
SCK SCK クロック
MOSI IO0/SI データ入力
MISO IO1/SO データ出力
- IO2/W# QSPIは使わず、W#も使わないためH固定
- IO3/HOLD# QSPIは使わず、HOLD#も使わないためH固定

まずはSPI転送とCS制御を書く

コマンドを幾つか実装する上で、まずはGPIOの制御とSPI自体の制御が必要になります。

Arduino以外のプラットフォームでも動作できるよう、ある程度汎用性をもたせるように設計します。

SPIはSPI.hを利用して、GPIOはpinMode, digitalWriteで実装します。

Serialでの表示はデバッグ用に残しているもので、不要であれば削除します。

#include <SPI.h>

#define FLASH_CS 10

void spi_init(){
  SPI.begin();
  pinMode(FLASH_CS, OUTPUT);
  spi_set_cs(0x1);
}
void spi_set_cs(uint8_t is_high) {
  Serial.print("#spi_set_cs is_high= ");
  Serial.print(is_high, DEC);
  digitalWrite(FLASH_CS, is_high & 0x1);
  Serial.print(" ... Done\n");
}
void spi_transfer(const uint8_t* tx_buf, uint8_t* rx_buf, uint16_t length){
  Serial.print("#spi_transfer length= ");
  Serial.print(length, DEC);
  for(uint16_t i = 0 ; i < length ; ++i){
    Serial.print(".");
    uint8_t data = SPI.transfer(tx_buf != NULL ? tx_buf[i] : 0x0);
    if(rx_buf != NULL){
      rx_buf[i] = data;
    }
  }
  Serial.print(" Done\n");
}

void spi_transfer(const uint8_t* tx_buf, uint8_t* rx_buf, uint16_t length) はlengthで指定した分のSPI転送を行います。

その際にtx_bufが指定されていなければ0x0のダミーデータを、rx_bufが指定されていたときのみ受信データを格納するように工夫します。

Flash制御の共通的な制御を書く

どのコマンドにも共通して、コマンドを送る、アドレスを送る、ダミーバイトを送る、データを送る、データを読み取る動作が存在します。

毎回spi_transferを呼び出してもいいですが、面倒なのと可読性を考慮して一段ラップします。呼び出しコストが気になるようであればコンパイル時にインライン展開されるような修飾を施します。

void flash_send_cmd(uint8_t cmd){
  spi_transfer(&cmd, NULL, 1);
}
void flash_send_addr(uint32_t addr){
  const uint8_t data[] = {
    (addr >> 16) & 0xff,
    (addr >>  8) & 0xff,
    (addr >>  0) & 0xff,
  };
  spi_transfer(data, NULL, 3);
}
void flash_send_dummy(uint16_t length){
  spi_transfer(NULL, NULL, length);
}
void flash_read_data(uint8_t* rx_buf, uint16_t length){
  spi_transfer(NULL, rx_buf, length);
}
void flash_write_data(const uint8_t* tx_buf, uint16_t length){
  spi_transfer(tx_buf, NULL, length);
}

基本的なコマンドを実装する

いよいよコマンドの実装です。データシートに記載された全種類を実装してもいいですが、冗長なので基本的な命令を一通り実装します。

メーカー間で差があるといえど全く異なるわけではないので、コマンドのニーモニックやダミーバイト数などを調節すれば他のデバイスも制御できます。

新しいコマンドを追加したい場合も既存の記述を参照すれば簡単に追加可能です。もっと高機能にしたくなりますが、高級な実装は後回しです。

//Flashのコマンドを発行(S25FL032)
#define CMD_RDID       (0x9f)
#define CMD_READ       (0x03)
#define CMD_WREN       (0x06)
#define CMD_WRDI       (0x04)
#define CMD_P4E        (0x20)
#define CMD_P8E        (0x40)
#define CMD_BE         (0x60)
#define CMD_PP         (0x02)
#define CMD_RDSR       (0x05)

void flash_rdid(uint8_t* rx_buf, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_RDID);
  flash_read_data(rx_buf, length);
  spi_set_cs(0x1);
}
void flash_read(uint32_t addr, uint8_t* rx_buf, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_READ);
  flash_send_addr(addr);
  flash_read_data(rx_buf, length);
  spi_set_cs(0x1);
}
void flash_wren(){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_WREN);
  spi_set_cs(0x1);
}
void flash_wrdi(){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_WRDI);
  spi_set_cs(0x1);
}
void flash_p4e(uint32_t addr){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_P4E);
  flash_send_addr(addr);
  spi_set_cs(0x1);  
}
void flash_be(){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_BE);
  spi_set_cs(0x1);  
}
void flash_pp(uint32_t addr, const uint8_t* wr_data, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_PP);
  flash_send_addr(addr);
  flash_write_data(wr_data, length);
  spi_set_cs(0x1);    
}
void flash_rdsr(uint8_t* rx_buf, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_RDSR);
  flash_read_data(rx_buf, length);
  spi_set_cs(0x1);
}

その他便利関数

EraseやProgramには完了を確認するためにステータスレジスタのWIP(Write In Progress)を監視する必要があるので、確認関数を追加します。

また、データのデバッグを楽にするためのデバッグプリントも実装しておきます。

//(WIP)Write In Progressかどうか確認
uint8_t flash_is_wip(){
  uint8_t data;
  flash_rdsr(&data, 0x1);
  return (data & 0x1);//WIP
}
//配列を表示
void data_debug_print(const uint8_t* data, uint16_t bytes){
  Serial.print("+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
  Serial.print("-----------------------------------------------\n");
  uint16_t depth = ((bytes - 1) >> 4) + 1;
  for(uint16_t j = 0 ; j < depth ; ++j){
    for(uint16_t i = 0 ; i < 16 ; ++i){
      uint8_t d = data[i + (j << 4)];
      Serial.print((d >> 4) & 0x0f, HEX);
      Serial.print((d >> 0) & 0x0f, HEX);
      Serial.print(" ");
    }
    Serial.print("\n");
  }
}

まずは動作確認

まずは確実に定値が読み出せるコマンドで、デバイスと正しく疎通できているか確認します。うまくいかない場合はコマンドか結線を疑います。オシロがあると楽です。

今回の場合、Read Identification(RDID)を使えば{Manufacturer Id, Device Id, Extended Bytes}の固定値が読み出せそうなのでこれを使います。

期待値は{0x01, 0x02, 0x15, 0x4d}です。

検証コード

void setup() {
  uint8_t rx_buf[256];

  spi_init();
  Serial.begin(9600);  
  while (!Serial) {}

  Serial.print("RDID\n");
  flash_rdid(rx_buf, 4);
  data_debug_print(rx_buf, 4);
}

結果

RDID
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 4.... Done
#spi_set_cs is_high= 1 ... Done
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
-----------------------------------------------
01 02 15 4D 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 

CSをLowにした後1byte(コマンド) -> 4byte(RDIDのデータ)の順にspi_transferが実行されています。

読み出した結果も先頭に注目すると期待通りの結果が読み出せています。(残りはゴミです)

直接関係ないのですが、USBを内蔵したATMELマイコンをベースにしたArduino特有の現象でSerial.begin()の後に初期化が終わるまで待たなければならないようです。

これはSerialの読み書きを、外部ではなく内部のUSB CDCを利用していることに起因しているようです。*2

特定の領域でRead, Erase, Program, Verifyをしてみる

バイスとの疎通は出来ているようなので、以下のシーケンスで任意のデータを書き込んでみます。

  • 現在のデータを読み出し
  • セクタイレースを実行 (全体削除のBulk Eraseは検証には時間がかかりすぎるため)
  • 適当な値を書き込み(シーケンシャルなデータを書き込む)
  • データを読み出して書き込んだ値と相違ないか確認

注意すべき点として、保持されているデータを変化させる命令にはWriteEnableを事前実行する必要があることです。

ちなみにステータスレジスタあたりから確認できます。またWriteDisable命令もあります。

検証コード

void setup() {
  uint8_t tx_buf[256];
  uint8_t rx_buf[256];
  uint32_t addr = 0x0;
  uint16_t length = 0x100;

  spi_init();
  Serial.begin(9600);  
  while (!Serial) {}

  Serial.print("RDID\n");
  flash_rdid(rx_buf, 4);
  data_debug_print(rx_buf, 4);

  Serial.print("Read\n");
  flash_read(addr, rx_buf, length);
  data_debug_print(rx_buf, length);

  Serial.print("Read after P4E\n");
  flash_wren();//P4Eの前に必要
  flash_p4e(addr);
  while(flash_is_wip()){//消去待ち
    Serial.print(".");
  }
  Serial.print("\n");
  flash_read(addr, rx_buf, length);
  data_debug_print(rx_buf, length);

  Serial.print("PP Sequential Data\n");
  for(uint16_t i = 0 ; i < length ; ++i){
    tx_buf[i] = i & 0xff;
  }
  flash_wren();
  flash_pp(addr, tx_buf, length);
  while(flash_is_wip()){//書き込み完了待ち
    Serial.print(".");
  }
  Serial.print("\n");

  Serial.print("Read after PP\n");  
  flash_read(addr, rx_buf, length);
  data_debug_print(rx_buf, length);
  
}

結果

一部見やすいように加工しています

RDID
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 4.... Done
#spi_set_cs is_high= 1 ... Done
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
-----------------------------------------------
01 02 15 4D 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 
(RDIDの結果、正しく通信できている)

Read
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 3... Done
#spi_transfer length= 256..(省略).. Done
#spi_set_cs is_high= 1 ... Done
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
-----------------------------------------------
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 
40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 
50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 
60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 
70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 
90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F 
A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF 
B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF 
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF 
D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF 
E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF 
F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF 
(同じコードを何度か実行しているので前のデータがそのまま見えている)

Read after P4E
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_set_cs is_high= 1 ... Done
(ここまでがWrite Enableコマンド、データを改変するコマンドには事前にWRENする必要がある)

#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 3... Done
#spi_set_cs is_high= 1 ... Done
(ここがP4Eコマンド、指定したセクタのデータが全部1になる、コマンドの完了には時間がかかるためWIPを見て待つ)

(ここからWIP待ち)
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 1. Done
#spi_set_cs is_high= 1 ... Done
(長いので省略...)


#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 3... Done
#spi_transfer length= 256..(省略).. Done
#spi_set_cs is_high= 1 ... Done
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
-----------------------------------------------
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
(削除後のデータ、全セルの値が1なのでffで埋め尽くされている)

PP Sequential Data
(WREN)
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_set_cs is_high= 1 ... Done

(PP)
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 3... Done
#spi_transfer length= 256...(省略).. Done
#spi_set_cs is_high= 1 ... Done

(WIP待ち)
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 1. Done
#spi_set_cs is_high= 1 ... Done



Read after PP
#spi_set_cs is_high= 0 ... Done
#spi_transfer length= 1. Done
#spi_transfer length= 3... Done
#spi_transfer length= 256..(省略).. Done
#spi_set_cs is_high= 1 ... Done
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
-----------------------------------------------
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 
40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 
50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 
60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 
70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 
90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F 
A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF 
B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF 
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF 
D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF 
E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF 
F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF 
(書き込み後のデータ、正しくインクリメンタルな値になった)

ログ中に注釈したとおり、Erase後にffになったデータがPPで書き込めていることが確認できます。

全面書き換えを行う

動作確認が出来たので、全面書き換えをしてみます。

Page Programはページごとに処理して、WIP待ちをしなければならないので0x100刻みに適当なデータを作って処理します。 ページをまたがなければ分ける必要はないので、今回は考慮していませんが0x3から100byteだけ書き換えたりなどは大丈夫です。

検証コード

  uint8_t tx_buf[256];
  uint8_t rx_buf[256];
  uint32_t length = 0x400000;

  /* 全面書き換え */
  flash_wren();

  Serial.print("Bulk Erase ...");
  flash_be();
  while(flash_is_wip()){
    //Serial.print(".");
  }
  Serial.print(" Done\n");

  //1Pageごと(256byteごと)処理をする
  for(uint32_t addr = 0x0 ; addr < length ; addr += 0x100){
    Serial.print("PP @");
    Serial.print(addr, HEX);
    //適当なデータを作る
    for(uint16_t i = 0 ; i < 0x100 ; ++i){
      tx_buf[i] = (i + (addr >> 8)) & 0xff;
    }
    flash_wren();
    flash_pp(addr, tx_buf, 0x100);
    while(flash_is_wip()){//書き込み完了待ち
      Serial.print(".");
    }
    Serial.print(" Done\n");    
  }

  //読み出して照合する
  bool is_all_match = 0x1;
  for(uint32_t addr = 0x0 ; addr < length ; addr += 0x100){
    Serial.print("Verify @");
    Serial.print(addr, HEX);
    flash_read(addr, rx_buf, 0x100);
    //結果を比較
    bool is_match = 0x1;
    for(uint16_t i = 0 ; i < 0x100 ; ++i){
      tx_buf[i] = (i + (addr >> 8)) & 0xff;
      if(tx_buf[i] != rx_buf[i]) {
        is_match = 0x0;
        is_all_match = 0x0;
        Serial.print(" Fail\n");    
        break;
      }
    }
    if(is_match){
      Serial.print(" Pass\n");    
    }
  }

  //先頭だけでも見ておく
  Serial.print("\n\n\n@0x000000\n");
  flash_read(0x0, rx_buf, 0x100);
  data_debug_print(rx_buf, 0x100);

  if(is_all_match){
    Serial.print("Pass: spi_flash_test2()\n");          
  } else {
    Serial.print("Fail: spi_flash_test2()\n");      
  }

結果

(めちゃくちゃ長いため省略)
Verify @3FF800 Pass
Verify @3FF900 Pass
Verify @3FFA00 Pass
Verify @3FFB00 Pass
Verify @3FFC00 Pass
Verify @3FFD00 Pass
Verify @3FFE00 Pass
Verify @3FFF00 Pass



@0x000000
+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
-----------------------------------------------
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 
10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 
20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 
30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 
40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 
50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 
60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 
70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 
90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F 
A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF 
B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF 
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF 
D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF 
E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF 
F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF 
Pass: spi_flash_test2()

動画

どうやら全データ書き換えられているようです。

終わりに

コード全体は以下に示します。自己責任でお願い致します。

間違い等ございましたらご指摘いただけると幸いです。

#include <SPI.h>

#define FLASH_CS 10

/////////////////////////////////////////////////////////////////////////////
//デバイスによって書き換える
void spi_init(){
  SPI.begin();
  pinMode(FLASH_CS, OUTPUT);
  spi_set_cs(0x1);
}
void spi_set_cs(uint8_t is_high) {
  //Serial.print("#spi_set_cs is_high= ");
  //Serial.print(is_high, DEC);
  digitalWrite(FLASH_CS, is_high & 0x1);
  //Serial.print(" ... Done\n");
}
void spi_transfer(const uint8_t* tx_buf, uint8_t* rx_buf, uint16_t length){
  //Serial.print("#spi_transfer length= ");
  //Serial.print(length, DEC);
  for(uint16_t i = 0 ; i < length ; ++i){
    //Serial.print(".");
    uint8_t data = SPI.transfer(tx_buf != NULL ? tx_buf[i] : 0x0);
    if(rx_buf != NULL){
      rx_buf[i] = data;
    }
  }
  //Serial.print(" Done\n");
}
/////////////////////////////////////////////////////////////////////////////
//Flashの制御
void flash_send_cmd(uint8_t cmd){
  spi_transfer(&cmd, NULL, 1);
}
void flash_send_addr(uint32_t addr){
  const uint8_t data[] = {
    (addr >> 16) & 0xff,
    (addr >>  8) & 0xff,
    (addr >>  0) & 0xff,
  };
  spi_transfer(data, NULL, 3);
}
void flash_send_dummy(uint16_t length){
  spi_transfer(NULL, NULL, length);
}
void flash_read_data(uint8_t* rx_buf, uint16_t length){
  spi_transfer(NULL, rx_buf, length);
}
void flash_write_data(const uint8_t* tx_buf, uint16_t length){
  spi_transfer(tx_buf, NULL, length);
}
/////////////////////////////////////////////////////////////////////////////
//Flashのコマンドを発行(S25FL032)
#define CMD_RDID       (0x9f)
#define CMD_READ       (0x03)
#define CMD_WREN       (0x06)
#define CMD_WRDI       (0x04)
#define CMD_P4E        (0x20)
#define CMD_P8E        (0x40)
#define CMD_BE         (0x60)
#define CMD_PP         (0x02)
#define CMD_RDSR       (0x05)

void flash_rdid(uint8_t* rx_buf, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_RDID);
  flash_read_data(rx_buf, length);
  spi_set_cs(0x1);
}
void flash_read(uint32_t addr, uint8_t* rx_buf, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_READ);
  flash_send_addr(addr);
  flash_read_data(rx_buf, length);
  spi_set_cs(0x1);
}
void flash_wren(){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_WREN);
  spi_set_cs(0x1);
}
void flash_wrdi(){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_WRDI);
  spi_set_cs(0x1);
}
void flash_p4e(uint32_t addr){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_P4E);
  flash_send_addr(addr);
  spi_set_cs(0x1);  
}
void flash_be(){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_BE);
  spi_set_cs(0x1);  
}
void flash_pp(uint32_t addr, const uint8_t* wr_data, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_PP);
  flash_send_addr(addr);
  flash_write_data(wr_data, length);
  spi_set_cs(0x1);    
}
void flash_rdsr(uint8_t* rx_buf, uint16_t length){
  spi_set_cs(0x0);
  flash_send_cmd(CMD_RDSR);
  flash_read_data(rx_buf, length);
  spi_set_cs(0x1);
}

/////////////////////////////////////////////////////////////////////////////
//その他
//(WIP)Write In Progressかどうか確認
uint8_t flash_is_wip(){
  uint8_t data;
  flash_rdsr(&data, 0x1);
  return (data & 0x1);//WIP
}
//配列を表示
void data_debug_print(const uint8_t* data, uint16_t bytes){
  Serial.print("+0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
  Serial.print("-----------------------------------------------\n");
  uint16_t depth = ((bytes - 1) >> 4) + 1;
  for(uint16_t j = 0 ; j < depth ; ++j){
    for(uint16_t i = 0 ; i < 16 ; ++i){
      uint8_t d = data[i + (j << 4)];
      Serial.print((d >> 4) & 0x0f, HEX);
      Serial.print((d >> 0) & 0x0f, HEX);
      Serial.print(" ");
    }
    Serial.print("\n");
  }
}
/////////////////////////////////////////////////////////////////////////////
//Arduinoの適当な制御コード
void spi_flash_test(){
  uint8_t tx_buf[256];
  uint8_t rx_buf[256];
  uint32_t addr = 0x0;
  uint16_t length = 0x100;

  Serial.print("RDID\n");
  flash_rdid(rx_buf, 4);
  data_debug_print(rx_buf, 4);

  Serial.print("Read\n");
  flash_read(addr, rx_buf, length);
  data_debug_print(rx_buf, length);

  Serial.print("Read after P4E\n");
  flash_wren();//P4Eの前に必要
  flash_p4e(addr);
  while(flash_is_wip()){//消去待ち
    Serial.print(".");
  }
  Serial.print("\n");
  flash_read(addr, rx_buf, length);
  data_debug_print(rx_buf, length);

  Serial.print("PP Sequential Data\n");
  for(uint16_t i = 0 ; i < length ; ++i){
    tx_buf[i] = i & 0xff;
  }
  flash_wren();
  flash_pp(addr, tx_buf, length);
  while(flash_is_wip()){//書き込み完了待ち
    Serial.print(".");
  }
  Serial.print("\n");

  Serial.print("Read after PP\n");  
  flash_read(addr, rx_buf, length);
  data_debug_print(rx_buf, length);
}

void spi_flash_test2(){
  uint8_t tx_buf[256];
  uint8_t rx_buf[256];
  uint32_t length = 0x400000;

  /* 全面書き換え */
  flash_wren();

  Serial.print("Bulk Erase ...");
  flash_be();
  while(flash_is_wip()){
    //Serial.print(".");
  }
  Serial.print(" Done\n");

  //1Pageごと(256byteごと)処理をする
  for(uint32_t addr = 0x0 ; addr < length ; addr += 0x100){
    Serial.print("PP @");
    Serial.print(addr, HEX);
    //適当なデータを作る
    for(uint16_t i = 0 ; i < 0x100 ; ++i){
      tx_buf[i] = (i + (addr >> 8)) & 0xff;
    }
    flash_wren();
    flash_pp(addr, tx_buf, 0x100);
    while(flash_is_wip()){//書き込み完了待ち
      Serial.print(".");
    }
    Serial.print(" Done\n");    
  }

  //読み出して照合する
  bool is_all_match = 0x1;
  for(uint32_t addr = 0x0 ; addr < length ; addr += 0x100){
    Serial.print("Verify @");
    Serial.print(addr, HEX);
    flash_read(addr, rx_buf, 0x100);
    //結果を比較
    bool is_match = 0x1;
    for(uint16_t i = 0 ; i < 0x100 ; ++i){
      tx_buf[i] = (i + (addr >> 8)) & 0xff;
      if(tx_buf[i] != rx_buf[i]) {
        is_match = 0x0;
        is_all_match = 0x0;
        Serial.print(" Fail\n");    
        break;
      }
    }
    if(is_match){
      Serial.print(" Pass\n");    
    }
  }

  //先頭だけでも見ておく
  Serial.print("\n\n\n@0x000000\n");
  flash_read(0x0, rx_buf, 0x100);
  data_debug_print(rx_buf, 0x100);

  if(is_all_match){
    Serial.print("Pass: spi_flash_test2()\n");          
  } else {
    Serial.print("Fail: spi_flash_test2()\n");      
  }
}

void setup() {
  spi_init();
  Serial.begin(9600);  
  while (!Serial) {}
  
  spi_flash_test2();
}

void loop() {
}

*1:2017年8月現在 新規設計非推奨となっていてS25FL-Lシリーズが推奨されています

*2:https://www.arduino.cc/en/Guide/ArduinoLeonardoMicro#toc6