i2c slave controller for multiple slave arduino/teensies

Status
Not open for further replies.

tonton81

Well-known member
SPI & I2C slave controller code for multiple slave arduino/teensies

little project im working on that im building up if anyone is interested, its a library for the master to control a slave arduino(or teensy), the slave doesnt run any libraries, only a sketch with defines and switch statements. the first video demonstrates 2 lcds running off a mega slave at 200,000baud, and while hotplugging the displays, the host library is able to resync fine over the i2c bus.

the second video demonstrates, in addition to the 2 running lcds in the first video, ive attached an mcp23s17 SPI expander on the slave and the master was able to poll its register bank over the i2c bus

hooking up 2 teensy 3.5/3.6 should gain you 12 hardware uarts accessible on a single mcu

analogread (uint16_t) and analogwrite (uint16_t) support is included, as well as pinMode, digitialWrite, and digitalRead

note: i could have made an SPI mcu controller as well, but attaching 2 or more slaves would lead to a wiring mess.
note: now its possible to have additional SPI busses, running over i2c. Should we call it 2-wire spi? ;)


200k baud uart over i2c

spi over i2c
 
Last edited:
here we initialize the objects we will be using: (interface, slave address, wirePort (0,1,2?))
Code:
I2CmcuController Serial50 = I2CmcuController("Serial", 9, &Wire);
I2CmcuController Serial60 = I2CmcuController("Serial1", 9, &Wire);
I2CmcuController Serial61 = I2CmcuController("Serial2", 9, &Wire);
I2CmcuController Serial62 = I2CmcuController("Serial3", 9, &Wire);
I2CmcuController gpio     = I2CmcuController("GPIO",    9, &Wire);
I2CmcuController SPI66    = I2CmcuController("SPI",    9, &Wire);

serial baudrates:
Code:
  Serial60.begin(200000);
  Serial62.begin(200000);

gpio controls:
Code:
  gpio.pinMode(13, OUTPUT);
  gpio.digitalWrite(53, HIGH);
  gpio.digitalWrite(13, !gpio.digitalRead(13)); // toggle led
  Serial.println(gpio.analogRead(A0)); // read analog port

spi:
Code:
  gpio.digitalWrite(53, HIGH);
  SPI66.begin();
  SPI66.beginTransaction();
  gpio.digitalWrite(53, LOW);
  SPI66.transfer(0x40); // Write command to init all chips.
  SPI66.transfer(0x0A); // Write IOCONA register
  SPI66.transfer(0x18); // DISSLW & HAEN on, other bits off.
  gpio.digitalWrite(53, HIGH);
  SPI66.endTransaction();
  SPI66.beginTransaction();
  gpio.digitalWrite(53, LOW);
  SPI66.transfer(0x4E); // Write command to init all chips.
  SPI66.transfer(0x0A); // Write IOCONA register
  SPI66.transfer(0x18); // DISSLW & HAEN on, other bits off.
  gpio.digitalWrite(53, HIGH);
  SPI66.endTransaction();

i couldnt figure out why spisettings was freezing when dynamically being set, so they are statically defined in the slave sketch, and the command falls onto that when beginTransaction() is called.
 
has anyone tested Wire as slave on teensy?

test run of 2560 master to 2560 slave was a success of gpios, uarts, and spi, however, teensy as an i2c slave tends to crash for some reason, so ill be debugging to see whats going on to see where the data is getting lost or whatever, but rebooting the master seems to cure the issue until a few secs/mins later where the i2c halts again. this type of behaviour is seen if there is a byte missing or extra appearing on the i2c bus, have not had any issues with the 2 megas running for about a week now, its only now i tried the wire slave sketch on teensy and testing only the uart data, so only uart and wire are used, tomorrow will be another day for tests :)
 
just a note:

mega2560 master to mega2560 slave was ok
mega2560 master to teensy 3.5 slave got very intermittant i2c lockups

i dropped 120mhz for the slave down to 24MHz and so far it seems to be running non stop! ill keep it running till tomorrow now :)

does this mean i2c is more stable at slower cpu speeds?
 
Last edited:
Here it is:

mega2560 master controlling teensy 3.5 slave set at 24MHz
2 lcds running at 625,000 serial baud rates over the i2c bus

