Teensy and Arduino two way communication

Status
Not open for further replies.

JotaEfe13

Well-known member
I am creating an audio sampler/sequencer with Teensy 4.1 and I am running out of pins, so instead continuing adding multiplexers, I have thought about using an inexpensive ATMEGA4809 (the Arduino Every microchip) to outsource some simple tasks. Specifically I have thought about using it for reading about 16 buttons and controlling 12 LEDs.
The idea is that the ATMEGA4809 will be permanently scanning if any button has been pushed and, if so, communicate it to the Teensy. On the other hand, the Teensy will send data to the ATMEGA4809 indicating that a specific LED has to be turned on or off. I have thought that liberating the Teensy of permanently scanning those buttons would free some processing power, too.


My first doubt was if it would be better to use I2C or SPI. I know SPI is faster, but on the other hand it takes more pins, is a bit more complicated to implement and is more prone to noise issues. And given the small amount of information that I have to transmit, I don't think that the difference in speed will be relevant. So I think it would be more appropriate to use I2C.

My main concern is how to implement the communication without causing any issue with the main task of the application, i.e. the audio stuff, and, at the same time without too much latency in the readings.

Ideally, I would like to send and receive messages only when new data is available, but I have seen that this behaviour is only possible for the direction Master --> Slave. Nevertheless, for getting data sent by the Slave, the Master must explicitly request it.

My main doubt is, is it a good idea to call the Wire.requestFrom() in every loop iteration so that the data is fetched as soon as possible, or would it be a better idea to do the call every X milliseconds? I have read that Wire.read() is a blocking operation, so could it mess up the timing in the audio?

Something that I have noticed is that after calling Wire.requestFrom(), Wire.available() seems to return true (and reads 0) even when the slave haven't written anything.


I have tested the following code using an Arduino Pro Micro instead of the ATMEGA4809 and it seems to work well, but I don't know how well it would scale. In this example I read the state of 4 buttons in the Teensy (Master) and another 4 buttons in the Arduino (Slave).
When a button is pushed in the Teensy (Master) it sends a new byte with the number of the button.
When a button is pushed in the Arduino (Slave), as it has to wait for the Master to make request, I am storing the pushed button in an array and sending their numbers in the following requests until there are no more in the array. The array is acting as a queue. I would try changing it for a CircularBuffer in case I continue with this approach.

Master - Teensy 4.1
Code:
/*
  Board: Teensy 4.1
  I2C Master
  SDA: 18
  SCL: 19
 */

#include <Wire.h>
#include <Bounce.h>

#define BTN_1_PIN 25  
#define BTN_2_PIN 26
#define BTN_3_PIN 27
#define BTN_4_PIN 28
#define ADDRESS 8

Bounce teensyBtn1 = Bounce(BTN_1_PIN, 20);
Bounce teensyBtn2 = Bounce(BTN_2_PIN, 20);
Bounce teensyBtn3 = Bounce(BTN_3_PIN, 20);
Bounce teensyBtn4 = Bounce(BTN_4_PIN, 20);
Bounce *teensyBtnArray[4] = {&teensyBtn1, &teensyBtn2, &teensyBtn3, &teensyBtn4};

void setup() {
  Serial.begin(9600);
  
  pinMode(BTN_1_PIN, INPUT_PULLUP);
  pinMode(BTN_2_PIN, INPUT_PULLUP);
  pinMode(BTN_3_PIN, INPUT_PULLUP);
  pinMode(BTN_4_PIN, INPUT_PULLUP);
  
  Wire.begin();
}

void loop() {

  // Check if there are buttons clicked
  for(byte i=0; i<4; i++) {
    teensyBtnArray[i]->update();
    if (teensyBtnArray[i]->fallingEdge()) {
      // Send button number to Arduino
      Wire.beginTransmission(ADDRESS);
      Wire.write(i + 1);
      Wire.endTransmission();
    }
  }

  // Request new data from Arduino
  Wire.requestFrom(ADDRESS, 1);
  
  // Read data sent from Arduino
  while (Wire.available()) { 
    int x = Wire.read();
    if (x > 0) {
      Serial.print("Clicked button ");
      Serial.print(x);
      Serial.println(" on Arduino.");
    }
  }
}


