We already saw in the two previous parts:
– Part 1: Pi and Arduino (SAMD21) using SPI
– Part 2: Pi and Arduino using Python
This is the last part for now: making a bi-directional connection between the Arduino (SAMD21) and the Pi.
While protocols like RS-232 have both a send (TX) and a receive (RX), other Serial protocols like i2c and SPI work a little different. For i2c / Wire, the standard Arduino libraries give us already very good examples of being a master reader, master writer, slave reader and slave writer so that we can manage to develop our bi-directional communication in our programs. No such luck for SPI.
But as it turns out, our example code already supports bi-directional communication. Every time a byte is written from the master, a byte is also read from the slave.
Slave (SAMD21)
Our sample code based on MartinL and Scotty454 has a line that is crucial for this: “SERCOM1->SPI.DATA.reg” the byte stored in this register is read by the master just after the current byte was written.
In the updated example for Arduino / SAMD21 we have added a buffer and a counter: the buffer contains the bytes that will be sent back to the master. We implemented it as a circular buffer for now (after the last character was sent, it will start over again).
/** * J.A. Korten August 6, 2017 * Bi-directional extension added March 12, 2018 * * Modified based on code by MartinL, Scotty454 */ #include <SPI.h> const int slaveAPin = 10; // SS (PA18 / D10 on SAMD21) const int numChars = 6; int testCharacters[numChars] = {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x3b}; // "Hello;" volatile int indexChars = 0; 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)) { byte currentChar = testCharacters[indexChars]; indexChars++; if (indexChars >= numChars) { indexChars = 0; } SERCOM1->SPI.DATA.reg = currentChar; // 0x41; // was AA (0x41 -> DEC 65, Char: A) } 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 }
For the Raspberry Pi side the proof of concept code is really simple:
""" Proof of concept to show how to make a bi-directional connection between a Raspberry Pi and (in our case) a SAMD21-based Arduino board. J.A. Korten March 12, 2018 spiBiPOC.py """ #!/usr/bin/python import spidev import time spi = spidev.SpiDev() spi.open(0, 0) spi.max_speed_hz = 40000 # write and read bytes def updateSPIMessage(): result = spi.xfer([0x41, 0x62, 0x3B, 0xA]) #some example bytes (Ab;\n) # from result with bytes to String myStr = "".join(map(chr, result)) print(myStr) #show what we received! try: # Keep updating messages while True: updateSPIMessage() time.sleep(0.5) except KeyboardInterrupt: spi.close()
And this yields as terminal output:
That is not bad at all for what I need! Now I want to create proper libraries and a data protocol for it, but so far so good! Hope you also learned something from it.
For further reading on the technical aspects of SPI see Sparkfun.com