master contains lcd library (unmodified) and slave control library
slave contains only a sketch .

https://www.youtube.com/watch?v=DR5b2hqNYEs


toggling led at same time over i2c as well:
Code:
  if ( millis() - tyme > 800 ) {
    tyme = millis();
    gpio.digitalWrite(13, !gpio.digitalRead(13)); // toggle led
  }

https://www.youtube.com/watch?v=8My-nLKtOI8
 
Last edited:
I've tested I2C slave mode many times, but mostly just simple tests using the examples that come with the Wire library.

Do you want me to attempt to investigate this issue?
 
yeah ill send you it later, i havnt posted it yet because i never made a release before so i pretty much have no headers or readme, if someone here wants to help out with that stuff im sure we can contribute this to the commuunity
 
also im also working on the spi version but having troubles to get t3.5 to behave as an spi slave, once i figure it out i will work on mcu controller code for spi bus, but the i2c is pretty stable, especially hammering 2 lcds at 625k baud (at 24mhz)

i never tried other cpu speeds because im working on the spi code atm
 
View attachment mcuUart.zip

Here is the I2C controller WIP

the gpios, and begin functions run through crc checks to make sure there will be no corruption (if ever) and no, theres been none at all so far (knock on wood) :)

uarts, and spi busses dont do crc for streaming
im sure there is great potential for this type of code, having extra spi busses and uarts from a spare mcu accessible within the same local sketch, it's alot cheaper to add a teensy than to buy an i2cuart adaptor with flaky library code, with benefits of several integrated hardware features... i plan to extend it with additional features if the need arises, but, just think of the gpio accessibility of it. it's alot easier to control single, or many t3.5/3.6 over i2c with 60 gpios than it is to run 4 mcp23017's to gain 64 gpios, in terms of accessibility, and small footprint, 2 teensies can easily replace an 8 chip bank of mcp23017 port expanders for about 120gpio access using a single host, a single sketch, a small footprint, a single class per teensy slave, with many additional hardware benefits included. Just try to control 8 different types of mcp23017 using adafruit's library, you will see how simple my code here will stand out regarding that:
gpio <-- was the initializer, but you know as well as I that you can call it whatever you want ;)
expander1? ok..
expander1.pinMode(13,OUTPUT);
expander1.digitalRead(13);
expander1.digitalWrite(13,HIGH);
expander1.analogRead(A0);
expander1.analogWrite(A0, 255);
i try to make it simple ;P

Also paul, the test with the i2c issue on 3.5 at 120mhz with the i2c bus locking up was tested on latest teensyduino using Faster, constantly read/writing to the uart channel, which never happened on the mega
my initial attempts were to delay some of the transactions but it didnt work, my 2nd test was to drop to 24mhz cpu and thats when it was stable, i presume this is an issue with code being slave needs to be clocked lower, not sure, im not an expert on that.

btw...

The file here is the library, this is run on the master, be it arduino or teensy, both use Wire.h (i didnt use i2c_t3 because it doesnt support Wire onreceive etc for slave support???)

The slave sketch is posted in CODE brackets, the initializers can be set using the posts above where i given examples
Like i said, i dont have time to do readmes, headers, or prepare example files, or rename the files themself (mcuUart.h) it should have a new name since initially i started it as a remote uart controller cuz i needed additional uarts, also a benefit is the high speed uart access teensy->teensy, FIFO, and huge buffers over i2c that you cannot obtain using other expensive commercial single hardware interface use chips. I mean really, who does or has a chip in the market capable of 625K baud serial device transfers over the i2c bus?? lol :) I've also looked around, I dont even think i2c to SPI bus chips exist, but here we have a 2-wire SPI bus.
Benefits?? Sure...

Lets say you have an SPI device and you dont have additional busses for it, or no available gpios for CS, OR, perhaps you plan to install the device further away..... and want to use less wiring back to your host...
this is ideal :)

Enjoy it guys! Leave feedback if anything is worthwhile adding for support, ill mark it in my todo list
Tony

Code:
#include <Wire.h>
#include <SPI.h>

