For one of our partners we are working on a connection between a SAMD21-based Arduino and a Raspberry Pi (Zero W in this case). One could use several options to connect the Arduino and Pi:
– UART (direct or USB)
– i2c
– SPI
In this case we chose for the latter. Advantages of SPI include the transferspeed and better control than i2c and UART, although the solution also has it’s disadvantages. The Pi wants to be the master (that is very fine in our solution), but normal Arduino’s don’t seem to like that. The SAMD21 uses the SERCOM options and has in fact an option to be a slave.
/** * J.A. Korten August 6, 2017 * Modified based on code by MartinL, Scotty454 */ #include <SPI.h> const int slaveAPin = 10; // SS (PA18 / D10 on SAMD21) void setup() { Serial.begin(115200); pinMode(slaveAPin, INPUT_PULLUP); attachInterrupt(10, SERCOM1_Handler, FALLING); spiSlave_init(); } void loop() { // put your main code here, to run repeatedly: asm ("nop"); } void SERCOM1_Handler() { noInterrupts(); uint8_t data = 0; data = (uint8_t)SERCOM1->SPI.DATA.reg; uint8_t interrupts = SERCOM1->SPI.INTFLAG.reg; //Read SPI interrupt register if (interrupts & (1 << 3)) { SERCOM1->SPI.INTFLAG.bit.SSL = 1; //clear slave select interrupt } if (interrupts & (1 << 2)) { data = SERCOM1->SPI.DATA.reg; //Read data register SERCOM1->SPI.INTFLAG.bit.RXC = 1; //clear receive complete interrupt } if (interrupts & (1 << 1)) { SERCOM1->SPI.INTFLAG.bit.TXC = 1; //clear receive complete interrupt } if (interrupts & (1 << 0)) { SERCOM1->SPI.DATA.reg = 0xAA; } char _data = data; Serial.print(_data); // print received data interrupts(); } void spiSlave_init() { PORT->Group[PORTA].PINCFG[16].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA18 Arduino PIN10 PORT->Group[PORTA].PMUX[8].bit.PMUXE = 0x2; //SERCOM 1 is selected for peripherial use of this pad PORT->Group[PORTA].PINCFG[17].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA18 Arduino PIN10 PORT->Group[PORTA].PMUX[8].bit.PMUXO = 0x2; //SERCOM 1 is selected for peripherial use of this pad PORT->Group[PORTA].PINCFG[18].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA18 Arduino PIN10 PORT->Group[PORTA].PMUX[9].bit.PMUXE = 0x2; //SERCOM 1 is selected for peripherial use of this pad PORT->Group[PORTA].PINCFG[19].bit.PMUXEN = 0x1; //Enable Peripheral Multiplexing for SERCOM1 SPI PA18 Arduino PIN10 PORT->Group[PORTA].PMUX[9].bit.PMUXO = 0x2; //SERCOM 1 is selected for peripherial use of this pad //Disable SPI 1 SERCOM1->SPI.CTRLA.bit.ENABLE = 0; while (SERCOM1->SPI.SYNCBUSY.bit.ENABLE); //Reset SPI 1 SERCOM1->SPI.CTRLA.bit.SWRST = 1; while (SERCOM1->SPI.CTRLA.bit.SWRST || SERCOM1->SPI.SYNCBUSY.bit.SWRST); //Setting up NVIC NVIC_EnableIRQ(SERCOM1_IRQn); NVIC_SetPriority(SERCOM1_IRQn, 2); //Setting Generic Clock Controller!!!! GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM1_CORE) | //Generic Clock 0 GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is the source GCLK_CLKCTRL_CLKEN; // Enable Generic Clock Generator while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); //Wait for synchronisation //Set up SPI Control A Register SERCOM1->SPI.CTRLA.bit.DORD = 0; //MSB first SERCOM1->SPI.CTRLA.bit.CPOL = 0; //SCK is low when idle, leading edge is rising edge SERCOM1->SPI.CTRLA.bit.CPHA = 0; //data sampled on leading sck edge and changed on a trailing sck edge SERCOM1->SPI.CTRLA.bit.FORM = 0x0; //Frame format = SPI SERCOM1->SPI.CTRLA.bit.DIPO = 0; //DATA PAD0 MOSI is used as input (slave mode) SERCOM1->SPI.CTRLA.bit.DOPO = 0x2; //DATA PAD3 MISO is used as output SERCOM1->SPI.CTRLA.bit.MODE = 0x2; //SPI in Slave mode SERCOM1->SPI.CTRLA.bit.IBON = 0x1; //Buffer Overflow notification SERCOM1->SPI.CTRLA.bit.RUNSTDBY = 1; //wake on receiver complete //Set up SPI control B register //SERCOM1->SPI.CTRLB.bit.RXEN = 0x1; //Enable Receiver SERCOM1->SPI.CTRLB.bit.SSDE = 0x1; //Slave Selecte Detection Enabled SERCOM1->SPI.CTRLB.bit.CHSIZE = 0; //character size 8 Bit //SERCOM1->SPI.CTRLB.bit.PLOADEN = 0x1; //Enable Preload Data Register //while (SERCOM1->SPI.SYNCBUSY.bit.CTRLB); //Set up SPI interrupts SERCOM1->SPI.INTENSET.bit.SSL = 0x1; //Enable Slave Select low interrupt SERCOM1->SPI.INTENSET.bit.RXC = 0x1; //Receive complete interrupt SERCOM1->SPI.INTENSET.bit.TXC = 0x1; //Receive complete interrupt SERCOM1->SPI.INTENSET.bit.ERROR = 0x1; //Receive complete interrupt SERCOM1->SPI.INTENSET.bit.DRE = 0x1; //Data Register Empty interrupt //init SPI CLK //SERCOM1->SPI.BAUD.reg = SERCOM_FREQ_REF / (2*4000000u)-1; //Enable SPI SERCOM1->SPI.CTRLA.bit.ENABLE = 1; while (SERCOM1->SPI.SYNCBUSY.bit.ENABLE); SERCOM1->SPI.CTRLB.bit.RXEN = 0x1; //Enable Receiver, this is done here due to errate issue while (SERCOM1->SPI.SYNCBUSY.bit.CTRLB); //wait until receiver is enabled }
I have used several source examples from the Arduino.cc fora. It would be better to wrap it into a library of course since it is too bulky for the main sketch: this is intended as a working example only.
For now I experienced quite some issues using Python (more or less the ‘Golden Language’ on a Raspberry Pi) including “Segmentation faults”. Next best thing is C/C++ so we used this example (see credits at header):
/* * SPI testing utility (using spidev driver) * * Copyright (c) 2007 MontaVista Software, Inc. * Copyright (c) 2007 Anton Vorontsov <avorontsov@ru.mvista.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * Cross-compile with cross-gcc -I/path/to/cross-kernel/include */ #include <stdint.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <getopt.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/types.h> #include <linux/spi/spidev.h> #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) static void pabort(const char *s) { perror(s); abort(); } static const char *device = "/dev/spidev0.0"; static uint8_t mode; static uint8_t bits = 8; static uint32_t speed = 100000; // Note: I had some bytes falling out when using 500 KHz so I switched to 100 KHz. static uint16_t delay; static void transfer(int fd) { int ret; uint8_t tx[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\r', '\n' }; uint8_t rx[ARRAY_SIZE(tx)] = {0, }; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = ARRAY_SIZE(tx), .delay_usecs = delay, .speed_hz = speed, .bits_per_word = bits, }; ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 1) pabort("can't send spi message"); /* for (ret = 0; ret < ARRAY_SIZE(tx); ret++) { if (!(ret % 6)) puts(""); printf("%.2X ", rx[ret]); } puts(""); */ } static void print_usage(const char *prog) { printf("Usage: %s [-DsbdlHOLC3]\n", prog); puts(" -D --device device to use (default /dev/spidev1.1)\n" " -s --speed max speed (Hz)\n" " -d --delay delay (usec)\n" " -b --bpw bits per word \n" " -l --loop loopback\n" " -H --cpha clock phase\n" " -O --cpol clock polarity\n" " -L --lsb least significant bit first\n" " -C --cs-high chip select active high\n" " -3 --3wire SI/SO signals shared\n"); exit(1); } static void parse_opts(int argc, char *argv[]) { while (1) { static const struct option lopts[] = { { "device", 1, 0, 'D' }, { "speed", 1, 0, 's' }, { "delay", 1, 0, 'd' }, { "bpw", 1, 0, 'b' }, { "loop", 0, 0, 'l' }, { "cpha", 0, 0, 'H' }, { "cpol", 0, 0, 'O' }, { "lsb", 0, 0, 'L' }, { "cs-high", 0, 0, 'C' }, { "3wire", 0, 0, '3' }, { "no-cs", 0, 0, 'N' }, { "ready", 0, 0, 'R' }, { NULL, 0, 0, 0 }, }; int c; c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL); if (c == -1) break; switch (c) { case 'D': device = optarg; break; case 's': speed = atoi(optarg); break; case 'd': delay = atoi(optarg); break; case 'b': bits = atoi(optarg); break; case 'l': mode |= SPI_LOOP; break; case 'H': mode |= SPI_CPHA; break; case 'O': mode |= SPI_CPOL; break; case 'L': mode |= SPI_LSB_FIRST; break; case 'C': mode |= SPI_CS_HIGH; break; case '3': mode |= SPI_3WIRE; break; case 'N': mode |= SPI_NO_CS; break; case 'R': mode |= SPI_READY; break; default: print_usage(argv[0]); break; } } } int main(int argc, char *argv[]) { int ret = 0; int fd; parse_opts(argc, argv); fd = open(device, O_RDWR); if (fd < 0) pabort("can't open device"); /* * spi mode */ ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); if (ret == -1) pabort("can't set spi mode"); ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); if (ret == -1) pabort("can't get spi mode"); /* * bits per word */ ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); if (ret == -1) pabort("can't set bits per word"); ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits); if (ret == -1) pabort("can't get bits per word"); /* * max speed hz */ ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); if (ret == -1) pabort("can't set max speed hz"); ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed); if (ret == -1) pabort("can't get max speed hz"); printf("spi mode: %d\n", mode); printf("bits per word: %d\n", bits); printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000); transfer(fd); close(fd); return ret; }
We called the script spidev_test.c and compiled the script:
gcc spidev_test.c -o spidev_test
To run the compiled script use:
./spidev_test
Here are the hardware connections between the SAMD21 and the Pi that I used:
Note: I placed a folder joint over CSPI_0 of the Pi and SS of the SAMD21.
This is what shows in the terminal after running the script on the Pi (twice).
Hope this was useful. Feel free to ask if something is unclear.
See also: http://www.gammon.com.au/spi and http://maxembedded.com/2013/11/the-spi-of-the-avr/
General SPI info: https://www.arduino.cc/en/Reference/SPI and Wikipedia on SPI
More info:
SPI communication basics
Part 2: Pi and Arduino using Python