Slave - Arduino Pro Micro
Code:
/*
  I2C Slave
  
  Board: Arduino Pro Micro (generic brand)
  Choose --> Tools / Board / Arduino Leonardo

  Conn  | Pro Micro | Teensy
  ---------------------------
  SDA   | pin 2     | pin 18  (pullup resistor 2K2-4K7 to 3.3V)
  SCL   | pin 3     | pin 19  (pullup resistor 2K2-4K7 to 3.3V)
  GND   | GND       | GND
 */

#include <Wire.h>
#include "Bounce.h"

#define BTN_1_PIN 10  
#define BTN_2_PIN 16
#define BTN_3_PIN 14  
#define BTN_4_PIN 15
#define ADDRESS 8

Bounce arduinoBtn1 = Bounce(BTN_1_PIN, 20);
Bounce arduinoBtn2 = Bounce(BTN_2_PIN, 20);
Bounce arduinoBtn3 = Bounce(BTN_3_PIN, 20);
Bounce arduinoBtn4 = Bounce(BTN_4_PIN, 20);
Bounce *arduinoBtnArray[4] = {&arduinoBtn1, &arduinoBtn2, &arduinoBtn3, &arduinoBtn4};

// Array for storing clicked buttons
bool buttonClicked[4] = {false, false, false, false};

void setup(){
  Serial.begin(9600);
  
  pinMode(BTN_1_PIN, INPUT_PULLUP);
  pinMode(BTN_2_PIN, INPUT_PULLUP);
  pinMode(BTN_3_PIN, INPUT_PULLUP);
  pinMode(BTN_4_PIN, INPUT_PULLUP);

  Wire.begin(ADDRESS);
  Wire.onRequest(requestFromTeensy);
  Wire.onReceive(receiveFromTeensy); 
}

void loop() {
  // Check if there are buttons clicked
  for(byte i=0; i<4; i++) {
    arduinoBtnArray[i]->update();
    if (arduinoBtnArray[i]->fallingEdge()) {
      // Store flag in array
      buttonClicked[i] = true;
    }
  }
}

void requestFromTeensy() {
  // Check array of clicked buttons
  for(byte i=0; i<4; i++) {
    if (buttonClicked[i]) {
      // Send button number to Teensy
      Wire.write(i + 1);
      buttonClicked[i] = false;
      break;
    }
  }
}

void receiveFromTeensy() {
  // Read data sent from Teensy
  while (Wire.available()) {
    int x = Wire.read();
    Serial.print("Clicked button ");
    Serial.print(x);
    Serial.println(" on Teensy.");
  }
}

Any recommendations for enhancing the code above?

Thanks.
 
That's more or lesse like a PCF8547. The PCF8547 is a I2C I/O Expander - there are more chips like this. You can use several of them on I2C.
It (optionally) uses an "Interrupt" ouput that can inform the CPU that something has changed, so polling is not needed if you use this pin. You need to read the data only, if the INT gave a signal. If you don't want a ready to use solution like this, you could add the INT functionality to your AVR.
 
Thank you for your insights, Frank. I had indeed thought about using interrupts, but I have read the documentation about interrupts in the Audio library, I am still not sure how/if adding more interrupts while I am using the Audio lib would mess up its routines. I will try and see if I notice any issues.
 
Thank you for your insights, Frank. I had indeed thought about using interrupts, but I have read the documentation about interrupts in the Audio library, I am still not sure how/if adding more interrupts while I am using the Audio lib would mess up its routines. I will try and see if I notice any issues.

If you use the interrupt only to set a flag (use a volatile variable) there will be no problem.
 
I have implemented the code to use interrupts, but not tested it yet, as I have not received the ATmega4809 yet, and the board I am using as slave uses 5v.

But I was thinking, maybe it would make more sense using Teensy as Slave and ATmega4809 as a Master. I found this teensy4_i2c library that allows to use Teensy 4 as Slave. As my main concern is to get fast updates from ATmega4809 whenever there is a change, and the Slave receives them without the need of a prior request, I think it would be better this way.

Does the change of role from Master to Slave have any implication in performance or noise? The only obvious difference I can see is that it will stop generating the clock signal.
 
Status
Not open for further replies.
Back
Top