volatile uint8_t PORT;
volatile uint8_t PIN;
volatile uint8_t bytes[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
volatile uint32_t spi_speed = 4000000;
volatile uint8_t spi_byte;
uint8_t checksum;

void setup() {
  Wire.begin(1);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
  SPI.begin();
  Serial.begin(115200);
}

void loop() {
}

void receiveEvent(int howMany) {
  bytes[0] = Wire.read();
  switch ( bytes[0] ) {
    case 0: { // COMMAND BYTE
        if (Wire.available() != 11) { // wrong length of bytes for CMD/CRC, FLUSH and EXIT!
          while ( Wire.available() > 0 ) Wire.read();
          return;
        }
        checksum = 0x00;
        for ( uint8_t i = 1; i <= 11; i++ ) bytes[i] = Wire.read();
        for ( uint8_t i; i <= 10; i++ ) checksum ^= bytes[i];
        Serial.print(bytes[0]); Serial.print(" : "); Serial.print(bytes[1]); Serial.print(" : "); Serial.print(bytes[2]); Serial.print(" : "); Serial.print(bytes[3]); Serial.print(" : "); Serial.print(bytes[4]); Serial.print(" : "); Serial.print(bytes[5]); Serial.print(" : "); Serial.print(bytes[6]); Serial.print(" : "); Serial.print(bytes[7]); Serial.print(" : "); Serial.print(bytes[8]); Serial.print(" : "); Serial.print(bytes[9]); Serial.print(" : "); Serial.print(bytes[10]); Serial.print(" : "); Serial.println(bytes[11]);
        if ( bytes[11] != checksum ) break; // bad checksum exit
        switch ( bytes[1] ) { // FUNCTION
          case 1: { // SERIAL PORT AND BAUD RATE
              uint8_t baudrate_split[4] = {bytes[3], bytes[4], bytes[5], bytes[6]};
              uint32_t baudrate = *(uint32_t*)&baudrate_split;
              switch ( bytes[2] ) {
                case 0: {
                    Serial.begin(baudrate); break;
                  }
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
                case 1: {
                    Serial1.begin(baudrate); break;
                  }
                case 2: {
                    Serial2.begin(baudrate); break;
                  }
                case 3: {
                    Serial3.begin(baudrate); break;
                  }
#endif
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
                case 4: {
                    Serial4.begin(baudrate); break;
                  }
                case 5: {
                    Serial5.begin(baudrate); break;
                  }
                case 6: {
                    Serial6.begin(baudrate); break;
                  }
#endif
              }
              break;
            }

          case 2: { // PINMODE
              pinMode(bytes[2], bytes[3]); break;
            }

          case 3: { // DIGITALWRITE
              digitalWrite(bytes[2], bytes[3]); break;
            }

          case 4: { // SPI begin()
              switch ( bytes[2] ) { // PORT
                case 0: { // SPI
                    SPI.begin(); break;
                  }
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
                case 1: { // SPI
                    SPI1.begin(); break;
                  }
                case 2: { // SPI
                    SPI2.begin(); break;
                  }
#endif
              }
              break;
            }

          case 5: { // SPI beginTransaction
              uint8_t speed_split[4] = {bytes[3], bytes[4], bytes[5], bytes[6]}, msblsb = bytes[7], mode = bytes[8], port = bytes[2];
              spi_speed = *(uint32_t*)&speed_split;
              switch ( port ) { // PORT
                case 0: { // SPI
                    SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); break;
                  }
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
                case 1: { // SPI1
                    SPI1.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); break;
                  }
                case 2: { // SPI2
                    SPI2.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); break;
                  }
#endif
              }
              break;
            }
        }
        break;
      }

    case 1: { // SEND A SERIAL BYTE
        if (Wire.available() != 2) {
          // wrong length of bytes for serial, FLUSH and EXIT!
          while ( Wire.available() > 0 ) Wire.read();
          return;
        }
        for ( uint8_t i = 1; i <= 2; i++ ) bytes[i] = Wire.read();
        switch ( bytes[1] ) {
          case 0: {
              Serial.write(bytes[2]); break;
            }
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 1: {
              Serial1.write(bytes[2]); break;
            }
          case 2: {
              Serial2.write(bytes[2]); break;
            }
          case 3: {
              Serial3.write(bytes[2]); break;
            }
#endif
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 4: {
              Serial4.write(bytes[2]); break;
            }
          case 5: {
              Serial5.write(bytes[2]); break;
            }
          case 6: {
              Serial6.write(bytes[2]); break;
            }
#endif
        }
        break;
      }

    case 2: { // READ A SERIAL BYTE
        if (Wire.available() != 1) {
          // wrong length of bytes for serial read, FLUSH and EXIT!
          while ( Wire.available() > 0 ) Wire.read();
          return;
        }
        PORT = Wire.read(); break;
      }

    case 3: { // AVAILABLE SERIAL BYTES
        if (Wire.available() != 1) {
          // wrong length of bytes for serial available, FLUSH and EXIT!
          while ( Wire.available() > 0 ) Wire.read();
          return;
        }
        PORT = Wire.read(); break;
      }

    case 4: { // PEEK SERIAL BYTE
        if (Wire.available() != 1) {
          // wrong length of bytes for serial peek, FLUSH and EXIT!
          while ( Wire.available() > 0 ) Wire.read();
          return;
        }
        PORT = Wire.read(); break;
      }

    case 5: { // digitalRead pin
        if (Wire.available() != 1) {
          // wrong length of bytes for digitalRead, FLUSH and EXIT!
          while ( Wire.available() > 0 ) Wire.read();
          return;
        }
        PIN = Wire.read(); break;
      }

    case 6: { // analogRead pin
        if (Wire.available() != 1) {
          // wrong length of bytes for analogRead, FLUSH and EXIT!
          while ( Wire.available() > 0 ) Wire.read();
          return;
        }
        PIN = Wire.read(); break;
      }

    case 7: { // spi.transfer
        PORT = Wire.read(); uint8_t toSend = Wire.read(); spi_byte = SPI.transfer(toSend); break;
      }

    case 8: { // spi.endTransaction
        PORT = Wire.read();
        switch ( PORT ) {
          case 0: {
              SPI.endTransaction(); break;
            }
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 1: {
              SPI1.endTransaction(); break;
            }
          case 2: {
              SPI2.endTransaction(); break;
            }
#endif
        }
        break;
      }


    case 9: { // analogWrite
        PIN = Wire.read();
        uint8_t value_split[2] = {(uint8_t)Wire.read(), (uint8_t)Wire.read()};
        uint16_t value = *(uint16_t*)&value_split;
        analogWrite(PIN, value);
        break;
      }

      //    case 10: { // analogWriteResolution
      //        PIN = Wire.read();
      //        uint8_t res = Wire.read();
      //        analogWriteResolution(res);
      //        break;
      //      }



  }
}

