Skip to content

Networking and communications

Task

Group assignment:

  • Send a message between two projects
  • Document your work to the group work page and reflect on your individual page what you learned

Group assignment page.

Individual assignment:

  • Design, build and connect wired or wireless node(s) with network or bus addresses and a local interface

SPI communication between board 03 and Barduino

Hardware SPI on SAMD21E18A

By selecting ONE_UART_ONE_WIRE_TWO_SPI in Arduino IDE tools/serial config, Hardware SPI gets defined on pins:

  • Sercom3: 18, 19, 22, 23
  • Sercom2: 8, 9, 14, 15

By default, with this config the other SERCOMs are used for:

  • Sercom0: Serial1
  • Sercom1: ?

I have designed my board 03 (to be used as spoke unit) to use sercom2 on 14 and 15 for communication with the LED strip, and 17, 18, 19 and 20 on sercom1 for communication with the hub unit.

I found a library that does Sercom SPI slave. I don't understand half of it but at least it gives me a reference pin configuration that I can use. From the README:

Sercom1
SPI pin description ATSAMD21 pin
MOSI    Master Out Slave In PA16
SCK Serial Clock    PA17
SS  Slave Select    PA18
MISO    Master In Slave Out PA19

I have decided to drop SPI communication because it seems that coding the slave part requires interrupts, which I don't understand and I don't have time to learn.

Another possibly useful post if I resume this in the future.

I2C between Barduino and board 03

In order to find out which pins I need to use for I2C, I went to the variant.h file:


/*
 * Wire Interfaces
 */
#if defined(TWO_WIRE)
#define WIRE_INTERFACES_COUNT 2
#else
#define WIRE_INTERFACES_COUNT 1
#endif

#define PIN_WIRE_SDA         (16u)
#define PIN_WIRE_SCL         (17u)
#if defined(THREE_UART) && defined(ONE_WIRE) && defined(NO_SPI)
  #define PERIPH_WIRE          sercom3
  #define WIRE_IT_HANDLER      SERCOM3_Handler
#else
  #define PERIPH_WIRE          sercom1
  #define WIRE_IT_HANDLER      SERCOM1_Handler
#endif

static const uint8_t SDA = PIN_WIRE_SDA;
static const uint8_t SCL = PIN_WIRE_SCL;

#define PIN_WIRE1_SDA         (8u)
#define PIN_WIRE1_SCL         (9u)
#define PERIPH_WIRE1          sercom2
#define WIRE1_IT_HANDLER      SERCOM2_Handler

static const uint8_t SDA1 = PIN_WIRE1_SDA;
static const uint8_t SCL1 = PIN_WIRE1_SCL;

