背景・目標
Arduino Nano Everyと呼ばれるお手軽なマイコンボードがあります.
ArduinoではArduinoISPと呼ばれるコードを利用してArduinoをAVRライター化することができるのですが,当時(2020年)ではArduinoISPはArduino Nano Everyに対応しきれていない様子でした.
そこで,ArduinoISPをちょっと書き換えてArduino Nano Everyに対応させたいというのが目標です.
私はあまりArduino言語(?)に詳しくないため、誤りがありましたら指摘していただけると幸いです.
普通に書き込んでみる
ArduinoISPをNano Everyに書き込んだ様子がこちらです.
Arduino 1.8.13ではSPISetting
クラスがambiguousだと怒られます。SPISetting
というクラスが他の場所でも定義されているためのようですね。
原因
このArduinoISPをArduino Unoに書き込んでもこのようなエラーは出ません.ではなぜNano Everyで発生するのか?それを探るべく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
// (略)
このコードの特に注目していただきたいのがこちら.
#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で使えるのです.つまり
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では,CPUクロックに依存してSPI通信の周波数を選びます(19.5.1 SPCR – SPI Control Registerのtable19-5を参照1).このため最低となる書き込み速度がCPUクロックに依存して決定してしまいます.
このような理由でArudino UnoではソフトウェアSPI通信を使わざるを得なくなっています.
しかし,Nano Everyに用いられているATmega4809のデータシートを見てみますと,なんとSPI通信の周波数の最低値が見つかりません(32.13 SPIのTable 32-19を参照).
そのため,Nano Everyでは「最低値が速すぎてハードウェアSPIが使えない」なんてことはないようです.
したがって,ArduinoISP内のソフトウェアSPI実装あたりはガッツリ消して,ハードウェアSPIを使うようにしてやれば良いことになります.
LEDの個数問題
SPI通信の問題が解決できると,次にLEDの個数の問題になります.
Nano Everyで利用可能なLEDは限られており,ArduinoSPIで想定してる個数より少ないです.
そのためバッサリとLED関係のコードは消去しちゃいましょう!困っても書き込みの様子等がわからなくなるくらいですからね.
必要であれば適切なピン番号を設定して,LEDを光らせる回路を作りましょう.
まとめ
以上を踏まえて,必要最低限に切り詰めたのが次のとおりです.
(ほとんど、ArduinoISPと同じコード...)
#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);
}
}
-
しっかりと全文を読んだわけではないため、間違っていたら指摘お願いします。 ↩