void requestEvent() {

  switch ( bytes[0] ) { // COMMAND BYTE
    case 0x02: { // break; read byte from serial
        switch ( PORT ) {
          case 0: {
              Wire.write(Serial.read()); break;
            }
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 1: {
              Wire.write(Serial1.read()); break;
            }
          case 2: {
              Wire.write(Serial2.read()); break;
            }
          case 3: {
              Wire.write(Serial3.read()); break;
            }
#endif
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 4: {
              Wire.write(Serial4.read()); break;
            }
          case 5: {
              Wire.write(Serial5.read()); break;
            }
          case 6: {
              Wire.write(Serial6.read()); break;
            }
#endif
        }
        break;
      }



    case 0x03: { // break; available bytes on serial port
        switch ( PORT ) {
          case 0: {
              Wire.write(Serial.available()); break;
            }
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 1: {
              Wire.write(Serial1.available()); break;
            }
          case 2: {
              Wire.write(Serial2.available()); break;
            }
          case 3: {
              Wire.write(Serial3.available()); break;
            }
#endif
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 4: {
              Wire.write(Serial4.available()); break;
            }
          case 5: {
              Wire.write(Serial5.available()); break;
            }
          case 6: {
              Wire.write(Serial6.available()); break;
            }
#endif
        }
        break;
      }



    case 0x04: { // break; peek byte on serial port
        switch ( PORT ) {
          case 0: {
              Wire.write(Serial.peek()); break;
            }
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 1: {
              Wire.write(Serial1.peek()); break;
            }
          case 2: {
              Wire.write(Serial2.peek()); break;
            }
          case 3: {
              Wire.write(Serial3.peek()); break;
            }
#endif
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
          case 4: {
              Wire.write(Serial4.peek()); break;
            }
          case 5: {
              Wire.write(Serial5.peek()); break;
            }
          case 6: {
              Wire.write(Serial6.peek()); break;
            }
#endif
        }
        break;
      }



    case 0x05: { // break; digital pin status
        Wire.write(digitalRead(PIN)); break;
      }



    case 0x06: { // break; analog pin value
        uint16_t value = analogRead(PIN);
        Wire.write((uint8_t)(value >> 8));
        Wire.write((uint8_t)value);
        break;
      }



    case 0x07: { // spi byte return
        Wire.write(spi_byte); break;
      }




  }

}
 