So Wire uses 16 and 17, and (if I choose TWO_WIRE in the Arduino IDE, Wire1 uses 8 and 9.

I jury-rigged a quick connector to expose 8 and 9:

My first attempt kind of worked, in that there was clearly communication between the two boards, but I couldn't actually read what was being sent:

Ok, I found the problem. I was reading from Wire instead of Wire1. Doh! Then it's a simple matter of sending from one to the other:

Just for completeness, I tried connecting through SERCOM1, on pins 16 & 17, the default. It worked fine!

This is it working:

I2C between board 04 (spoke unit) and the hub unit

Next step was to move closer to production. I made a hub unit design and tried connecting it to the spoke unit through I2C.

I2C comms between board 04 and hub unit. Click to show code. Hub unit: takes human input from `Serial`, then sends it through `Wire` to the spoke unit.
#include <Wire.h>

#define ADDRESS 97  // Slave; address randomly chosen

// Variables
const int bufferSize = 50;  // Define the size of your buffer
char buffer[bufferSize];    // Create the character buffer
char terminator = '\n';     // Define the terminator character
int bytesRead = 0;          // Variable to hold the number of bytes read
bool readPending = false;

void setup() {
  Wire.begin();
  Serial.begin(115200);
}

void loop() {
  Serial.print(".");
  delay(1000);
  Serial.print(".");
  delay(1000);
  Serial.println(".");
  delay(1000);

  if (Serial.available() > 0) {
    bytesRead = Serial.readBytesUntil('\n', buffer, bufferSize - 1);
    // Null-terminate the buffer to make it a valid C string
    buffer[bytesRead] = '\0';
    Serial.print("Read from Serial: ");
    Serial.println(buffer);

    Wire.beginTransmission(ADDRESS);
    Wire.write(buffer);
    Wire.endTransmission();
    memset(buffer, 0, bufferSize);
    readPending = true;
  }





  if (readPending) {
    Serial.println("Proceeding to request echo");
    Wire.requestFrom(ADDRESS, bytesRead);


    if (Wire.available()) {
      size_t i = 0;
      while (Wire.available()) {
        buffer[i++] = Wire.read();
      }
      // Null-terminate the received data to make it a valid C string
      buffer[i] = '\0';

      Serial.println("Received response:");
      Serial.println(buffer);
      // Clear the buffer after reading
      memset(buffer, 0, bufferSize);
      readPending = false;
    } else {
      Serial.println("No response received");
    }
  }

  delay(500);
}
Spoke unit: Receives input through `Wire`, then sends it back when requested for a response:
#include <Wire.h>


// Status indicator LED
#define LED_R 0
#define LED_G 1

// Human input
#define BUTTON_1 7
#define BUTTON_2 6

// Sensor
#define HALL 3

#define ADDRESS 97  // Slave; address randomly chosen

// Variables
const int bufferSize = 50;  // Define the size of your buffer
char buffer[bufferSize];    // Create the character buffer
bool pendingTransmission = false;

void setup() {
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);

  pinMode(BUTTON_1, INPUT_PULLUP);
  pinMode(BUTTON_2, INPUT_PULLUP);

  Serial.begin(115200);

  Wire.begin(ADDRESS);
  Wire.onReceive(receive);
  Wire.onRequest(respond);
}

void loop() {
  Serial.print(".");
  delay(1000);
  Serial.print(".");
  delay(1000);
  Serial.println(".");
  delay(1000);
}

void receive(int bytesToRead) {
  int i = 0;
  Serial.println("");
  while (Wire.available() && i < bytesToRead) {
    buffer[i] = Wire.read();  // Read each byte into the buffer
    i++;
  }


  buffer[bytesToRead] = '\0';
  pendingTransmission = true;

  Serial.print("Received through Wire: ");
  Serial.println(buffer);
}

void respond() {
  Serial.println("Master requested data");

  // Send the prepared response to the master
  if (pendingTransmission) {
    Wire.write(buffer);

    Serial.print("Sent to master: ");
    Serial.println(buffer);
  }

  memset(buffer, 0, bufferSize);
  pendingTransmission = false;
}

It worked! Both heard and said what they needed to. To make sure that they were fit for the use case that I have in mind, I disconnected the spoke unit from USB. This way, it had to draw power from the hub as well as communicate through it. It worked perfectly!!

Connecting board_03 to a dotstar strip

Using Adafruit_DotStar

Adafruit_DotStar uses "soft" SPI by default. There's an alternative constructor that uses hardware SPI. Line 45 of Adafruit_DotStar.cpp (1.2.5) : @brief DotStar constructor for hardware SPI. Must be connected to MOSI, SCK pins.

This constructor needs a Pointer to hardware SPIClass. The SPIClass is defined in ~/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h.

The SparkFun SERCOM tutorial refierences a different SPIClass that is defined within the

Reading through the fab SAM core I came across the option to enable 2 SPIs: Tools menu -> Serial Config.

https://www.reddit.com/r/FastLED/comments/jxrffn/fastled_on_seeeduino_cortex_m0_no_hardware_spi/

