2023年5月8日月曜日

ATmega32u4をSPIのslaveとして使う


背景

ProMicroをSPIのslaveとして動かそうと思ったところ、SS(Slave select、CS: Change slaveと呼ばれることもある)ピンであるPB0(D17)の引き出しが必要でした。

引出して繋いだら動かせたもののまあまあ手間だったので、備忘録として取り組んだ内容を記事に残します。

使ったもの

  • ATmega32u4が使われているarduino
    LeonardoやProMicroが使えます。
  • SPIの親機として使うarduino
    何でも良いです。
    同じarduinoを使うと混乱しそうなので、Unooを親機として使いました。
  • プログラムを書き込むPC
    arduino IDEかvscodeとplatformioでプログラムを書き込めます。
    はんだごて + はんだ + フラックス + ピンセット
    SS線に繋がっているRXのLED回路の分解に使います。
  • ブレッドボード + ジャンパワイヤ
    マイコン間の接続に使います。
  • 導線
    D17のSS線を引き出します。
  • ホットボンド
    引き出した導線はハンダづけだけだと剥離しやすいため、ホットボンドなどでの固定をお勧めします。

SSを引き出せば良いと分かった経緯: stack overflowでarduinoの中の人の回答を発見

ATmega32u4を使っているLeonardoやProMicroはSS用のピンが引き出されていないからSPIのslaveとして使えないという情報が下記のページで共有されていました。

How do you use SPI on an Arduino?


SSが出てないなら出せば使えると思い、試したところ使えました。

RXのLEDに使われているSSピンを引き出す

ProMicroは「SS-抵抗-LED-VCC」で回路が組まれているので、抵抗を外して信号線を引き出しました。
参考: ProMicro schema (PDF)



Leonardoは「SS-LED-抵抗-VCC」で回路が組まれているので、LEDを外して信号線を引き出しました。
参考: Leonardo schema (PDF)



それぞれ信号線を引出した後は千切れ防止のためにホットボンドで補強しています。

SPIのslaveとmasterを繋げる

MOSI、MISO、SCK、SS、GND(、必要ならVCCや5V)を繋ぎます。
ATmega系のマイコンはMOSIとMISOの入出力をで切り替えてくれるようで「masterならMOSIが出力」「slaveならMISOが出力」となり、masterやslaveに関わらずMOSIとMISOを繋げば良いです。

公式のArduinoのSPIのピン情報は下記のページにまとまっています。

Arduino as ISP and Arduino Bootloaders

masterのUnoとslaveのProMicroをこのように繋げました
MOSI :Uno D11 - ProMicro D16
MISO: Uno D12 - ProMicro D14
CLK: Uno D13 - ProMicro D15
SS: Uno D10 - ProMicro 引出したSS
GND: それぞれ空いているところ
5V: それぞれ空いているところ


masterのUnoとslaveのLeonardoをこのように繋げました
MOSI :Uno D11 - Lonardo ICSP4
MISO: Uno D12 - Lonardo ICSP1
CLK: Uno D13 - Lonardo ICSP3
SS: Uno D10 - Lonardo 引出したSS
GND: それぞれ空いているところ
5V: それぞれ空いているところ


プログラムを書き込んで動作確認

SSを引出したProMicroやLeonardoは、ATmega系のSPIのslaveプログラムを特にATmega32u4向けに変更することなく使えます。

参考にしたstack overflowの投稿で紹介されていたコマンドによって送信した値に足し引きするコードを基本として、master側はやり取りの時刻をログに出し、slave側は受け取ったコマンドをログに出す処理を加えたプログラムを書き込みました。

SPI master(この記事だとUno)
#include <Arduino.h>
#include <SPI.h>

#define SS0 10

void setup(void) {
Serial.begin(115200);
Serial.println();
pinMode(SS0, OUTPUT);
digitalWrite(SS0, HIGH); // ensure SS stays high for now
SPI.begin();

// Slow down the master a bit
SPI.setClockDivider(SPI_CLOCK_DIV128);
// SPI.setClockDivider(SPI_CLOCK_DIV8);
} // end of setup

byte transferAndWait(const byte what) {
byte a = SPI.transfer(what);
delayMicroseconds(20);
return a;
} // end of transferAndWait

void loop(void) {
byte a, b, c, d;

// enable Slave Select
digitalWrite(SS0, LOW);

transferAndWait('a'); // add command
transferAndWait(10);
a = transferAndWait(17);
b = transferAndWait(33);
c = transferAndWait(42);
d = transferAndWait(0);

// disable Slave Select
digitalWrite(SS0, HIGH);
delay(10);

Serial.println("Adding results:");
Serial.println(a, DEC);
Serial.println(b, DEC);
Serial.println(c, DEC);
Serial.println(d, DEC);

// enable Slave Select
digitalWrite(SS0, LOW);

transferAndWait('s'); // subtract command
transferAndWait(10);
a = transferAndWait(17);
b = transferAndWait(33);
c = transferAndWait(42);
d = transferAndWait(0);

// disable Slave Select
digitalWrite(SS0, HIGH);

Serial.println("Subtracting results:");
Serial.println(a, DEC);
Serial.println(b, DEC);
Serial.println(c, DEC);
Serial.println(d, DEC);
Serial.println("at " + String(millis()));
Serial.flush();

delay(1000); // 1 second delay
} // end of loop


SPI slave(この記事だとProMicroかLeonardo)
#include <Arduino.h>
#include <pins_arduino.h>

void setup(void) {
// have to send on master in, *slave out*
pinMode(MISO, OUTPUT);

// turn on SPI in slave mode
SPCR |= _BV(SPE);

// turn on interrupts
SPCR |= _BV(SPIE);
// SPI.attachInterrupt();

Serial.begin(115200);
Serial.println("hello");

} // end of setup

char command = 0;
bool receivedCommand = false;

// SPI interrupt routine
ISR(SPI_STC_vect) {
byte c = SPDR;

switch (command) {
// no command? then this is the command
case 0:
receivedCommand = true;
command = c;
SPDR = 9;
break;
// add to incoming byte, return result
case 'a':
SPDR = c + 15; // add 15
break;
// subtract from incoming byte, return result
case 's':
SPDR = c - 8; // subtract 8
break;
default:
SPDR = 5;
break;
} // end of switch

} // end of interrupt service routine (ISR) SPI_STC_vect

void loop(void) {
// if SPI not active, clear current command
if (receivedCommand) {
Serial.println("received command " + String(command) + " at " +
String(millis()));
receivedCommand = false;
}
if (digitalRead(SS) == HIGH) command = 0;
delay(10);
} // end of loop

上記のプログラムをmasterとslaveにそれぞれ確認して通信に成功すると下記のようなログが表示されます。
masterのログ
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
at 1200484
slaveのログ
received command a at 30117
received command s at 30129
received command a at 31129
received command s at 31139

Uno-ProMicroの組み合わせでもUno-Leonardoの組み合わせでも、上記のプログラムが期待通りに動きました。

おわり

ProMicroとLeonardoのSPIのSSを引出してslaveとして動かせました。

引き出すにははんだ付けが必須なので手間ですが、自分がちょっとした実験に利用することが多いProMicroをSPIのslave動かせて嬉しいです。

参考

ATmega32u4のSSピンが引き出されていないことを知ったstack overflowの投稿です。
How do you use SPI on an Arduino?

回路図です。
ProMicro schema (PDF)
Leonardo schema (PDF)

0 件のコメント :