[Arduino] Nano EveryをAVRライター化する。

# 背景・目標
[Arduino Nano Every](https://store.arduino.cc/usa/nano-every)と呼ばれるお手軽なマイコンボードがあります．

ArduinoではArduinoISPと呼ばれるコードを利用してArduinoをAVRライター化することができるのですが，当時（2020年）ではArduinoISPはArduino Nano Everyに対応しきれていない様子でした．

そこで，ArduinoISPをちょっと書き換えてArduino Nano Everyに対応させたいというのが目標です．

::: note warn
私はあまりArduino言語（？）に詳しくないため、誤りがありましたら指摘していただけると幸いです．
::: 

# 普通に書き込んでみる

ArduinoISPをNano Everyに書き込んだ様子がこちらです．

![Screenshot from 2020-11-04 18-02-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665043/e1eaed5c-0ec3-792a-7d31-b6a6e77a699d.png)

Arduino 1.8.13では`SPISetting`クラスがambiguousだと怒られます。`SPISetting`というクラスが他の場所でも定義されているためのようですね。

# 原因

このArduinoISPをArduino Unoに書き込んでもこのようなエラーは出ません．ではなぜNano Everyで発生するのか？それを探るべくArduinoISPコードを見てみると，そこで見つかる関係している部分がこちらです．

```c++:ArduinoISP

// (略)

#define SPI_CLOCK 		(1000000/6)

#if defined(ARDUINO_ARCH_AVR)

#if SPI_CLOCK > (F_CPU / 128)
#define USE_HARDWARE_SPI
#endif

#endif

// (中略)

#ifdef USE_HARDWARE_SPI
#include "SPI.h"
#else

#define SPI_MODE0 0x00

class SPISettings {
  // 略
};

class BitBangedSPI {
  // 略
};

static BitBangedSPI SPI;

#endif

// (略)
```

このコードの特に注目していただきたいのがこちら．

```c++
#if SPI_CLOCK > (F_CPU / 128)
#define USE_HARDWARE_SPI
#endif
```
ここでは，定義されている「USE_HARDWARE_SPI」という定数名からわかる通り，ハードウェアSPI通信を使うかどうかを決定しています．条件から`SPI_CLOCK`と`F_CPU`に依存して決めていることもわかります．

具体的には，ライターにしようとしているArduinoのCPUクロックに合わせて，SPI通信が，ソフトウェアSPI通信かハードウェアSPI通信（Arduinoライブラリを利用）かの，どちらかに切り替えられます．

（試しに`static BitBangedSPI SPI`を消してみるとわかりやすい．）

さて，これが先程のambiguousだというお叱りとどう関係があるかというと，ソフトウェアSPIを使う場合はSPISetting等のソフトウェアSPIをするためのコードをArduinoISPコード内で定義しているのですが，`SPISettings`は標準でも利用可能なため名前衝突がおきてしまいます．

`SPISettings`は，なにも，includeしなくてもNano Everyで使えるのです．つまり

```c++
void setup() {
  SPISettings(1, MSBFIRST, SPI_MODE0);
}

void loop() {
  // something
}
```

はコンパイル可能なのです．

そのため，ArduinoISP.inoで`SPISettings`が定義されると重複が起きてしまうのです．

# そもそも何故、ソフトとハードを切り替えるの？
理由はコードのコメントに書いてあります。
> Select hardware or software SPI, depending on SPI clock.
> Currently only for AVR, for other architectures (Due, Zero,...), hardware SPI is probably too fast anyway.

とあるように，ある種類ではハードウェアSPIでは速すぎることがあるようです．

速すぎると，書き込み対象のMCUが対応できないのでしょうね．

そのため，ハードウェアSPIでは設定できない書き込み速度を使うために，最低速度が速すぎる場合はソフトウェアSPIに切り替えるようです．


ということで，Nano EveryはソフトウェアSPIに切り替える条件を満たしていたため，ソフトウェアSPIの実装が定義され，それが衝突しエラーが起きています．

# Arduino Nano Every のSPI事情

では「Nano EveryではソフトウェアSPIを使わないといけないのか」というと，そんな訳でもありません．

そもそも，なぜ切り替える必要が在るかというとハードウェアSPI通信の最低速度が早すぎるためです．

例えばArduino Unoで用いられている[ATmega328](https://www.mouser.jp/ProductDetail/Microchip-Technology-Atmel/ATMEGA328P?qs=HbI%2FMOA3e14Ubd7tUwWKwQ%3D%3D)では，CPUクロックに依存してSPI通信の周波数を選びます（19.5.1 SPCR – SPI Control Registerのtable19-5を参照[^1]）．このため最低となる書き込み速度がCPUクロックに依存して決定してしまいます．

[^1]: しっかりと全文を読んだわけではないため、間違っていたら指摘お願いします。

このような理由でArudino UnoではソフトウェアSPI通信を使わざるを得なくなっています．

しかし，Nano Everyに用いられている[ATmega4809](https://www.mouser.jp/ProductDetail/Microchip-Technology-Atmel/ATMEGA4809-AFR?qs=lYGu3FyN48fyX8GjkvcD6Q%3D%3D)のデータシートを見てみますと，なんとSPI通信の周波数の最低値が見つかりません（32.13 SPIのTable 32-19を参照）．

そのため，Nano Everyでは「最低値が速すぎてハードウェアSPIが使えない」なんてことはないようです．

したがって，ArduinoISP内のソフトウェアSPI実装あたりはガッツリ消して，ハードウェアSPIを使うようにしてやれば良いことになります．

# LEDの個数問題
SPI通信の問題が解決できると，次にLEDの個数の問題になります．

Nano Everyで利用可能なLEDは限られており，ArduinoSPIで想定してる個数より少ないです．

そのためバッサリとLED関係のコードは消去しちゃいましょう！困っても書き込みの様子等がわからなくなるくらいですからね．

必要であれば適切なピン番号を設定して，LEDを光らせる回路を作りましょう．

# まとめ

以上を踏まえて,必要最低限に切り詰めたのが次のとおりです.
（ほとんど、ArduinoISPと同じコード．．．）

```c++:ArduinoISP.ino
#include "SPI.h"


#define SPI_CLOCK 		(1000000/6)

#define RESET     2
#define PIN_MOSI	11
#define PIN_MISO	12
#define PIN_SCK		13

#define BAUDRATE	19200


#define HWVER 2
#define SWMAJ 1
#define SWMIN 18


#define STK_OK      0x10
#define STK_FAILED  0x11
#define STK_UNKNOWN 0x12
#define STK_INSYNC  0x14
#define STK_NOSYNC  0x15
#define CRC_EOP     0x20


void setup() {
  Serial.begin(BAUDRATE);
}

int error = 0;
int pmode = 0;

unsigned int here;
uint8_t buff[256];

#define beget16(addr) (*addr * 256 + *(addr+1) )
typedef struct param {
  uint8_t devicecode;
  uint8_t revision;
  uint8_t progtype;
  uint8_t parmode;
  uint8_t polling;
  uint8_t selftimed;
  uint8_t lockbytes;
  uint8_t fusebytes;
  uint8_t flashpoll;
  uint16_t eeprompoll;
  uint16_t pagesize;
  uint16_t eepromsize;
  uint32_t flashsize;
} parameter;

parameter param;


static bool rst_active_high;

void reset_target(bool reset) {
  digitalWrite(RESET, ((reset && rst_active_high) || (!reset && !rst_active_high)) ? HIGH : LOW);
}

void loop(void) {
  if (Serial.available()) {
    avrisp();
  }
}

uint8_t getch() {
  while (!Serial.available());
  return Serial.read();
}
void fill(int n) {
  for (int x = 0; x < n; x++) {
    buff[x] = getch();
  }
}

uint8_t spi_transaction(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
  SPI.transfer(a);
  SPI.transfer(b);
  SPI.transfer(c);
  return SPI.transfer(d);
}

void empty_reply() {
  if (CRC_EOP == getch()) {
    Serial.print((char)STK_INSYNC);
    Serial.print((char)STK_OK);
  } else {
    error++;
    Serial.print((char)STK_NOSYNC);
  }
}

void breply(uint8_t b) {
  if (CRC_EOP == getch()) {
    Serial.print((char)STK_INSYNC);
    Serial.print((char)b);
    Serial.print((char)STK_OK);
  } else {
    error++;
    Serial.print((char)STK_NOSYNC);
  }
}

void get_version(uint8_t c) {
  switch (c) {
    case 0x80:
      breply(HWVER);
      break;
    case 0x81:
      breply(SWMAJ);
      break;
    case 0x82:
      breply(SWMIN);
      break;
    case 0x93:
      breply('S');
      break;
    default:
      breply(0);
  }
}

void set_parameters() {
  param.devicecode = buff[0];
  param.revision   = buff[1];
  param.progtype   = buff[2];
  param.parmode    = buff[3];
  param.polling    = buff[4];
  param.selftimed  = buff[5];
  param.lockbytes  = buff[6];
  param.fusebytes  = buff[7];
  param.flashpoll  = buff[8];
  param.eeprompoll = beget16(&buff[10]);
  param.pagesize   = beget16(&buff[12]);
  param.eepromsize = beget16(&buff[14]);

  param.flashsize = buff[16] * 0x01000000
                    + buff[17] * 0x00010000
                    + buff[18] * 0x00000100
                    + buff[19];

  rst_active_high = (param.devicecode >= 0xe0);
}

void start_pmode() {
  reset_target(true);
  pinMode(RESET, OUTPUT);
  SPI.begin();
  SPI.beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0));

  digitalWrite(PIN_SCK, LOW);
  delay(20);
  reset_target(false);
  delayMicroseconds(100);
  reset_target(true);

  delay(50);
  spi_transaction(0xAC, 0x53, 0x00, 0x00);
  pmode = 1;
}

void end_pmode() {
  SPI.end();
  pinMode(PIN_MOSI, INPUT);
  pinMode(PIN_SCK, INPUT);
  reset_target(false);
  pinMode(RESET, INPUT);
  pmode = 0;
}

void universal() {
  uint8_t ch;
  fill(4);
  ch = spi_transaction(buff[0], buff[1], buff[2], buff[3]);
  breply(ch);
}

void flash(uint8_t hilo, unsigned int addr, uint8_t data) {
  spi_transaction(0x40 + 8 * hilo,
                  addr >> 8 & 0xFF,
                  addr & 0xFF,
                  data);
}
void commit(unsigned int addr) {
  spi_transaction(0x4C, (addr >> 8) & 0xFF, addr & 0xFF, 0);
}

unsigned int current_page() {
  if (param.pagesize == 32) {
    return here & 0xFFFFFFF0;
  }
  if (param.pagesize == 64) {
    return here & 0xFFFFFFE0;
  }
  if (param.pagesize == 128) {
    return here & 0xFFFFFFC0;
  }
  if (param.pagesize == 256) {
    return here & 0xFFFFFF80;
  }
  return here;
}


void write_flash(int length) {
  fill(length);
  if (CRC_EOP == getch()) {
    Serial.print((char) STK_INSYNC);
    Serial.print((char) write_flash_pages(length));
  } else {
    error++;
    Serial.print((char) STK_NOSYNC);
  }
}

uint8_t write_flash_pages(int length) {
  int x = 0;
  unsigned int page = current_page();
  while (x < length) {
    if (page != current_page()) {
      commit(page);
      page = current_page();
    }
    flash(LOW, here, buff[x++]);
    flash(HIGH, here, buff[x++]);
    here++;
  }

  commit(page);

  return STK_OK;
}

#define EECHUNK (32)
uint8_t write_eeprom(unsigned int length) {
  unsigned int start = here * 2;
  unsigned int remaining = length;
  if (length > param.eepromsize) {
    error++;
    return STK_FAILED;
  }
  while (remaining > EECHUNK) {
    write_eeprom_chunk(start, EECHUNK);
    start += EECHUNK;
    remaining -= EECHUNK;
  }
  write_eeprom_chunk(start, remaining);
  return STK_OK;
}

uint8_t write_eeprom_chunk(unsigned int start, unsigned int length) {
  fill(length);
  for (unsigned int x = 0; x < length; x++) {
    unsigned int addr = start + x;
    spi_transaction(0xC0, (addr >> 8) & 0xFF, addr & 0xFF, buff[x]);
    delay(45);
  }
  return STK_OK;
}

void program_page() {
  char result = (char) STK_FAILED;
  unsigned int length = 256 * getch();
  length += getch();
  char memtype = getch();
  if (memtype == 'F') {
    write_flash(length);
    return;
  }
  if (memtype == 'E') {
    result = (char)write_eeprom(length);
    if (CRC_EOP == getch()) {
      Serial.print((char) STK_INSYNC);
      Serial.print(result);
    } else {
      error++;
      Serial.print((char) STK_NOSYNC);
    }
    return;
  }
  Serial.print((char)STK_FAILED);
  return;
}

uint8_t flash_read(uint8_t hilo, unsigned int addr) {
  return spi_transaction(0x20 + hilo * 8,
                         (addr >> 8) & 0xFF,
                         addr & 0xFF,
                         0);
}

char flash_read_page(int length) {
  for (int x = 0; x < length; x += 2) {
    uint8_t low = flash_read(LOW, here);
    Serial.print((char) low);
    uint8_t high = flash_read(HIGH, here);
    Serial.print((char) high);
    here++;
  }
  return STK_OK;
}

char eeprom_read_page(int length) {
  // here again we have a word address
  int start = here * 2;
  for (int x = 0; x < length; x++) {
    int addr = start + x;
    uint8_t ee = spi_transaction(0xA0, (addr >> 8) & 0xFF, addr & 0xFF, 0xFF);
    Serial.print((char) ee);
  }
  return STK_OK;
}

void read_page() {
  char result = (char)STK_FAILED;
  int length = 256 * getch();
  length += getch();
  char memtype = getch();
  if (CRC_EOP != getch()) {
    error++;
    Serial.print((char) STK_NOSYNC);
    return;
  }
  Serial.print((char) STK_INSYNC);
  if (memtype == 'F') result = flash_read_page(length);
  if (memtype == 'E') result = eeprom_read_page(length);
  Serial.print(result);
}

void read_signature() {
  if (CRC_EOP != getch()) {
    error++;
    Serial.print((char) STK_NOSYNC);
    return;
  }
  Serial.print((char) STK_INSYNC);
  uint8_t high = spi_transaction(0x30, 0x00, 0x00, 0x00);
  Serial.print((char) high);
  uint8_t middle = spi_transaction(0x30, 0x00, 0x01, 0x00);
  Serial.print((char) middle);
  uint8_t low = spi_transaction(0x30, 0x00, 0x02, 0x00);
  Serial.print((char) low);
  Serial.print((char) STK_OK);
}


void avrisp() {
  uint8_t ch = getch();
  switch (ch) {
    case '0':
      error = 0;
      empty_reply();
      break;
    case '1':
      if (getch() == CRC_EOP) {
        Serial.print((char) STK_INSYNC);
        Serial.print("AVR ISP");
        Serial.print((char) STK_OK);
      }
      else {
        error++;
        Serial.print((char) STK_NOSYNC);
      }
      break;
    case 'A':
      get_version(getch());
      break;
    case 'B':
      fill(20);
      set_parameters();
      empty_reply();
      break;
    case 'E':
      fill(5);
      empty_reply();
      break;
    case 'P':
      if (!pmode)
        start_pmode();
      empty_reply();
      break;
    case 'U':
      here = getch();
      here += 256 * getch();
      empty_reply();
      break;

    case 0x60:
      getch();
      getch();
      empty_reply();
      break;
    case 0x61:
      getch();
      empty_reply();
      break;

    case 0x64:
      program_page();
      break;

    case 0x74:
      read_page();
      break;

    case 'V':
      universal();
      break;
    case 'Q':
      error = 0;
      end_pmode();
      empty_reply();
      break;

    case 0x75:
      read_signature();
      break;

    case CRC_EOP:
      error++;
      Serial.print((char) STK_NOSYNC);
      break;

    default:
      error++;
      if (CRC_EOP == getch())
        Serial.print((char)STK_UNKNOWN);
      else
        Serial.print((char)STK_NOSYNC);
  }
}
```