SPI-based protocol for two-way data-message exchange between Arduinos
Demonstration sketches and a library for a DUE and an MKR 1010 WiFi
Components and supplies
Arduino Due
Arduino MKR 1010 Wifi
Apps and platforms
Arduino IDE
Visual Studio 2022
Project description
Code
Sketch: demo DUE SPI controller
This sketch to run on the DUE
1/* 2This sketch is a demo of two-way SPI data-array exchanges between an Arduino DUE and an Arduino MKR 1010 WiFi. 3It uses a couple of classes (CBT_SPIController on the DUE, CBT_Sercom3Per on the MKR, CBT_SPIMessage on both). 4These SPI classes exchange CBT_SPIMessage objects. These are arrays of uint8_t data, with a header containing some parameters. 5This logic allows two advantages over basic SPI: 61) By exchange of a few leading bytes, the MOSI and MISO user-data exchanges are synchronised. First MISO goes at the same time as first MOSI. 72) The amount of MISO data can exceed the amount of MOSI data 8 9On the DUE, we use the standard SPI.h library, wrapped up in a small class CBT_SPIController to handle the specifics of the protocol. 10On the MKR - with its SERCOM funtionality, we use the Arduino library SercomSPISlave (developed by lenvm) for the configuration. It is then wrapped up 11in a class CBT_SercomSPIPer for the specifics of the protocol. This is so because the standard SPI library doesn't cover the MKR as SPI peripheral. 12 13The MKR 1010 WiFi with the SAM D21 chip features 6 serial communication (SERCOM) channels, and the SercomSPISlave library allows for all of the 6 chip channels. 14However, on the MKR 1010 WiFi board only Sercom channel 3 is straightforward to use: it is by default connected to the board's SPI pins. 15 16Important issues about timing of transfers and tranasctions are explained in the comments of CBT_SPIController. 17(Note on terminology: a transfer is a byte. A transaction is a series of transfers) 18 19In our setup, we use an extra pin to pin connection for the peripheral to pass a "busy / ready" signal to the controller. 20The controller polls this signal, and will start the next transaction once the peripheral is "ready". 21This extra functionality, in contrast to standard SPI or I2C, can allow the peripheral to initiate a transaction by setting this pin low. 22 23With the current settings, the time for a two way transaction of 8192 bytes, as per Demo4, is around 125ms, or 15ms / kByte. 24 25The sketch carries out a few demos in sequence: 261 - A simple exchange of small uint8_t data arrays, showing that the number of miso bytes can exceed the number of mosi bytes. 272 - An exchange of char arrays - simply by casting char to uint8_t 283 - A demo of transfer of uint32_t data, by casting uint32_t to uint8_t and vice versa, again with more miso bytes than mosi bytes. 29 The same pointer cast logic can be used to transfer other data types, incl. double etc. 304 - A test of timing and data integrity by a two-way exchange of arrays of 8192 bytes. After the transaction, the controller verifies that the miso answer matches the mosi data. 31 32Please note that error checking / reporting is not consistently implemented. 33 34CODE: 35The DUE should run: Demo_DUE_SPIControllerV1.ino, this sketch 36The MKR should run: Demo_MKR_SPIPeripheralV1.ino 37 38WIRING: 39Both the DUE and the MKR operate 3.3V, so no level shifts are needed. 40If desired, the MKR can be powered on its VIn from the 5V of the DUE. 41Connect MOSI, SCK, MISO pins from the DUE dedicated SPI header (next to chip) to the MKR SPI pins 8, 9, 10. MOSI to MOSI, SCK to SCK, MISO to MISO. 42Connect the SS signal from the DUE pin 10 to the MKR pin 6 43Connect the "Busy / ready" signal from the MKR pin 7 to the DUE pin 9 44 45*/ 46//INCLUDES 47#include <SPI.h> //The standard SPI 48#include <CBT_SPIMessage.h> //The CBT_SPIMessage class 49#include <CBT_SPIController.h> //The CBT_SPIController class 50//PINS 51const uint16_t PIN_SPISELECT = 10; //SPI channel select SS pin 52const uint16_t PIN_BOARDLED = 13; //Board led 53const uint16_t PIN_SPIPERREADY = 9; //Pin on which the peripheral can pass a ready signal, optional 54//DECLARATIONS 55char strBuf[200]; //Character buffer for general text output 56CBT_SPIController SPICon(PIN_SPISELECT); //The CBT_SPIController object. It needs to know the Slave-Select pin 57uint16_t interTransactionDelay = 0; //A delay, in ms, between transactions. The delays are commented out here, because we now use the SPIPERREADY pin to control timing. 58//For Demo1, the CBT_SPIMesage objects are prepared in setup, hence must be declared globally 59CBT_SPIMessage* pDemo1Miso; 60CBT_SPIMessage* pDemo1Mosi; 61//SETUP 62void setup() { 63 bool bOK; 64 //Preliminaries 65 Serial.begin(115200); 66 while(!Serial); 67 Serial.println("\r\nDUE SPI controller for testing MKR SPI connection"); 68 pinMode(PIN_BOARDLED, OUTPUT); 69 pinMode(PIN_SPISELECT, OUTPUT); 70 pinMode(PIN_SPIPERREADY, INPUT); 71 //Begin the SPI 72 digitalWrite(PIN_SPISELECT, HIGH); //DON'T FORGET, SS pin best be high from the start 73 SPI.begin(); 74 SPICon.setTransferDelay(0); //Sets the inter transfer delay. A minimum value is imposed by the controller (few microseconds), see m_minTransferDelay 75 // 76 //First demo, simple uint8_t exchange, miso dataCount > than mosi dataCount 77 //Wait until the peripheral sets the ready flag, then run first demo. Print some dots to indicate a length of wait 78 while(!digitalRead(PIN_SPIPERREADY)) {delay(1); Serial.print(".");} 79 demo1(); 80 Serial.println("\r\n-----------------------------"); 81 // 82 //Second demo, char data exchange 83 //Wait until the peripheral sets the ready flag, then run second demo 84 //delay(interTransactionDelay); //Alternative to using the ready flag, use a fixed delay allowing the peripheral to digest the past transaction 85 while(!digitalRead(PIN_SPIPERREADY)) {delay(1); Serial.print(".");} 86 demo2(); 87 Serial.println("\r\n-----------------------------"); 88 // 89 //Third demo, example of casting to from uint32_t, miso dataCount > than mosi dataCount 90 //Wait until the peripheral sets the ready flag, then run fourth demo 91 //delay(interTransactionDelay); 92 while(!digitalRead(PIN_SPIPERREADY)) {delay(1); Serial.print(".");} 93 demo3(); 94 Serial.println("\r\n-----------------------------"); 95 // 96 //Fourth demo, exchange of large uint8_t arrays, timing measurement and transfer integrity verification 97 //Wait until the peripheral sets the ready flag, then run third demo 98 //delay(interTransactionDelay); 99 while(!digitalRead(PIN_SPIPERREADY)) {delay(1); Serial.print(".");} 100 demo4(); 101 Serial.println("\r\n-----------------------------"); 102 Serial.println("\r\n--------- END -------------"); 103} 104 105void demo1() { 106 bool bOK; 107 //Demo1 shows exchange of simple CBT_SPIMessage objects between Controller and Peripheral. 108 Serial.println("STARTING DEMO1"); 109 uint8_t demo1MosiArray[20]; //Array for the mosi data to be sent. Size dataSize must be equal / bigger than dataCount 110 uint8_t demo1MisoArray[32]; //Array for the miso data to be received (note that, with this librry, miso can be bigger than mosi) 111 pDemo1Mosi = new CBT_SPIMessage(demo1MosiArray, sizeof(demo1MosiArray)); //The CBT_SPIMessage object for mosi 112 pDemo1Miso = new CBT_SPIMessage(demo1MisoArray, sizeof(demo1MisoArray)); //The CBT_SPIMessage object for miso 113 bOK = pDemo1Mosi->writeHeader(101, 10, 102, 6); //For mosi, write the header: message ID; type, a reference (not used), dataCount 114 if(!bOK) Serial.println("ERROR writeHeader"); 115 for(uint8_t i = 0; i < pDemo1Mosi->m_dataCount; i++) pDemo1Mosi->getpData()[i] = 51 + i; //Write some mosi data, type uint8_t 116 //Do the transaction, i.e. exchange of MOSI and MISO message objects 117 SPICon.doTransaction(pDemo1Mosi, pDemo1Miso); 118 //Print the sent MOSI message 119 Serial.println("MOSI"); 120 pDemo1Mosi->printHeader(); 121 pDemo1Mosi->printData(); 122 //Print the received MISO message 123 Serial.println("MISO"); 124 pDemo1Miso->printHeader(); 125 pDemo1Miso->printData(); 126 Serial.println("END OF DEMO1"); 127} 128void demo2() { 129 bool bOK; 130 //Demo2 exchanges two char arrays. 131 Serial.println("STARTING DEMO2"); 132 uint8_t demo2MosiArray[64]; //Array for the mosi data to be sent 133 uint8_t demo2MisoArray[64]; //Array for the miso data to be received (note that, with this librry, miso can be bigger than mosi) 134 //In this case the message objects are constructed on the heap 135 CBT_SPIMessage* pDemo2Mosi = new CBT_SPIMessage(demo2MosiArray, sizeof(demo2MosiArray)); 136 CBT_SPIMessage* pDemo2Miso = new CBT_SPIMessage(demo2MisoArray, sizeof(demo2MisoArray)); 137 bOK = pDemo2Mosi->writeHeader(201, 20, 0, 0); //mosi userDataCount set to 0, it will be determined by subsequent writeCharData() 138 if(!bOK) Serial.println("ERROR writeHeader"); 139 bOK = pDemo2Mosi->writeCharData("Humpty Dumpty sat on the wall"); 140 if(!bOK) Serial.println("ERROR writeCharData"); 141 //Do the transaction, i.e. exchange of MOSI and MISO message objects 142 SPICon.doTransaction(pDemo2Mosi, pDemo2Miso); 143 //Print the sent MOSI message 144 Serial.println("MOSI"); 145 pDemo2Mosi->printHeader(); 146 pDemo2Mosi->printDataAsString(); 147 //Print the received MISO message 148 Serial.println("MISO"); 149 pDemo2Miso->printHeader(); 150 pDemo2Miso->printDataAsString(); 151 Serial.println("END OF DEMO2"); 152} 153void demo3() { 154 bool bOK; 155 //Demo1 shows exchange of simple CBT_SPIMessage objects between Controller and Peripheral. 156 Serial.println("STARTING DEMO3"); 157 uint8_t demo3MosiArray[20]; //Array for the mosi data to be sent 158 uint8_t demo3MisoArray[32]; //Array for the miso data to be received (note that, with this library, miso can be bigger than mosi) 159 CBT_SPIMessage* pDemo3Mosi = new CBT_SPIMessage(demo3MosiArray, sizeof(demo3MosiArray)); //The CBT_SPIMessage object for mosi 160 CBT_SPIMessage* pDemo3Miso = new CBT_SPIMessage(demo3MisoArray, sizeof(demo3MisoArray)); //The CBT_SPIMessage object for miso 161 uint16_t n32Val = 4; 162 uint32_t* p32Mosi = (uint32_t*) demo3MosiArray; 163 for (int i = 0; i < n32Val; i++) p32Mosi[i] = 22222222 * (i + 1); //Some data needing 32 bit 164 uint16_t dataCount3 = 4 * n32Val; 165 pDemo3Mosi = new CBT_SPIMessage(demo3MosiArray, sizeof(demo3MosiArray), 301, 10, 302, dataCount3); 166 //Do the transaction, i.e. exchange of MOSI and MISO message objects 167 SPICon.doTransaction(pDemo3Mosi, pDemo3Miso); 168 //Print the MOSI message 169 Serial.println("MOSI"); 170 pDemo3Mosi->printHeader(); 171 uint32_t* p32MosiData = (uint32_t*) pDemo3Mosi->getpData(); 172 Serial.println("Mosi 32 bit data"); 173 for(int i = 0; i < pDemo3Mosi->m_dataCount / 4; i++) { 174 Serial.println(p32MosiData[i]); 175 } 176 //Print the MISO message 177 Serial.println("MISO"); 178 pDemo3Miso->printHeader(); 179 uint32_t* p32MisoData = (uint32_t*) pDemo3Miso->getpData(); 180 Serial.println("Miso 32 bit data"); 181 for(int i = 0; i < pDemo3Miso->m_dataCount / 4; i++) { 182 Serial.println(p32MisoData[i]); 183 } 184 Serial.println("END OF DEMO3"); 185} 186void demo4() { 187 uint32_t millisStart, millisDuration; 188 bool bOK; 189 Serial.println("STARTING DEMO4"); 190 uint8_t demo4MosiArray[8500]; //Array for the mosi data to be sent 191 uint8_t demo4MisoArray[8500]; //Array for the miso data to be received (note that, with this librry, miso can be bigger than mosi) 192 CBT_SPIMessage* pDemo4Mosi = new CBT_SPIMessage(demo4MosiArray, sizeof(demo4MosiArray)); //The CBT_SPIMessage object for mosi 193 CBT_SPIMessage* pDemo4Miso = new CBT_SPIMessage(demo4MisoArray, sizeof(demo4MisoArray)); //The CBT_SPIMessage object for miso 194 uint16_t dataCount = 8192; //Data size 195 bOK = pDemo4Mosi->writeHeader(401, 10, 402, dataCount); 196 if(!bOK) Serial.println("ERROR writeHeader"); 197 for(int i = 0; i < pDemo4Mosi->m_dataCount; i++) pDemo4Mosi->getpData()[i] = i%256; //Make some data that will allow verification of results 198 millisStart = millis(); 199 //Do the transaction, i.e. exchange of MOSI and MISO message objects, measure the time taken 200 SPICon.doTransaction(pDemo4Mosi, pDemo4Miso); 201 millisDuration = millis() - millisStart; 202 sprintf(strBuf, "Duration of the transaction: %u ms", millisDuration); Serial.println(strBuf); 203 //Print the sent MOSI message, header only 204 Serial.println("MOSI"); 205 pDemo4Mosi->printHeader(); 206 //Print the received MISO message, header only 207 Serial.println("MISO"); 208 pDemo4Miso->printHeader(); 209 //Check for data corruption 210 //If data corruption occurs: 211 // - Verify that the summ of mosi(i) and miso(i) add up to 255 212 // - Verify that mosi and miso data counts are equal 213 // - if so, increase and play with the transfer delay (in setup) and with the delay between the demos. The issue is that the peripheral must have time 214 // to complete handling a message before the next can be sent, especially is Serial output is involved. This may take tens of ms. 215 if(pDemo4Mosi->m_dataCount != pDemo4Miso->m_dataCount) Serial.println("Data corruption: miso datacount is not equal to mosi datacount"); 216 else Serial.println("Mosi and miso data counts are equal, no sign of data corruption"); 217 bool bCheck = true; 218 uint16_t nCheck = 0; 219 for (int i = 0; i < pDemo4Mosi->m_dataCount; i++) nCheck += pDemo4Mosi->getpData()[i] + pDemo4Miso->getpData()[i] - 255; 220 if(nCheck != 0) {Serial.print("Data corruption: sum of mosi and miso data is not equal 0, nCheck = "); Serial.println(nCheck);} 221 else Serial.println("Check of sum of mosi and miso values is successful, no sign of data corruption"); 222 Serial.println("END OF DEMO4"); 223} 224//LOOP 225void loop() { 226}
Sketch: demo MKR 1010 WiFi peripheral
This code to run on the MKR
1/* 2 Example code for SPI data exchange using the SERCOMSPISlave library: DUE <-> MKR 3 4 The SERCOMSPISlave library is written and distributed by lenvm, 2022, GitHub : https://github.com/lenvm 5 Reference: https://github.com/lenvm/SercomSPISlave 6 The SercomSPISlave library supports setting up SPI slave (or peripheral) functionality on ATSAMD21 boards. The ATSAMD21 chip provides 6 7 Sercom (Serial Communication) units, and the library supports all / any of these. 8 The example going with the library supplies the code for setting up Sercom units as SPI and transmitting a byte. 9 The Arduino forum makes reference to the library in: https://forum.arduino.cc/t/spi-slave-on-mkr-gsm-1400/696361/2 10 11 For an MKR 1010 WiFi, only the Sercom3 unit can be used for SPI without major wiring effort. 12 Secrom3 is wired, by default, to the three SPI pins: MOSI = 8, SCK = 9, MISO = 10. 13 To function as peripheral (slave), it also needs an SS (slave select) pin. Using the SercomSPISlave library, this is provided 14 by routing SS to pin 6. 15 16 A few things to note: 17 - The DUE is set for SPI mode0. It was confirmed by scope that indeed the pulse train follows mode0. 18 However, on the MKR, I needed to set the parameters CPOL (clock polarity) and CPHA (clock phase) to 1 in order to have correct data transfers. 19 See the function CBT_Sercom3Per::init(). I can't explain. 20 - The Sercom unit provides an interrupt flag 'Data Register Empty' if and when the data register is empty and can therefore be written to. 21 It seems to be important, whenever this interrupt is raised, to always write to the register (SERCOM3->SPI.DATA.reg = misoData;). 22 If not, the interrupt keeps looping. The consequence is that the board becomes unresponsive and doesn't even react to an IDE USB upload - a situation 23 well documented on the forum. It this happens: reset the board by quickly pressing the reset button; look in IDE Tools/Port for the port 24 on which the board reconnects; upload clean code. The board will return to the original port. 25 - In normal data transfer operation, the interrupt that invokes the ISR SERCOM3_Handler carries 26 both the flags Data Register Empty and Data received Complete. 27 - In SPI, there is a byte lag between miso and miso. On the SAMD21, it is specified that the first miso byte in a transaction is meaningless. 28 I found it complicated even to handle the second miso properly: miso starts to work ok only on the third transaction. 29 For that reason, the code here exchanges a couple (default 4) of discardable bytes before the user arrays are exchanged. 30 The advantage is that we now have a simple mirroring of the mosi and miso arrays. 31 - If debugging is needed, it is ok to use Serial.println inside of the Sercom3_Handler. However, it will then be absoluetely necessary 32 to slow down the transfer rate. This is done on the controller by introducing delay() between transfers, like a couple of ms. 33 - I tried variations of the controller's SPI clock speed, SPISettings on the controller. 34 - With 1Mhz, I get correct results and can transfer an 8192 byte array in 76ms. 35 - With 8Mhz, I see a loss of data in demo2: mosiByteCount is less than 8192. 36 - With 8Mhz, I can correct that by delays of 4 microseconds between transfers (in function bt_Transaction on the DUE). The time is then 52ms. 37 - For my purpose, 1Mhz is more than enough, so I fix 1Mhz and did not experiment further. 38*/ 39//INCLUDES 40#include <SercomSPISlave.h> //The lenvm library 41#include <CBT_Sercom3Per.h> //The class encapsulating Sercom3 SPI (which also includes the CBT_SPIMessage class) 42//PINS 43//NOTE we use the board's dedicated pins for MOSI, MISO, and SCK, and pin 6 for SS. SS is PA20. 44const uint8_t PIN_SERCOMSPIREADY = 7; //The pin for the SERCOMSPIREADY connection: 0 = "I am busy", 1 = "Ready for transaction" 45//DECLARATIONS 46char strBuf[200]; //Text buffer to use sprintf for formatting 47CBT_Sercom3Per SPIPer; //The CBT_Sercom3SPI object 48bool flagSercom3Handler; //Flag from Sercom3_handler to loop, upon completion of a transaction 49//Declare the arrays for user-data for each message 50uint8_t demo1MosiArray[8]; 51uint8_t demo1MisoArray[32]; 52uint8_t demo2MosiArray[64]; 53uint8_t demo2MisoArray[64]; 54uint8_t demo3MosiArray[200]; 55uint8_t demo3MisoArray[200]; 56uint8_t demo4MosiArray[9000]; 57uint8_t demo4MisoArray[9000]; 58//Declare pointers for the messages 59CBT_SPIMessage* pDemo1Mosi; //Demo1 MOSI message will be read from controller 60CBT_SPIMessage* pDemo1Miso; //Demo1 MISO message will be sent to controller 61CBT_SPIMessage* pDemo2Mosi; //Demo2 MOSI message will be read from controller 62CBT_SPIMessage* pDemo2Miso; //Demo2 MISO message will be sent to controller 63CBT_SPIMessage* pDemo3Mosi; //Demo3 MOSI message will be read from controller 64CBT_SPIMessage* pDemo3Miso; //Demo3 MISO message will be sent to controller 65CBT_SPIMessage* pDemo4Mosi; //Demo4 MOSI message will be read from controller 66CBT_SPIMessage* pDemo4Miso; //Demo4 MISO message will be sent to controller 67//----------------------------------------------------------------------- 68//SETUP 69void setup() 70{ 71 bool bOK; //Return value 72 Serial.begin(115200); 73 while(!Serial); 74 Serial.println("\r\nDemo_MKR_SPIPeripheral"); 75 pinMode(PIN_SERCOMSPIREADY, OUTPUT); //The output pin for the busy / ready signal 76 digitalWrite(PIN_SERCOMSPIREADY, LOW); //And set it to busy while setting up 77//SPI: Default initialisation for MKR1010 WiFi 78 SPIPer.begin(); //Initialisation of the Sercom3 79 Serial.println("SPI initialised"); 80 //PREPARE MESSAGE OBJECTS FOR DEMO1 81 pDemo1Mosi = new CBT_SPIMessage(demo1MosiArray, sizeof(demo1MosiArray), 255); //Example of initialising each element - for debugging purposes. Mosi remains empty here 82 pDemo1Miso = new CBT_SPIMessage(demo1MisoArray, sizeof(demo1MisoArray)); //Set array pointer and size, always use sizeof 83 uint16_t dataCount1 = 9; //The number of miso data bytes to be sent 84 pDemo1Miso->writeHeader(102, 10, 101, dataCount1); //Set an ID, type, reference ID, and dataCount. My convention: 10 for uint_8, 20 for char 85 for(int i = 0; i < dataCount1; i++) pDemo1Miso->getpData()[i] = 31 + i; //Write some data 86 //PREPARE MESSAGE OBJECTS FOR DEMO2 87 pDemo2Mosi = new CBT_SPIMessage(demo2MosiArray, sizeof(demo2MosiArray)); 88 pDemo2Miso = new CBT_SPIMessage(demo2MisoArray, sizeof(demo2MisoArray), 202, 20, 201, 0); //Here writing the header data through the constructor. DataCount will be written by writeCharData 89 bOK = pDemo2Miso->writeCharData("Humpty Dumpty had a great fall"); //Write some char data, which also sets the datacount 90 if(!bOK) Serial.println("ERROR writeCharData"); 91 //PREPARE MESSAGE OBJECTS FOR DEMO3 92 pDemo3Mosi = new CBT_SPIMessage(demo3MosiArray, sizeof(demo3MosiArray)); 93 uint16_t n32Val = 8; 94 uint32_t* p32Miso = (uint32_t*) demo3MisoArray; 95 for (int i = 0; i < n32Val; i++) p32Miso[i] = 11111111 * (i + 1); //Some data needing 32 bit 96 uint16_t dataCount3 = 4 * n32Val; 97 pDemo3Miso = new CBT_SPIMessage(demo3MisoArray, sizeof(demo3MisoArray), 302, 10, 301, dataCount3); 98 //PREPARE MESSAGE OBJECTS FOR DEMO4 99 pDemo4Mosi = new CBT_SPIMessage(demo4MosiArray, sizeof(demo4MosiArray)); 100 uint16_t dataCount4 = 8192; 101 pDemo4Miso = new CBT_SPIMessage(demo4MisoArray, sizeof(demo4MisoArray), 402, 10, 401, dataCount4); 102 for(int i = 0; i < dataCount4; i++) pDemo4Miso->getpData()[i] = 255 - i%256; 103 //PREPARE TO START WITH DEMO1: we preset the Demo1 message pointers to the SPIPer object 104 SPIPer.setMosi(pDemo1Mosi); 105 SPIPer.setMiso(pDemo1Miso); 106 //READY FOR TRANSACTION 107 digitalWrite(PIN_SERCOMSPIREADY, HIGH); 108} 109//----------------------------------------------------------------------- 110//LOOP 111void loop() 112{ 113 if(flagSercom3Handler) { 114 flagSercom3Handler = false; 115 Serial.print("\r\n***** loop called by flagSercom3Handler: "); 116 Serial.print("SPIMessage received, ID = "); Serial.println(SPIPer.getMosi()->m_ID); 117 //DEMO1 118 if(SPIPer.getMosi()->m_ID == 101) 119 { 120 Serial.println("--------------------------"); 121 Serial.println("DEMO1"); 122 pDemo1Mosi->printHeader(); 123 pDemo1Mosi->printData(); 124 pDemo1Mosi->printDataArray(); 125 //We now set the SPIPer pointers to the ones for DEMO2 126 Serial.println("Swapping pointers"); 127 SPIPer.setMosi(pDemo2Mosi); 128 SPIPer.setMiso(pDemo2Miso); 129 } 130 //DEMO2 131 if(SPIPer.getMosi()->m_ID == 201) 132 { 133 Serial.println("--------------------------"); 134 Serial.println("DEMO2"); 135 pDemo2Mosi->printHeader(); 136 pDemo2Mosi->printDataAsString(); 137 //We now set the SPIPer pointers to the ones for DEMO3 138 Serial.println("Swapping pointers"); 139 SPIPer.setMosi(pDemo3Mosi); 140 SPIPer.setMiso(pDemo3Miso); 141 } 142 //DEMO3 143 if(SPIPer.getMosi()->m_ID == 301) 144 { 145 Serial.println("--------------------------"); 146 Serial.println("DEMO3"); 147 pDemo3Mosi->printHeader(); 148 uint32_t* p32MosiData = (uint32_t*) pDemo3Mosi->getpData(); //Cast the uint8_t* data array pointer to a uint32_t* pointer 149 Serial.println("Mosi 32 bit data"); 150 for(int i = 0; i < pDemo3Mosi->m_dataCount / 4; i++) { //As we will now access the array in 4-byte elements, we need to divide the datacount by 4 151 Serial.println(p32MosiData[i]); //And access the array with the uint32_t* pointer 152 } 153 pDemo3Miso->printHeader(); 154 Serial.println("Miso 32 bit data"); 155 uint32_t* p32MisoData = (uint32_t*) pDemo3Miso->getpData(); //The pointer cast logic is the same as for mosi 156 for(int i = 0; i < pDemo3Miso->m_dataCount / 4; i++) { 157 Serial.println(p32MisoData[i]); 158 } 159 //We now set the SPIPer pointers to the ones for DEMO4 160 Serial.println("Swapping pointers"); 161 SPIPer.setMosi(pDemo4Mosi); 162 SPIPer.setMiso(pDemo4Miso); 163 } 164 //DEMO4 165 if(SPIPer.getMosi()->m_ID == 401) 166 { 167 Serial.println("--------------------------"); 168 Serial.println("DEMO4"); 169 pDemo4Mosi->printHeader(); 170 pDemo4Miso->printHeader(); 171 Serial.println("Swapping pointers"); 172 //NOTE!!!!! Since we may repeat the controller, we have to set the pointers back to DEMO1 173 SPIPer.setMosi(pDemo1Mosi); 174 SPIPer.setMiso(pDemo1Miso); 175 } 176 //COMMON: at end of transaction, no longer busy 177 //delay(1000); //Optional, if you want a delay between demos and / or test the SPIREADY pin 178 digitalWrite(PIN_SERCOMSPIREADY, HIGH); 179 } 180} 181//----------------------------------------------------------------------- 182//THE SERCOM3 HANDLER: it just invokes the handler code of the SPIPer class 183void SERCOM3_Handler() { 184 digitalWrite(PIN_SERCOMSPIREADY, LOW); //Set the pin to let the controller know we are busy 185 flagSercom3Handler = SPIPer.inSercom3Handler(); //Invoke the handler code of the SPIPer class 186} 187//-----------------------------------------------------------------------
Message class header file (C++)
To be included in Arduino libraries directory
1/* 2Updates: 320250413: reference date 4*/ 5 6/* 7Class CBT_SPIMessage works together with classes CBT_SPIController and CBT_Sercom3Per to exchange data messages between two Arduinos. 8CBT_Sercom3Per is written specifically for an Arduino MKR 1010 WiFi with its SERCOM serial interfaces. However, it can readily be rewritten for 9other Arduino. 10 11This class encapsulates the SPI messages. A message consists of a header and a pointer to an uint8_t data-array. 12Management of the data arrays, and ensuring their sufficient size, is the responsibility of the user. 13 14In a typical use-case, the user will take the following steps: 15- On the controller: 16 - Declare an array for MOSI data, create a CBT_SPI object and pass to it the array pointer and size (in uint8_t). 17 - Supply the MOSI header data, which is all optional except for dataCount: the number of bytes to transfer 18 - Fill the array with some data. Data of type other than uint8_t can be cast to uint8_t. 19 - Declare an array for receiving MISO data, create a CBT_SPI object and pass to it the array pointer and size (in uint8_t). No other data is needed. 20- On the peripheral: 21 - Declare an array for MISO data, create a CBT_SPI object and pass to it the array pointer and size (in uint8_t). 22 - Supply the MISO header data, which is all optional except for dataCount: the number of bytes to transfer 23 - Fill the array with some data. Data of type other than uint8_t can be cast to uint8_t. 24 - Declare an array for receiving MOSI data, create a CBT_SPI object and pass to it the array pointer and size (in uint8_t). No other data is needed. 25 26Then, use the functionality of CBT_SPIController and CBT_Sercom3Per to exchange data. After the transaction, the MOSI CBT_SPIMessage object on the peripheral 27will be an exact copy of the MOSI on the controller, and the MISO on the controller will be an exact copy of the MISO on the peripheral. 28 29Note that arrays do not have to be of matching size, as long as they are big enough. Also note that - contrary to what one may expect in SPI - the datacount of MISO 30may be larger than the datacount of MOSI. After sending MOSI, the controller will continue to make empty transfers to collect MISO up to the MISO datacount. 31 32The three optional fields in the header may be useful as follows: 33- m_ID can be used as a message ID to be recognised by the receiving side to choose the right processing 34- m_type can be used to indicate a type of the message. I have used 0 = empty, 10 = binary (uint8_t), 20 is char, 30 = json 35- m_refID can be used to refer to another message ID, for example to request an answer message 36 37The last three functions will not normally be used directly. 38- The function sendMosiReadMiso is called by CBT_SPIController. It performs the actual sequence of byte transfers. 39- The functions readMosi and writeMiso are called by CBT_Sercom3Per, on a byte by byte basis. They get the bytes from or sort the bytes too the right data fields. 40 41To deal easily with char datatype we provide the functions writeCharData(char* chArray). This function makes the cast of char* to uint8* and sets the 42correct datacount, allowing for the \0 character. It is matched by the function printDataAsString(). 43 44For eventual debugging purposes we provide an overload of the constructor that initialises the entire data array to a given value, and a function printDataArray that 45prints the entire array. 46*/ 47#ifndef CBT_SPIMESSAGE_H 48#define CBT_SPIMESSAGE_H 49#include <arduino.h> 50#include <SPI.h> 51 52class CBT_SPIMessage { 53 private: 54 uint8_t* m_pData; //Pointer to the data array 55 uint16_t m_dataSize; //Size of the user data array, in bytes 56 public: 57 const uint8_t m_headerSize = 8; //Size of the message header, in bytes 58 uint16_t m_ID; //User-defined ID of the message 59 uint16_t m_type; //Type of the message: uint8_t, char, json. Can be user-defined 60 uint16_t m_refID; //User-defined reference to another message: allows connecting a response to an incoming message 61 uint16_t m_dataCount; //Number of data bytes to be transferred, must be <= Size 62 public: 63 //CONSTRUCTORS & DESTRUCTOR 64 CBT_SPIMessage(uint8_t* pData, uint16_t dataSize); //Constructs object with an empty header and empty data array, typical for mosi in peripheral and miso in controller 65 CBT_SPIMessage(uint8_t* pData, uint16_t dataSize, uint16_t ID, uint16_t type, uint16_t refID, uint16_t dataCount); //Full constructor 66 CBT_SPIMessage(uint8_t* pData, uint16_t dataSize, uint16_t initVal); //Empty constructor initialiasing all data to initVal. Maybe useful for debugging 67 //WRITING MESSAGE HEADER AND DATA 68 bool writeHeader(uint16_t ID, uint16_t type, uint16_t refID, uint16_t dataCount); //Writes the message header values 69 bool writeCharData(const char* chArr); //Replaces the m_pData with a pointer to a char array. Sets m_dataCount. 70 bool copyCharData(const char* chArr); //Stores a copy of the char arrayin the data array. Sets m_dataCount. 71 //GETTERS 72 uint8_t* getpData(); //Returns a pointer to the data array, type is uint8_t* 73 uint16_t getDataSize(); //Returns the size of the data array, in uint8_t (i.e. bytes) 74 //UTILITY FUNCTIONS PRINTING MESSAGE INFO 75 bool printHeader() const; //Utility: prints the header data to Serial 76 bool printData() const; //Utility: prints the userdata to Serial 77 bool printDataAsString() const; //Utility: prints the userdata to Serial with a cast from uint8_t* to char* 78 bool printDataArray() const; //Utility: for debugging purposes, print the entire userData array 79 //TO BE USED BY CONTROLLER FOR TRANSFERS: needs to be seen together with class CBT_SPIController 80 bool sendMosiReadMiso(CBT_SPIMessage* pMiso, uint16_t transferDelay); //Called by controller to send MOSI and read MISO 81 //TO BE USED BY PERIPHERAL IN INTERRUPT HANDLING: need to be seen together with class CBT_Sercom3Per 82 uint16_t readMosi(uint16_t byteCount, uint8_t data, uint16_t mosiDataReadCount); //Called by peripheral for incoming MOSI bytes. Sorts databytes into the right variable in header or userdata (byteCount = 0 is start of message) 83 uint8_t writeMiso(uint16_t byteCount) const; //Called by peripheral for writing MISO bytes. Returns the databyte for a given byteCount (byteCount = 0 is start of message) 84}; 85#endif //CBT_SPIMESSAGE_H
Message class implementation file (C++)
To be included in Arduino libraries directory
1#include "CBT_SPIMessage.h" 2CBT_SPIMessage::CBT_SPIMessage(uint8_t* pData, uint16_t dataSize) { 3 m_pData = pData; 4 m_dataSize = dataSize; 5}; 6CBT_SPIMessage::CBT_SPIMessage(uint8_t* pData, uint16_t dataSize, uint16_t initVal) { 7 m_pData = pData; 8 m_dataSize = dataSize; 9 for (int i = 0; i < m_dataSize; i++) pData[i] = initVal; 10}; 11CBT_SPIMessage::CBT_SPIMessage(uint8_t* pData, uint16_t dataSize, uint16_t ID, uint16_t type, uint16_t refID, uint16_t dataCount) { 12 m_pData = pData; 13 m_dataSize = dataSize; 14 m_ID = ID; 15 m_type = type; 16 m_refID = refID; 17 m_dataCount = dataCount; 18}; 19//WRITING MESSAGE HEADER AND DATA 20bool CBT_SPIMessage::writeHeader(uint16_t ID, uint16_t type, uint16_t refID, uint16_t dataCount){ 21 m_ID = ID; 22 m_type = type; 23 m_refID = refID; 24 m_dataCount = dataCount; 25 if (m_dataCount > m_dataSize) return false; 26 return true; 27} 28bool CBT_SPIMessage::writeCharData(const char* chArr) { 29 m_dataCount = strlen((char*) chArr) + 1; 30 if (m_dataCount > m_dataSize) return false; 31 m_pData = (uint8_t*) chArr; 32 return true; 33} 34bool CBT_SPIMessage::copyCharData(const char* chArr) { 35 m_dataCount = strlen(chArr) + 1; 36 strcpy((char*)m_pData, chArr); 37} 38//GETTERS 39uint8_t* CBT_SPIMessage::getpData() { 40 return m_pData; 41} 42uint16_t CBT_SPIMessage::getDataSize() { 43 return m_dataSize; 44} 45//UTILITY FUNCTIONS PRINTING MESSAGE INFO 46bool CBT_SPIMessage::printHeader() const { 47 char chBuf[300]; 48 sprintf(chBuf, "MESSAGE: dataSize = %u HEADER: ID = %u type = %u refID = %u dataCount = %u", m_dataSize, m_ID, m_type, m_refID, m_dataCount); 49 Serial.println(chBuf); 50 return true; 51} 52bool CBT_SPIMessage::printData() const { 53 char chBuf[100]; 54 sprintf(chBuf, "MESSAGE: ID = %u DATA", m_ID); Serial.println(chBuf); 55 for(int i = 0; i < m_dataCount; i++) { 56 sprintf(chBuf, "%10u -> %10u", i, m_pData[i]); 57 Serial.println(chBuf); 58 } 59 return true; 60} 61bool CBT_SPIMessage::printDataAsString() const { //TODO protect against string > strBuf 62 char chBuf[100]; 63 sprintf(chBuf, "MESSAGE: ID = %u DATA", m_ID); Serial.println(chBuf); 64 Serial.println((char*) m_pData); 65 return true; 66} 67bool CBT_SPIMessage::printDataArray() const { 68 char chBuf[100]; 69 sprintf(chBuf, "MESSAGE: ID = %u DATA ARRAY", m_ID); Serial.println(chBuf); 70 for(int i = 0; i < m_dataSize; i++) { 71 sprintf(chBuf, "%10u %10u", i, m_pData[i]); 72 Serial.println(chBuf); 73 } 74 return true; 75} 76//TO BE USED BY CONTROLLER FOR TRANSFERS 77bool CBT_SPIMessage::sendMosiReadMiso(CBT_SPIMessage* pMiso, uint16_t transferDelay) { 78 uint8_t bh, bl; 79 //HEADER BYTES 80 bh = SPI.transfer((m_ID >> 8) & 0xff); delayMicroseconds(transferDelay); 81 bl = SPI.transfer(m_ID & 0xff); delayMicroseconds(transferDelay); 82 //Serial.print("In sendMosiReadMiso, sending, ID: "); Serial.println(m_ID); 83 if (pMiso != NULL) pMiso->m_ID = bh * 256 + bl; 84 //Serial.print("In sendMosiReadMiso, receiving, ID: "); Serial.println(pMiso->m_ID); 85 bh = SPI.transfer((m_type >> 8) & 0xff); delayMicroseconds(transferDelay); 86 bl = SPI.transfer(m_type & 0xff); delayMicroseconds(transferDelay); 87 if (pMiso != NULL) pMiso->m_type = bh * 256 + bl; 88 bh = SPI.transfer((m_refID >> 8) & 0xff); delayMicroseconds(transferDelay); 89 bl = SPI.transfer(m_refID & 0xff); delayMicroseconds(transferDelay); 90 if (pMiso != NULL) pMiso->m_refID = bh * 256 + bl; 91 bh = SPI.transfer((m_dataCount >> 8) & 0xff); delayMicroseconds(transferDelay); 92 bl = SPI.transfer(m_dataCount & 0xff); delayMicroseconds(transferDelay); 93 if (pMiso != NULL) pMiso->m_dataCount = bh * 256 + bl; 94 //DATA 95 static uint16_t misoReadCount; 96 misoReadCount = 0; 97 if (pMiso != NULL) { 98 for (int i = 0; i < m_dataCount; i++) { 99 uint8_t dat = SPI.transfer(m_pData[i]); delayMicroseconds(transferDelay); 100 if (i < pMiso->m_dataSize & i < pMiso->m_dataCount) { 101 pMiso->m_pData[i] = dat; 102 misoReadCount++; 103 } 104 } 105 //If the MISO userDataSize > mosiUserDataSize, keep transferring 0's and store the MISO 106 if (pMiso->m_dataCount > m_dataCount) { 107 for (int i = m_dataCount; i < pMiso->m_dataCount; i++) { 108 uint8_t dat = SPI.transfer(0); delayMicroseconds(transferDelay); 109 if (i < pMiso->m_dataSize & i < pMiso->m_dataCount) { 110 pMiso->m_pData[i] = dat; 111 misoReadCount++; 112 } 113 } 114 } 115 pMiso->m_dataCount = misoReadCount; //Set the m_dataCount to the number actually read. 116 } 117 else 118 { 119 for (int i = 0; i < m_dataCount; i++) { 120 SPI.transfer(m_pData[i]); delayMicroseconds(transferDelay); 121 } 122 } 123 return true; 124} 125//TO BE USED BY PERIPHERAL IN INTERRUPT HANDLING 126uint16_t CBT_SPIMessage::readMosi(uint16_t byteCount, uint8_t data, uint16_t mosiDataReadCount) { 127 static uint8_t bl, bh; 128 //First the data, avoids the switch in majority of transactions 129 //Note that data-writing stops at m_dataCount. In that way, any extra zero's from the controller are ignored (they happen when miso is bigger than mosi) 130 //Note that no data will be written beyond the array (third check) 131 if(byteCount >= m_headerSize & byteCount < m_dataCount + m_headerSize & byteCount < m_dataSize + m_headerSize) { 132 //Serial.print("byteCount: "); Serial.print(byteCount); Serial.print(" data: "); Serial.println(data); 133 m_pData[byteCount - m_headerSize] = data; 134 mosiDataReadCount++; 135 } 136 else { 137 switch(byteCount) { 138 case 0: bh = data; break; 139 case 1: bl = data; m_ID = bh * 256 + bl; break; 140 case 2: bh = data; break; 141 case 3: bl = data; m_type = bh * 256 + bl; break; 142 case 4: bh = data; break; 143 case 5: bl = data; m_refID = bh * 256 + bl; break; 144 case 6: bh = data; break; 145 case 7: bl = data; m_dataCount = bh * 256 + bl; break; 146 } 147 } 148 return mosiDataReadCount; 149} 150uint8_t CBT_SPIMessage::writeMiso(uint16_t byteCount) const { 151 uint8_t misoData; 152 //First deal with the data, that avoids the switch for data -> more efficient for larger data 153 //Note that, if dataCount > dataSize, writing will be limited to dataSize 154 if(byteCount >= m_headerSize & byteCount < m_headerSize + m_dataCount & byteCount < m_headerSize + m_dataSize) { 155 misoData = m_pData[byteCount - m_headerSize]; 156 } 157 else { 158 switch(byteCount) { 159 case 0: misoData = (m_ID >> 8 & 0xff); break; 160 case 1: misoData = m_ID & 0xff; break; 161 case 2: misoData = (m_type >> 8 & 0xff); break; 162 case 3: misoData = m_type & 0xff; break; 163 case 4: misoData = (m_refID >> 8 & 0xff); break; 164 case 5: misoData = m_refID & 0xff; break; 165 case 6: misoData = (m_dataCount >> 8 & 0xff); break; 166 case 7: misoData = m_dataCount & 0xff; break; 167 } 168 } 169 return misoData; 170}
Controller class header file (C++)
To be included in Arduino libraries directory
1/* 2Class CBT_SPIController is the intermediary between the CBT_SPIMessage class and Arduino's standard SPI libray. 3It facilitates SPI communication - exchanging messages with a header and a data array - with a peripheral, provided the peripheral runs the matching code. 4The key function is doTransaction(). It will execute the sending of the mosi message and reception of the miso message. 5The actual parsing of data happens in the read / write functions of the CBT_SPIMessage class. 6In this way, the CBT_Message class can be compatible with other SPI peripheral systems - just use or write a different peripheral class. 7 8NOTE: in the approach here - unlike in basic SPI - MISO can be longer than MOSI. 9 10NOTE: the user should take care to give the peripheral enough time to digest a transfer (i.e. one byte), and to digges a transaction (i.e. the message) before 11sending a subsequent transfer or executing a subsequent transaction. This is especially releant if Serial prints are executed during transfers on the peripheral side, 12such as may be the case during debugging. As soon as these times are set too short, you will see data corruption - but not necessarily systematic. 13 The member m_transferDelay (which is user-settable and will not be smaller than m_minTransferDelay) is the delay between transfers in microseconds. 14I have found 5mms to be a good value in transfers between an Arduino DUE and an Arduino MKR 1010 WiFi. But different boards 15may require this setting to be changed - if it is too short you will see data corruption. 16 It is up to the user to ensure delays between transactions are sufficient. There are two main options: 171 - simply use delay() 182 - use a wire to provide a "busy / ready" signal from the peripheral to the controller. The controler should check or poll this pin before transactions. 19The peripheral should set "busy" immediately inside its SPI handler, and reset to "ready" once the message has been digested. 20 21In order to synchronise the mosi and miso arays - i.e. miso[0] is received in the transfer of mosi[0] - we transfer a small number - typ. 4 - leading bytes. 22This number, m_leadingByteCount, is transferred to the peripheral in the very first transfer of the transaction. This allows the 23peripheral (which has its own lag between miso and miso) to calculate at which transfer to start sending miso data. 24 25In the function doTransaction we set the SPI frequency and mode. At 1Mhz, I don't see data corruption in my tests. At 8Mhz, I do. 26 27Ben Tubbing, December 2024 28*/ 29#ifndef CBT_SPICONTROLLER_H 30#define CBT_SPICONTROLLER_H 31#include <arduino.h> 32#include <SPI.h> 33#include <CBT_SPIMessage.h> 34 35class CBT_SPIController { 36private: 37 uint32_t m_clockSpeed = 1000000; //SPI clock speed, initialised at default value 38 CBT_SPIMessage* m_pSPIMosiMessage; //Pointer to the message object to be written 39 CBT_SPIMessage* m_pSPIMisoMessage; //Pointer to the message object that should receive the MISO 40 uint16_t m_pinSS; //The Slave Select pin 41 const uint8_t m_leadingByteCount = 4; //The number of bytes transferred before starting transfer of the message 42 const uint16_t m_minTransferDelay = 5; //Minimum value of the delay time between transfers 43 uint16_t m_transferDelay = m_minTransferDelay; //Delay time between transfers in microseconds - lower-limited by m_minTransferDelay 44public: 45 CBT_SPIController(uint16_t pinSS); //Constructor 46 bool setClockSpeed(uint32_t clockSpeed); //Optionally sets the clock speed. A default value is coded in the .h file 47 bool setTransferDelay(uint16_t transferDelay); //sets the transfer delay, but it is lower limited by m_minTransferDelay 48 uint16_t getTransferDelay(); //Returns the transfer delay 49 uint16_t getMinimumTransferDelay(); //Returns the minimum transfer delay 50 uint32_t getClockSpeed(); //Returns the current clock speed 51 CBT_SPIMessage* getMosi(); //Returns the pointer to the mosi message object 52 bool setMosi(CBT_SPIMessage* pSPIMosiMessage); //Sets the pointer to the mosi message object 53 CBT_SPIMessage* getMiso(); //Returns the pointer to the miso message object 54 bool setMiso(CBT_SPIMessage* pSPIMisoMessage); //Sets the pointer to the miso message object 55 bool doTransaction(); //Execute a transaction 56 bool doTransaction(CBT_SPIMessage* pSPIMosiMsg, CBT_SPIMessage* pSPIMisoMsg); //Executes a transactions, bypassing setMosi and setMiso 57private: 58 bool sendAndReceive(); //Carries out the transfers 59}; 60 61 62 63 64 65#endif //CBT_SPICONTROLLER_H
Controller class implementation file (C++)
To be included in Arduino libraries directory
1/* 2Implementation of the CBT_SPIController class 3*/ 4#include "CBT_SPIController.h" 5CBT_SPIController::CBT_SPIController(uint16_t pinSS) { 6 m_pinSS = pinSS; 7} 8bool CBT_SPIController::setTransferDelay(uint16_t transferDelay) { 9 m_transferDelay = (transferDelay < m_minTransferDelay)? m_minTransferDelay: transferDelay; 10 return true; 11} 12bool CBT_SPIController::setClockSpeed(uint32_t clockSpeed) { 13 m_clockSpeed = clockSpeed; 14 return true; 15} 16uint16_t CBT_SPIController::getTransferDelay() { 17 return m_transferDelay; 18} 19uint16_t CBT_SPIController::getMinimumTransferDelay() { 20 return m_minTransferDelay; 21} 22uint32_t CBT_SPIController::getClockSpeed() { 23 return m_clockSpeed; 24} 25bool CBT_SPIController::doTransaction() { 26 digitalWrite(m_pinSS, LOW); 27 delayMicroseconds(10); //Some delay (needed?) to give the peripheral time to handle the SS low interrupt 28 SPI.beginTransaction(SPISettings(m_clockSpeed, MSBFIRST, SPI_MODE0)); 29 delayMicroseconds(10); //Delay between the start of transaction and the first transfers. It gives peripheral time needed to set the ready pin to low 30 sendAndReceive(); 31 SPI.endTransaction(); 32 digitalWrite(m_pinSS, HIGH); 33 return true; 34} 35bool CBT_SPIController::doTransaction(CBT_SPIMessage* pSPIMosiMsg, CBT_SPIMessage* pSPIMisoMsg) { 36 m_pSPIMosiMessage = pSPIMosiMsg; 37 m_pSPIMisoMessage = pSPIMisoMsg; 38 m_pSPIMisoMessage->m_ID = 0; //Reset the miso, as it is the receiving message on the controller 39 m_pSPIMisoMessage->m_type = 0; 40 m_pSPIMisoMessage->m_refID = 0; 41 m_pSPIMisoMessage->m_dataCount = 0; 42 doTransaction(); 43 return true; 44} 45CBT_SPIMessage* CBT_SPIController::getMosi() { 46 return m_pSPIMosiMessage; 47} 48bool CBT_SPIController::setMosi(CBT_SPIMessage* pSPIMosiMessage) { 49 m_pSPIMosiMessage = pSPIMosiMessage; 50 return true; 51} 52CBT_SPIMessage* CBT_SPIController::getMiso() { 53 return m_pSPIMisoMessage; 54} 55bool CBT_SPIController::setMiso(CBT_SPIMessage* pSPIMisoMessage) { 56 m_pSPIMisoMessage = pSPIMisoMessage; 57 return true; 58} 59bool CBT_SPIController::sendAndReceive() { 60 uint8_t bh, bl; 61 //LEADING BYTES 62 SPI.transfer(m_leadingByteCount); delayMicroseconds(m_transferDelay); 63 SPI.transfer(0); delayMicroseconds(m_transferDelay); 64 SPI.transfer(0); delayMicroseconds(m_transferDelay); 65 SPI.transfer(0); delayMicroseconds(m_transferDelay); 66 m_pSPIMosiMessage->sendMosiReadMiso(m_pSPIMisoMessage, m_transferDelay); 67 return true; 68}
Peripheral class implementation file
To be included in Arduino libraries directory
1/* 2Implementation of the CBT_Sercom3Per class 3*/ 4#include "CBT_Sercom3Per.h" 5bool CBT_Sercom3Per::begin(uint16_t readyPin) { 6 m_readyPin = readyPin; 7 SPISlave.SercomInit(SPISlave.MOSI_Pins::PA16, SPISlave.SCK_Pins::PA17, SPISlave.SS_Pins::PA20, SPISlave.MISO_Pins::PA19); 8 //BT modifications to init 9 SERCOM3->SPI.CTRLA.bit.ENABLE = 0; 10 while(SERCOM3->SPI.SYNCBUSY.bit.ENABLE); //wait 11 SERCOM3->SPI.CTRLA.bit.DORD = 0; 12 SERCOM3->SPI.CTRLA.bit.CPHA = 1; //Mystery? this is supposed to be 0 in mode0 13 SERCOM3->SPI.CTRLA.bit.CPOL = 1; //Mystery? this is supposed to be 0 in mode0 14 SERCOM3->SPI.CTRLA.bit.ENABLE = 1; 15 while(SERCOM3->SPI.SYNCBUSY.bit.ENABLE); //wait 16 //SERCOM3->SPI.CTRLB.bit.PLOADEN = 1; //I never managed to get preload working. We don't need it. 17 while(SERCOM3->SPI.SYNCBUSY.bit.ENABLE); //wait 18 return true; 19} 20bool CBT_Sercom3Per::begin() { 21 return begin(0); 22} 23 24bool CBT_Sercom3Per::inSercom3Handler() { 25 char strBuf[100]; 26 static uint16_t readCount; //Counts the bytes (transfers) 27 static uint16_t mosiDataReadCount; //Passed to & returned by the readMosi function. It is incremented each time a data byte is passed to the array. Then writes to message's m_dataCount 28 uint8_t data; //The byte data 29 //READ THE INTERRUPT FLAG. Note that normal read interrupts raise both DRC and DRE flags 30 uint8_t interrupts = SERCOM3->SPI.INTFLAG.reg; // Read SPI interrupt register 31 //** 32 //HANDLE SLAVE SELECT LOW INTERRUPT FLAG - it is raised at a new transaction 33 //If a "ready / busy" pin has been specified, we set it low, meaning "busy" 34 if (interrupts & (1 << 3)) // 1000 = bit 3 = SSL // page 503 35 { 36 SERCOM3->SPI.INTFLAG.bit.SSL = 1; // Clear Slave Select Low interrupt 37 if(m_readyPin != 0) digitalWrite(m_readyPin, LOW); 38 readCount = 0; 39 mosiDataReadCount = 0; 40 } 41 //HANDLE DATA RECEIVED COMPLETE AND DATA REGISTER EMPTY INTERRUPT FLAGS 42 //Data Received Complete interrupt and Data Register Empty interrupt flags 43 //In normal transfers we receive both DRC and DRE. Then we set miso for the next transfer, and we read mosi data 44 //In a less usual case we receive only DRE. In that case, we must supply data to avoid endless DRE loop 45 if (interrupts & (1 << 2)) // 0100 = bit 2 = RXC // page 503 (RXC = DRC) 46 { 47 //Data Register Empty interrupt: the normal case of DRC and DRE flags 48 //Miso data is written to data register. 49 // - For the first few bytes, until leadingByteCount - misoLagByteCount, write 0 to the register 50 // - Subsequently write data from the misoArray, up to misoByteCount 51 // - After these misoByteCount misoArray values has been sent, set any further register data to 0 52 uint8_t misoData; 53 if (interrupts & (1 << 0)) // 0001 = bit 0 = DRE // page 503 54 { 55 misoData = 0; //Ensure that the register is always written, otherwise the loop will repeat 56 uint16_t nStart = m_leadingSize - m_misoLagByteCount; //This is the readCount at which the first miso byte should be written. LeadingSize is obtained from the first MOSI byte 57 uint16_t byteCount = readCount - nStart; //The byte of the message 58 misoData = m_pSPIMisoMessage->writeMiso(byteCount); //The miso data to be written, obtained from the CBT_SPIMessage object 59 SERCOM3->SPI.DATA.reg = misoData; //And write to register 60 } 61 //Read incoming data and, once beyond the leading bytes, write to mosiArray 62 data = (uint8_t) SERCOM3->SPI.DATA.reg; // Read data register 63 //Discard the leading bytes and write user mosi bytes to mosiArray 64 bool bOK; 65 if(readCount == 0) { 66 m_leadingSize = data; //The first byte gives us the number of leading bytes, prior to the message 67 } 68 if(readCount >= m_leadingSize) { 69 mosiDataReadCount = m_pSPIMosiMessage->readMosi(readCount - m_leadingSize, data, mosiDataReadCount); //The CBT_SPIMessage function stores the data and returns is the current data read count 70 } 71 readCount++; 72 SERCOM3->SPI.INTFLAG.bit.RXC = 1; // Clear Receive Complete interrupt 73 } 74 else { //HANDLE A SINGLE DRE INTERRUPT 75 if (interrupts & (1 << 0)) // 0001 = bit 0 = DRE // page 503 76 { 77 SERCOM3->SPI.DATA.reg = 255; //Traceable value, should not normally occur 78 SERCOM3->SPI.INTFLAG.bit.DRE = 1; // Clear Data Register Empty interrupt 79 } 80 } 81 82 //HANDLE DATA TRANSMIT COMPLETE INTERRUPT: end of the transaction 83 if (interrupts & (1 << 1)) // 0010 = bit 1 = TXC // page 503 84 { 85 SERCOM3->SPI.INTFLAG.bit.TXC = 1; // Clear Transmit Complete interrupt 86 return true; 87 } 88 return false; 89} 90bool CBT_Sercom3Per::setMosi(CBT_SPIMessage* pSPIMosiMessage) { 91 m_pSPIMosiMessage = pSPIMosiMessage; 92 m_pSPIMosiMessage->m_ID = 0; 93 m_pSPIMosiMessage->m_type = 0; 94 m_pSPIMosiMessage->m_refID = 0; 95 m_pSPIMosiMessage->m_dataCount = 0; 96 return true; 97} 98CBT_SPIMessage* CBT_Sercom3Per::getMosi() { 99 return m_pSPIMosiMessage; 100} 101bool CBT_Sercom3Per::setMiso(CBT_SPIMessage* pSPIMisoMessage) { 102 m_pSPIMisoMessage = pSPIMisoMessage; 103 return true; 104} 105CBT_SPIMessage* CBT_Sercom3Per::getMiso() { 106 return m_pSPIMisoMessage; 107} 108bool CBT_Sercom3Per::setPostTransactionDelay(uint32_t delay) { 109 m_postTransactionDelay = delay; 110 return true; 111} 112uint32_t CBT_Sercom3Per::getPostTransactionDelay() { 113 return m_postTransactionDelay; 114}
Peripheral class header file
To be included in Arduino libraries directory
1/* 2Class CBT_Sercom3Per is an intermediary between the CBT_SPIMessage class and Arduino's SPISlave library. 3It facilitates SERCOM SPI communication - exchanging messages with a header and a data array - with Arduino MKR 1010 WiFi as peripheral. 4The key function is inSercom3Handler. This function needs to be called from within SERCOM3_Handler of SPISlave. 5It will then orchestrate the reception of MOSI data and its storage in the MOSI message object, and the 6sending of MISO data from the MISO message object. 7The actual parsing of data happens in the read / write functions of the CBT_SPIMessage class. 8In this way, the CBT_Message class can be compatible with other SPI peripheral systems - just write a different peripheral class. 9 10NOTE: in the approach here - unlike in basic SPI - MISO can be longer than MOSI. 11 12In SPI, if the peripheral sets a given MISO value at transfer n, then the controller receives it with a lag of 1 or 2 transfers, i.e. transfer n+1 or n+2. In the MKR case, apparently n+2. 13This class, with the matching CBT_SPIController class, compensate for this "misoLag" by the controller sending a couple of leading bytes. This allows the peripheral to start entering MISO data 14a few transfers (the lag number) before the controller starts sending actual mosi data. The consequence is that the transfers of mosi and miso are synchronised, i.e. miso[0] and mosi[0] are exchanged 15in the same transfer. 16 17The class provides for an optional "ready/busy" signal on the pin m_readyPin, optionally passed in the begin(readyPin) function. If passed, 18that pin will be set low by inSercom3Handler() at the beginning of a transaction, i.e. at the SlaveSelect Low interrupt. It is then up to the user to reset the 19pin to high after the completion of the transaction. Note that the same pin can be used to block the controller from sending SPI at any other time, up to the user. 20 21 22Ben Tubbing, December 2024 23*/ 24#ifndef CBT_SERCOM3PER_H 25#define CBT_SERCOM3PER_H 26//INCLUDES 27#include <SercomSPISlave.h> 28#include <CBT_SPIMessage.h> 29//CLASS 30class CBT_Sercom3Per { 31private: 32 CBT_SPIMessage* m_pSPIMosiMessage; //Pointer to the message object to be (over)written by the MOSI 33 CBT_SPIMessage* m_pSPIMisoMessage; //Pointer to the message object to be written to MISO 34 Sercom3SPISlave SPISlave; //The SPISlave object 35 uint16_t m_misoLagByteCount = 2; //The bytes-delay between a peripheral MISO write and the subsequent controller MISO read of a given byte. 2 for the Sercom3 in this implementation 36 uint8_t m_leadingSize; //The number of bytes the controller writes before starting the message proper. This allows to compensate for the misoLagByteCount and sync the miso with mosi messages 37 uint16_t m_readyPin = 0; //The pin number of the "ready / busy" signal output 38 uint32_t m_postTransactionDelay = 0; //A NON-BLOCKING time-lag, in mms, after the end of transaction to setting the "readyPIN" pin high 39 40public: 41 bool begin(); 42 bool begin(uint16_t readyPin); //Default initialisation for Arduino MKR1010 WiFi, with default pins for MOSI, SCK and MISO, and pin 6 (=PA20) for SS 43 bool inSercom3Handler(); //Function to be called from within Sercom3_Handler 44 bool setMosi(CBT_SPIMessage* pSPIMosiMessage); //Sets the MOSI message pointer and clears its header - because for the peripheral it is the receiving message 45 CBT_SPIMessage* getMosi(); //Gets the MOSI message pointer 46 bool setMiso(CBT_SPIMessage* pSPIMisoMessage); //Sets the MISO message pointer 47 CBT_SPIMessage* getMiso(); //Gets the MISO message pointer 48 bool setPostTransactionDelay(uint32_t delay); //Sets an optional delay (microseconds) between end of transaction and setting the "ready" pin high 49 uint32_t getPostTransactionDelay(); //Returns the post transaction delay 50}; 51#endif //CBT_SERCOM3PER_H
Documentation
Connection diagram
Essential connections for these demos
Diagram.pdf
Comments
Only logged in users can leave comments