https://mtm.cba.mit.edu/2021/2021-10_microcontroller-primer/fab-arduino/

Hardware SPI

Software SPI resulted in a strip.show() time of 1400us, which is too slow for my requirements. After reading up a lot and, frankly, getting a bit desperate at times, Miquel's questions helped me find the key in the Fab SAM core.

variant.h and variant.cpp describe the specifics of the particular board (or chip) you are using.

There is an SPIClass in Adafruit_BusIO/Adafruit_SPIDevice.h:

`$> grep -r SPIClass ~/repos/arduino_playground/libraries/Adafruit_*`. Click to show output
/Users/dani/repos/arduino_playground/libraries/Adafruit_BusIO/Adafruit_SPIDevice.h:typedef uint8_t SPIClass;
/Users/dani/repos/arduino_playground/libraries/Adafruit_BusIO/Adafruit_SPIDevice.h:                     uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = &SPI);
/Users/dani/repos/arduino_playground/libraries/Adafruit_BusIO/Adafruit_SPIDevice.h:                     uint8_t dataMode = SPI_MODE0, SPIClass *theSPI = nullptr);
/Users/dani/repos/arduino_playground/libraries/Adafruit_BusIO/Adafruit_SPIDevice.h:  SPIClass *_spi = nullptr;
/Users/dani/repos/arduino_playground/libraries/Adafruit_BusIO/Adafruit_SPIDevice.cpp:                                       uint8_t dataMode, SPIClass *theSPI) {
/Users/dani/repos/arduino_playground/libraries/Adafruit_DotStar/Adafruit_DotStar.h:  Adafruit_DotStar(uint16_t n, uint8_t o = DOTSTAR_BRG, SPIClass *spi = &SPI);
/Users/dani/repos/arduino_playground/libraries/Adafruit_DotStar/Adafruit_DotStar.h:  Adafruit_DotStar(uint16_t n, uint8_t o = DOTSTAR_BRG, SPIClass *spi = NULL);
/Users/dani/repos/arduino_playground/libraries/Adafruit_DotStar/Adafruit_DotStar.cpp:  @param   spi  Pointer to hardware SPIClass object (default is primary
/Users/dani/repos/arduino_playground/libraries/Adafruit_DotStar/Adafruit_DotStar.cpp:Adafruit_DotStar::Adafruit_DotStar(uint16_t n, uint8_t o, SPIClass *spi)

But the one I'm using is in the Arduino core I'm using (the Fab SAM core), as determined by Cmd-clicking on it in Arduino IDE:

`$> grep -r SPIClass ~/Library/Arduino15/packages/Fab_SAM_Arduino`. Click to show output
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:SPIClass::SPIClass(SERCOM *p_sercom, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI, SercomSpiTXPad PadTx, SercomRXPad PadRx)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::begin()
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::init()
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::config(SPISettings settings)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::end()
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::usingInterrupt(int interruptNumber)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::notUsingInterrupt(int interruptNumber)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::beginTransaction(SPISettings settings)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::endTransaction(void)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::setBitOrder(BitOrder order)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::setDataMode(uint8_t mode)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::setClockDivider(uint8_t div)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:byte SPIClass::transfer(uint8_t data)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:uint16_t SPIClass::transfer16(uint16_t data) {
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::transfer(void *buf, size_t count)
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::attachInterrupt() {
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:void SPIClass::detachInterrupt() {
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI (&PERIPH_SPI,  PIN_SPI_MISO,  PIN_SPI_SCK,  PIN_SPI_MOSI,  PAD_SPI_TX,  PAD_SPI_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI1(&PERIPH_SPI1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI, PAD_SPI1_TX, PAD_SPI1_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI2(&PERIPH_SPI2, PIN_SPI2_MISO, PIN_SPI2_SCK, PIN_SPI2_MOSI, PAD_SPI2_TX, PAD_SPI2_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI3(&PERIPH_SPI3, PIN_SPI3_MISO, PIN_SPI3_SCK, PIN_SPI3_MOSI, PAD_SPI3_TX, PAD_SPI3_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI4(&PERIPH_SPI4, PIN_SPI4_MISO, PIN_SPI4_SCK, PIN_SPI4_MOSI, PAD_SPI4_TX, PAD_SPI4_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI5(&PERIPH_SPI5, PIN_SPI5_MISO, PIN_SPI5_SCK, PIN_SPI5_MOSI, PAD_SPI5_TX, PAD_SPI5_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI6(&PERIPH_SPI6, PIN_SPI6_MISO, PIN_SPI6_SCK, PIN_SPI6_MOSI, PAD_SPI6_TX, PAD_SPI6_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.cpp:  SPIClass SPI7(&PERIPH_SPI7, PIN_SPI7_MISO, PIN_SPI7_SCK, PIN_SPI7_MOSI, PAD_SPI7_TX, PAD_SPI7_RX);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  friend class SPIClass;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:class SPIClass {
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  SPIClass(SERCOM *p_sercom, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI, SercomSpiTXPad, SercomRXPad);
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI1;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI2;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI3;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI4;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI5;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI6;
/Users/dani/Library/Arduino15/packages/Fab_SAM_Arduino/hardware/samd/1.12.0/libraries/SPI/SPI.h:  extern SPIClass SPI7;

It seems that if I wanted to use sercom1 for SPI (which my board was designed around, for future communication with the ESP32S3 hub unit) I will have to release one of the UART sercoms (why are there so many?). In variant.h:

`variant.h`. Click to show.
/*
 * SPI Interfaces
 */
#if defined(TWO_SPI)
#define SPI_INTERFACES_COUNT 2
#else
#define SPI_INTERFACES_COUNT 1
#endif

#define PIN_SPI_MISO         (22u)
#define PIN_SPI_MOSI         (18u)
#define PIN_SPI_SCK          (19u)
#define PIN_SPI_SS           (23u)
#define PERIPH_SPI           sercom3
#define PAD_SPI_TX           SPI_PAD_2_SCK_3
#define PAD_SPI_RX           SERCOM_RX_PAD_0

static const uint8_t SS   = PIN_SPI_SS ;    // The SERCOM SS PAD is available on this pin but HW SS isn't used. Set here only for reference.
static const uint8_t MOSI = PIN_SPI_MOSI ;
static const uint8_t MISO = PIN_SPI_MISO ;
static const uint8_t SCK  = PIN_SPI_SCK ;

#define PIN_SPI1_MISO         (8u)
#define PIN_SPI1_MOSI         (14u)
#define PIN_SPI1_SCK          (15u)
#define PIN_SPI1_SS           (9u)
#define PERIPH_SPI1           sercom2
#define PAD_SPI1_TX           SPI_PAD_2_SCK_3
#define PAD_SPI1_RX           SERCOM_RX_PAD_0

static const uint8_t SS1   = PIN_SPI1_SS ;  // The SERCOM SS PAD is available on this pin but HW SS isn't used. Set here only for reference.
static const uint8_t MOSI1 = PIN_SPI1_MOSI ;
static const uint8_t MISO1 = PIN_SPI1_MISO ;
static const uint8_t SCK1  = PIN_SPI1_SCK ;

The TWO_SPI value is defined by clicking on Arduino IDE -> Tools -> Serial Config -> [choose one with more than one SPI]. So I guess I can change those values to use sercom1 (which is the one that I intended when I designed the board) and relase sercom1 in the other file, variant.cpp. Or Maybe not, since I didn't need to do that in order to use SERCOM2 for SPI?

`SPI.cpp`. Click to show.
#if SPI_INTERFACES_COUNT > 0
  /* In case new variant doesn't define these macros,
   * we put here the ones for Arduino Zero.
   *
   * These values should be different on some variants!
   *
   * The SPI PAD values can be found in cores/arduino/SERCOM.h:
   *   - SercomSpiTXPad
   *   - SercomRXPad
   */
  #ifndef PERIPH_SPI
    #define PERIPH_SPI           sercom4
    #define PAD_SPI_TX           SPI_PAD_2_SCK_3
    #define PAD_SPI_RX           SERCOM_RX_PAD_0
  #endif // PERIPH_SPI
  SPIClass SPI (&PERIPH_SPI,  PIN_SPI_MISO,  PIN_SPI_SCK,  PIN_SPI_MOSI,  PAD_SPI_TX,  PAD_SPI_RX);
#endif
#if SPI_INTERFACES_COUNT > 1
  SPIClass SPI1(&PERIPH_SPI1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI, PAD_SPI1_TX, PAD_SPI1_RX);
#endif
#if SPI_INTERFACES_COUNT > 2
  SPIClass SPI2(&PERIPH_SPI2, PIN_SPI2_MISO, PIN_SPI2_SCK, PIN_SPI2_MOSI, PAD_SPI2_TX, PAD_SPI2_RX);
#endif
#if SPI_INTERFACES_COUNT > 3
  SPIClass SPI3(&PERIPH_SPI3, PIN_SPI3_MISO, PIN_SPI3_SCK, PIN_SPI3_MOSI, PAD_SPI3_TX, PAD_SPI3_RX);
#endif
#if SPI_INTERFACES_COUNT > 4
  SPIClass SPI4(&PERIPH_SPI4, PIN_SPI4_MISO, PIN_SPI4_SCK, PIN_SPI4_MOSI, PAD_SPI4_TX, PAD_SPI4_RX);
#endif
#if SPI_INTERFACES_COUNT > 5
  SPIClass SPI5(&PERIPH_SPI5, PIN_SPI5_MISO, PIN_SPI5_SCK, PIN_SPI5_MOSI, PAD_SPI5_TX, PAD_SPI5_RX);
#endif
#if SPI_INTERFACES_COUNT > 6
  SPIClass SPI6(&PERIPH_SPI6, PIN_SPI6_MISO, PIN_SPI6_SCK, PIN_SPI6_MOSI, PAD_SPI6_TX, PAD_SPI6_RX);
#endif
#if SPI_INTERFACES_COUNT > 7
  SPIClass SPI7(&PERIPH_SPI7, PIN_SPI7_MISO, PIN_SPI7_SCK, PIN_SPI7_MOSI, PAD_SPI7_TX, PAD_SPI7_RX);
#endif

This Adafruit tutorial is a great reference, although I only began to understand it after I had gotten mine working.

It was also interesting to read Leo Kuiper's code to understand how peripheral multiplexing can be used with direct port manipulation.

Using FastLED

FastLED didn't work on a first try, even though it is supposed to fall back to software SPI, just like Adafruit Dotstar.

One clue might be that the blue LED in my board 03, connected to pin PA02, lit up even though I'm not telling it to. It's not a SERCOM pin though. I think FastLED uses its own pin definitions.

Investigating why FastLED doesn't work.

FastLED

pinPeripheral is defined in a file within the ArduinoCore-SAMD. It allows you to set a pin to be used for peripheral multiplexing without touching the variant.cpp pin definitions.

It does! I found this in the FastLED porting guide: "Sometimes "porting" FastLED simply consists of supplying new pin definitions for the given platform".

The pin definitions table is in $ARDUINO_HOME/libraries/FastLED/src/platforms/arm/d21/fastpin_arm_d21.h. Its last entry is for the ARDUINO_SAMD_ZERO:

#define MAX_PIN 42
_FL_DEFPIN( 0,10,0); _FL_DEFPIN( 1,11,0); _FL_DEFPIN( 2, 8,0); _FL_DEFPIN( 3, 9,0);
_FL_DEFPIN( 4,14,0); _FL_DEFPIN( 5,15,0); _FL_DEFPIN( 6,20,0); _FL_DEFPIN( 7,21,0);
_FL_DEFPIN( 8, 6,0); _FL_DEFPIN( 9, 7,0); _FL_DEFPIN(10,18,0); _FL_DEFPIN(11,16,0);
_FL_DEFPIN(12,19,0); _FL_DEFPIN(13,17,0); _FL_DEFPIN(14, 2,0); _FL_DEFPIN(15, 8,1);
_FL_DEFPIN(16, 9,1); _FL_DEFPIN(17, 4,0); _FL_DEFPIN(18, 5,0); _FL_DEFPIN(19, 2,1);
_FL_DEFPIN(20,22,0); _FL_DEFPIN(21,23,0); _FL_DEFPIN(22,12,0); _FL_DEFPIN(23,11,1);
_FL_DEFPIN(24,10,1); _FL_DEFPIN(25, 3,1); _FL_DEFPIN(26,27,0); _FL_DEFPIN(27,28,0);
_FL_DEFPIN(28,24,0); _FL_DEFPIN(29,25,0); _FL_DEFPIN(30,22,1); _FL_DEFPIN(31,23,1);
_FL_DEFPIN(32,22,0); _FL_DEFPIN(33,23,0); _FL_DEFPIN(34,19,0); _FL_DEFPIN(35,16,0);
_FL_DEFPIN(36,18,0); _FL_DEFPIN(37,17,0); _FL_DEFPIN(38,13,0); _FL_DEFPIN(39,21,0);
_FL_DEFPIN(40, 6,0); _FL_DEFPIN(41, 7,0); _FL_DEFPIN(42, 3,0);

#define SPI_DATA 24
#define SPI_CLOCK 23

#define HAS_HARDWARE_PIN_SUPPORT 1

FastLED seems to be recognizing my board as an Arduino Zero: ARDUINO_SAMD_ZERO is defined. It must be defined in the Fab SAM core though because it's also defined when I don't #include <FastLED.h>. After a bit of research, I find that the macro ARDUINO_SAMD_ZERO is defined by the following line in boards.txt: x21e.build.board=SAMD_ZERO. If I change that to x21e.build.board=SAMD_PEPINO my board will identify itself as ARDUINO_SAMD_PEPINO. I then add the following entry to the board definition table:

#elif defined(ARDUINO_SAMD_PEPINO)

#define MAX_PIN 31
_FL_DEFPIN( 0, 0,0); _FL_DEFPIN( 1, 1,0); _FL_DEFPIN( 2, 2,0); _FL_DEFPIN( 3, 3,0);
_FL_DEFPIN( 4, 4,0); _FL_DEFPIN( 5, 5,0); _FL_DEFPIN( 6, 6,0); _FL_DEFPIN( 7, 7,0);
_FL_DEFPIN( 8, 8,0); _FL_DEFPIN( 9, 9,0); _FL_DEFPIN(10,10,0); _FL_DEFPIN(11,11,0);
_FL_DEFPIN(12,12,0); _FL_DEFPIN(13,13,0); _FL_DEFPIN(14,14,0); _FL_DEFPIN(15,15,0);
_FL_DEFPIN(16,16,0); _FL_DEFPIN(17,17,0); _FL_DEFPIN(18,18,0); _FL_DEFPIN(19,19,0);
_FL_DEFPIN(20,20,0); _FL_DEFPIN(21,21,0); _FL_DEFPIN(22,22,0); _FL_DEFPIN(23,23,0);
_FL_DEFPIN(24,24,0); _FL_DEFPIN(25,25,0); _FL_DEFPIN(26,26,0); _FL_DEFPIN(27,27,0);
_FL_DEFPIN(28,28,0); _FL_DEFPIN(29,29,0); _FL_DEFPIN(30,30,0); _FL_DEFPIN(31,31,0);

#define SPI_DATA 14
#define SPI_CLOCK 15

#define HAS_HARDWARE_PIN_SUPPORT 1

I get blinking LEDs on pins 14 and 15!!! I really didn't expect that to work that fast!

TODO video

I'm pretty sure it's using software, since the time to .show() is around 360us and it doesn't change when I remove HAS_HARDWARE_PIN_SUPPORT. But as Tony said, this is as good as it gets! I'm getting the speed I need and I still have room for improvement. Changing the data rate in FastLED.addLeds<DOTSTAR, STRIP_DATA, STRIP_CLOCK, RGB, DATA_RATE_MHZ(24)>(leds, NUMPIXELS); has a small but noticeable effect, at least until 48MHz.

Next step: set the right PlatformIO pin mappings.

Hardware SPI on FastLED

If I add this to the sketch: #define FASTLED_ALL_PINS_HARDWARE_SPI (as per this reddit post, compilation fails with warnings about SPI.

It seems FastLED doesn't support hardware SPI on SAMD21: closed issue and a more recent open one. I offered to help on the open issue but I don't expect it'll be ready in time.

Local Class Notes

Hardware serial

When Bardunio has "USB CDC on boot" disabled, it sends serial communication to the Tx and Rx pins in the board (labelled there, between 21 and 48).

To demonstrate this, Josep uses an FTDI to USB adapter to read from those pins.

Having a conversation across two computers and two boards. Click to show code.
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial0.begin(115200);
  delay(100);
  Serial.println("Let's begin! This is the laptop");
  Serial0.println("This is coming through the rx tx pins. Everyone hates goats BTW.");
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("hello");
  delay(500);

  if (Serial.available()) {
    Serial0.write(Serial.read());
  }

  if (Serial0.available()) {
    Serial.write(Serial0.read());
  }
}

int reading = Serial.read(); will store ASCII codes in reading.

Lighting up an LED on the other board. Click to show code.
void setup() {
  pinMode(48, OUTPUT);
  Serial.begin(115200);
  Serial0.begin(115200);
  delay(100);
  Serial.println("Let's begin! This is the Mac laptop");
  Serial0.println("This is coming through the rx tx pins. Everyone hates goats BTW.");
}

void loop() {
  if (Serial.available()) {
    Serial0.write(Serial.read());
  }

  if (Serial0.available()) {
    char reading = Serial0.read();
    if (reading == '1') {
      digitalWrite(48, HIGH);
    } else if (reading == '0') {
      digitalWrite(48, LOW);
    }

    Serial.write(reading);
  }
}

Software Serial

The ESP32S3 in the Barduino has 2 hardware serial devices, but some like the ATTiny don't.

EspSoftwareSerial implements arduino software serial for ESP32-based boards like the Barduino.

At 115200 baud it misses some characters with the Barduino

I2C

SDA (data) and SCL (clock) pins. You can also arrange a connector to send also ground and power.

Both SDA and SCL need to be pulled up. Barduino already has these pull-ups.

One or more masters, one or more slaves, just two lines.

Sensors have hardcoded addresses. There are not many so there can be collisions. Sensors often offer alternative addreses that you can instruct them to use by pulling pins high or low.

Barduino has SCL and SDA on pins 8 and 9.

Children can never initiate a transmission.

SPI

Another protocol. 6 pins. VCC, ground, reset, plus clock (SCK), MISO (master in, slave out), and MOSI (master out, slave in).

If you have more than one slave, you will need one SCK, one MOSI, one MISO, and on SS per slave.

You can go faster, so transfer intensive devices (eg SD cards) will use it rather than I2C.

Generally the slave code is hardcoded in the devices, the master side is what you'll find in software libraries.

Wireless

Electromagnetic spectrum, antennas, gain...

We'll use the ESP32 S3 in Barduino. It has WiFi and BLE.

MQTT

References

Arduino Zero SERCOM1 SPI slave