Last edited:
it wouldnt compile when i replaced the headers, dont know why, hopefully it wasnt a mistake on my part, but if it was, sorry ;P
 
good news guys, and perhaps i need a suggestion

good news! spi slave is working on teensy 3.5 using a single 15 line function in the slave code!
i need a quick suggestion before i start adapting an SPI version of this code for improved high speed functions
right now ill put teensy spi slave support, avr slave support i will add later, they will be switched automatically with the defines

before i start i need to know whats best for the transfers
should i keep the communication to teensy's native 16bit transfer mode or drop it to usual 8bit transfers (for compatibility, if any?) i know some of you might find this question silly, but it's not to me :p

whats a good decision to make, keep it 8 or 16 for the mcu controller code?
SPI.transfer16(0xFFFF); or SPI.transfer(0xFF);

this i have to know before i start writing the packets
also, another suggestion, should i (actually i should but i should ask anyways) CRC every interaction with the other controller with verification on success, and if fail, (for uart/i2c/spi/digital/analog etc data) to automatically resend 1-3 times before ending the function? or just stream the data but continue to CRC the main controls?

btw, im transferring data at 8mhz from mega2560 to teensy using 16bit spi mode for testing, all the frames seem to be intact :)

the mcp23s17 chip can do 10mhz, i wonder how fast teensy-teensy can go for remote GPIO? ;)

since teensy includes digital(x)fast in it's core, that will definately be added to integration

I'd also like to point out for people not familar with mcu-mcu spi traffic (this first time i did this so... )
MISO ON MASTER GOES TO MOSI ON SLAVE
MOSI ON MASTER GOES TO MISO ON SLAVE
 
Last edited:
i was just wondering the complexity of keeping transfers at uint16_t for performance (if there isnt any), for sure it will add complexity to the code, is it worth it? im still playing around with it

so far I have it setup for 16bit transfers ready for command processing (crc) or streaming(non crc, concurrent transfers)

aa7rj8.jpg

6x 16-byte transfers every 500 ms from mega2560 to teensy 3.5


lets not forget the setup of wiring :)

master, left: mega2560
slave, right: teensy 3.5

4hs6jn.jpg
 
Last edited:
working on the SPI slave sketch today, functional commands using 16bit SPI transfers with CRC checking:


Implemented currently:
digitalReadFast
digitalWriteFast
analogReadResolution
analogWriteResolution
analogRead
analogWrite
Serialx.begin
serialx.write
serialx.read
serialx.peek
serialx.available
wirex.read
wirex.write
wirex.begin
wirex.begintransmission
wirex.endtransmission
wirex.requestfrom
supports all remote wire interfaces... 2 T3.5's == 6 I2C busses

the PUSHR register is flushed (set to 0x0000) once the ISR is exited as well, since the stale value exists indefinately, until changed by the code/user
Slave mode function is 11 lines of commands to get up and running, using pin 14 instead of 13 so the led can be used.

Still working on it, but its pretty stable ! ;)
 
Last edited:
well guys heres a sample video of mega master, controlling a teensy spi slave. master running the spi controller library im building, and the lcd library.
the slave teensy is controlling a display on the uart line at 625,000 baud

the stream of the spi bus is setup so you can continuously control objects without cycling the cs line, you can still toggle it if you wish, doing so would allow you to run several devices on the same CS line, or, reasserting the signal will also reset the buffer of the slave. all switches verify for CRC errors as well

https://youtu.be/FOLaDUJmNdw


Code:
spi_controller teensySerial1 = spi_controller("Serial1", 53, &SPI);
spi_controller gpio = spi_controller("GPIO", 53, &SPI);

teensySerial1.begin(625000);
while ( teensySerial1.available() > 0 ) Serial.print((char)teensySerial1.read());
Serial.println((char)teensySerial1.peek());

  gpio.pinMode(13, OUTPUT);
 for ( int i = 0; i < 10; i++) {
    gpio.digitalWrite(13, !gpio.digitalRead(13));
    delay(150);
  }
  gpio.digitalWrite(13, gpio.analogRead(16));
  gpio.analogReadResolution(4);

below is testing 2x uarts read/writing constantly at 625,000 baud rate, (read+write) toggling pin 13 at 10ms, and polling Analog2 (pin16), all over a single SPI connection.
I do, however, get intermittant lockups on the teensy, lowering the SPI speed on mega seems to help, but I will keep monitoring it this week to see whats related
Example of 1 issue, at 2MHz, polling the A2 pin pulled HIGH seems to get mostly 1023, but occassional 0x0001 or 0x0002 returns, lowering to 1MHz fixed this.
Example #2, between 800khz-1mhz, i still see very itntermittant lockups. I dropped it to 500Khz and its been running for awhile now. Whether this is slave related, or code related, not sure, but consistant processing without toggling the CS line seems to be working pretty well with these settings.

this is all also with 16bit SPI transfers, with a buffer max of 6 bytes (uint16_t)

11ln8zb.jpg

assert signaling vs continous test:
https://www.youtube.com/watch?v=cclnPb7Ci1U

its hard to see in a video due to the nature of the led i guess, but visually you can see it toggling faster IRL without reasserting the CS line and running constant packets
 
Last edited:
Should we show this on the blog?

Robin writes the blog posts for projects. She'll need a brief "stand alone" description that explains the value of using this peripheral expansion, ideally readable for beginners who may not necessarily know the background behind the idea.
 
great news paul! i got the timing going! running 8mhz!!!!

i need a little class help i will ask later because im not home, but its getting there! ill try to finish it up hopefully before next week, with some info i guess, the issues with the spi bus speed are done and running at 8mhz with 120mhz cpu clock t3.5 slave

i still have to sprinkle in a couple of ifdefs for different teensy boards, but as it stands, its setup for teensy 3.x only (if they all follow the same SPI0 register codes, avr support will be last (if any), although the library for master will work for both, slave was designed around the 3.5, and there is nothing in setup() other than the SPI slave function setup call. ps, i also added reset detection support, so the host knows if the slave was reprogrammed/reset, so the end user could reinitialize their hardware automatically

i just drove home to check the analogread/write polling, 10ms led toggling, and uart rx/tx flooding over the SPI bus and its still kicking strong after 24hrs at 8mhz/120cpu, this week i want to add SPI bus ports support as well to finalize things, the rest is testing stages with spi hardware and i2c hardware, like my i2c controller was designed to do (and it controlled the mcp23s17 spi chip over the i2c bus pretty well indeed), but wasnt conplete because SPI would have potential for fastest speeds, so I started from thereon :)

also note: the 8mhz is because its the mega2560 limit, i have not yet attempted to try a teensy 3.5 to 3.5 performance test of centralized hardware in a single sketch, my plan of design is drop in support for libraries , like the lcd, where passing the object to an unmodified library worked as is

another benefit is, perhaps you want a T3.6 or Teensy 4.0 ;) and want 5V tolerancy with extra peripheral access, your thumb sized dual teensy setup could have not only the power of the newer teensies, and peripheral access of the slave, but 5v tolerancy as well without dealing with level shifters since T3.6+ will be 3.3V only

should i even add support for AVR? or just keep it teensy only, we might get more users :D
someone saw this project here and told me theyre ordering teensy just because of it, i _could_ make the slave codes and defines switchable automatically based on the slave device, but let the voters decide, i just might release it as teensy only (the slave) and not waste time with other micros, and just work on adding functionality and features., master library can run on any avr/teensy (that support SPI.transfer16() of course).

I might even put the i2c version on hold as well indefinately, and stick to SPI protocol, its more stable than i2c in general, and faster, that and keeping up support for both takes time to write up the protocols. Its better to stick to one hardware interface and work up from there, and I don't believe relying on i2c will make anyone happy either, especially with the amount of data pushing through to control the hardware at fastest possible speeds. when ill be done i will also setup, test out, and post a dual 3.6+3.5 setup, to demonstrate all the features of this
 
Last edited:
Today's update:

It seems the slave doesnt like to initialize the SPI1/2 ports remotely, so, we will leave those pre-set and initialized, SPI1 using alternate pins to free up Serial1, and SPI2 using default pins, you may choose your Transaction speeds for them before writing to slave. Irregardless, there is progress. In my test I assigned pin2 as the CS using SPI1 alternate pins. So we will be using only 2/3 commands in the master. digitalWrite, transfer, or transfer16 is supported
I am attaching a screenshot of the loop function together with the spi and i2c mcp23x17 expanders on the teensy slave
Code:
  if ( millis() - tyme > 2000 ) {
    tyme = millis();
    ( !flipper ) ? flipper = 0b10000000 : flipper = 0;
    TWire1.beginTransmission(0x20);
    TWire1.write(0x00);
    TWire1.write(0x00);
    TWire1.endTransmission();

    TWire1.beginTransmission(0x20);
    TWire1.write(0x12);
    TWire1.write(flipper);
    TWire1.endTransmission();
    // initialize chip
    TSPI1.digitalWrite(2, LOW); TSPI1.transfer(0x40); TSPI1.transfer(0x0A); TSPI1.transfer(0x18); TSPI1.digitalWrite(2, HIGH);
    // set registers
    TSPI1.digitalWrite(2, LOW);
    TSPI1.transfer(0x40 | ((0x20 & 0b111) << 1));
    TSPI1.transfer(0x00);
    TSPI1.transfer(0x00);
    TSPI1.digitalWrite(2, HIGH);

    TSPI1.digitalWrite(2, LOW);
    TSPI1.transfer(0x40 | ((0x20 & 0b111) << 1));
    TSPI1.transfer(0x12);
    TSPI1.transfer(flipper);
    TSPI1.digitalWrite(2, HIGH);


  }
At 8 MHz, the loop is cycling these devices ( and pretty soon i wont have a workbench at this rate of massive combined wiring & device tests LOL ) :

Serial1 @ 650,000 baud constant read/writes to a display
Serial2 @ 650,000 baud constant read/writes to a display
gpio.digitalWrite(13, !gpio.digitalRead(13)); led toggle with a 10ms delay (so the flicker will be seen);
analogWriting
gpio.analogWrite(15, random(1,200));
Serial.println(gpio.analogRead(16));
toggling of mcp23017 bank A pin 7 every 2 seconds over Wire1 bus
toggling of mcp23S17 bank A pin 7 every 2 seconds over SPI bus

everything combined over a single SPI interface!

https://www.youtube.com/watch?v=eN6KXOUNS90&t=7s

Mega2560 == host
Teensy 3.5 == slave
BLACK breadboard sports a MCP23S17 SPI expander
RED breadboard sports a MCP23017 I2C expander
 
Last edited:
Update:
Seeing as the T3.5 has no alternate pins for TX2 situated on pin10, I had to relocate the slave to use pin2 instead. This allows (tested) slave to continue operation while Serial2 remains functional.
After careful debugging, there is definately uart corruption going above 4Mhz, which can be seen in this video, where those digits were written to the display and read back to display in debug monitor, 4Mhz seemed to be stable when comparing against 6Mhz

https://www.youtube.com/watch?v=7Y3k9itEhiQ
 
Last edited:
Update! 24 MHz SLAVE SUPPORT! it seems every 16bit transfer needed a few microsec delays to be "stable" between each other (aparently not just for reading, but writing as well!), there are no more serial issues at 24MHz using stock cpu speeds of T3.5(120MHz) slave and T3.6 master(180Mhz).

I am, however having problems (not with sending SPI traffic to a remote SPI port, but receiving it through the same ISR. This I havn't done before but it is indeed tricky and I'll keep at it!

^--- edit, I was using the wrong command to read the chip. fixed on the next post ;)
 
Last edited:
Update:
1) SPI begin and end transactions finally working remotely via uint32_t speed specified at the host
2) x.transfer and x.transfer16 are both working and both return their values if requested as well to the host, as shown in the video
3) same goes for wire read, it reads the value from the remote expander and flips it to toggle the chip's gpios as demonstrated on the DMM
4) both lcds still run non stop at 625000 baud rate
5) the led is blinking and analogread is running as well. Analogwrite was tested on both DAC0 and DAC1
6) I defined most of the peripheral ports used in this code (with adjusted pin change) of the slave to be active, so initializing them in the host is not necessary, but still possible
Serial2 is usable as the slave's CS line is moved to pin2 of the slave. The 3.6 is connected from it's SPI2 to the SPI0 of the T3.5slave
7) I decided to keep the code teensy specific :)
8) I have no knowledge of DMA, however, you'll be glad to know this doesnt use it at these speeds!
9) This code uses the built in FIFO buffer of the slave, the slave goes through the buffer for the correct data, so yeah, it prolly wont work on a 1 FIFO slave :)

I have attached a video of the teensy 3.5+3.6 running at 24mhz spi bus speed, with the 3.5 controlling 5v i2c+spi port expanders, while the T3.6 host treated it as it's own with no level shifters involved! :)
https://www.youtube.com/watch?v=mhdjaL9d31U&t=5s

Code:
spi_controller teensyUART = spi_controller("Serial", 43, &SPI2);
spi_controller teensyUART1 = spi_controller("Serial1", 43, &SPI2);
spi_controller teensyUART2 = spi_controller("Serial2", 43, &SPI2);
spi_controller teensyUART3 = spi_controller("Serial3", 43, &SPI2);
spi_controller teensyGPIO = spi_controller("GPIO", 43, &SPI2);
spi_controller TWire = spi_controller("Wire", 43, &SPI2);
spi_controller TWire1 = spi_controller("Wire1", 43, &SPI2);
spi_controller TSPI1 = spi_controller("SPI1", 43, &SPI2);
spi_controller TSPI2 = spi_controller("SPI2", 43, &SPI2);

also, the initializer above also puts the specified CS line (43 in this case) in a HIGH output state (pinmode+digitalwrite) after assigning the object to it.
SPI0 code is blocked and not used, since it belongs to the slave, it musn't be touched.
 
Last edited:
i think i remember SPISettings clocks SPI down to max speed, master FBUS/2, slave FBUS/4 is this correct?
if so, at stock 120mhz slave teensy 3.5 and 180mhz master T3.6, with spisettings of 24,000,000, spisettings doesnt actually set that speed correct?
does anyone know what kind of SPI bus speed i should be achieving at stock speed vs what i could achieve during the tests of the master/slave code if i were to overclock the cpu of the slave (168mhz?) and master(240mhz), i mightes well test it since the devices are connected and pumping traffic every day now non stop to test the stability

i also really wanna test a dual slave setup, perhaps 1 master teensy3.6 and 1x t3.5 slave and 1x teensy 3.6 slave, but i need to go get more headers first

edit:
new observation, 3MHz is minimum it seems for teensy to be slave, i tried lower than 3mhz for spisettings and it's actually causing loss of data between master slave, from all peripheral ports
anywhere between 3000000 -> 24000000 is retaining values

observation 2, master OC @ 240mhz, slave OC @ 168mhz, SPISettings set to 75,000,000, both set to Faster
again, im pretty sure spisettings lowers the rate to the max supported, and the code is still working with the overlocked master/slave setup and exagerated spisettings

observation #3, the 3.6 @ 240 seems a bit warmer than the 3.5 @ 168, but we're talking luke warm by touch test, i dont have a thermometer here :p slave is the one doing most of the work tho hehe

observation #4, keeping SPISettings at 75,000,000 was running stable (while debugging monitor values) with 240mhz t3.6 master and 186mhz t3.5 slave.
keeping it at 75,000,000, i put the stock cpu speeds back, 180 for 3.6, 120 for 3.5
Result: data corruptions, albeit intermittant
Changed to 30,000,000, intermittant, less errors.
Changed to 24,000,000, stable at stock speeds
I would guess SPISettings doesnt go towards the less highest if it got errors at stock, but what the heck, did I actually run a 75mhz spi setup? and if so, how far could i push it? lol./..
 
Last edited:
Status
Not open for further replies.
Back
Top