So I'm receiving data from an ESP module via I2C. I then need to send this data immediately over the UART. Not sure how I should do this. Would it be ok to place the UART sending inside the i2c receive interrupt?
-
1\$\begingroup\$ Before you can implement this, you need to decide what should happen if the I2C generates data faster than the serial port can dispose of it, or implement something to make sure that cannot happen. One you have that, you can use an efficient implementation of a transmit buffer. \$\endgroup\$– Chris StrattonCommented Feb 27, 2019 at 17:33
1 Answer
Are you using the Arduino IDE to program your ESP8266? Never put Serial code into an interrupt function. More info here. Check the section Hints for writing ISRs
Regardless of the IDE you're using, I'd recommend you to define a global variable in your code (generally a boolean) to inform that your data was received over the I2C protocol, then in the main part of the code (loop function in Arduino programs) you could check this variable and then send the received data over the UART.
Here is an example using the Arduino IDE. Of course, you'll have to use the correspondent Wire library to your ESP8266.
EDIT: Added the concept of atomicity to ensure our variable inputString
is not messed up with consecutive unhandled interruption. For more details, you can check this demonstration
#include <Wire.h>
#include <util/atomic.h>
String inputString = ""; // a String to hold incoming data
volatile bool stringCompleted = false; // whether the string is complete
void setup() {
Wire.begin(0x20); // join i2c bus with address 0x20
Wire.onReceive(receiveEvent); // register event
// reserve 200 bytes for the inputString:
inputString.reserve(200);
Serial.begin(9600); // start serial for output
}
void loop() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// print the string when a newline arrives:
if(stringCompleted) {
Serial.println(inputString);
// clear the string:
inputString = "";
stringCompleted = false;
}
}
}
// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
while (Wire.available() > 0) { // loop through all
char inChar = (char)Wire.read(); // receive byte as a character
inputString += inChar; // add it to the inputString:
// if the incoming character is a newline, set a flag so the main loop can
// do something about it:
if(inChar == '\n') {
stringCompleted = true;
}
}
}
-
\$\begingroup\$ I think you need the
volatile
attribute for your globalbool
variable. Also, to be precise, serial code in an ISR is fine, calls toSerial.print()
should not be in an ISR. \$\endgroup\$ Commented Feb 27, 2019 at 13:03 -
\$\begingroup\$ Yeah! and
Serial.read()
must not be used also. To avoid problems, I prefer to not put Serial codes in ISRs at all. You are correct about thevolatile
attribute \$\endgroup\$ Commented Feb 27, 2019 at 13:06 -
\$\begingroup\$ Using the expensive String class for this is unwise. It would actually be better to use the already written efficient transmit buffering in the Serial class, the catch is that you need to detect the overflow condition where it can block. But your code does not handle that either, and with I2C often faster that is quite possible - or probable if you try to use such a slow baud rate! \$\endgroup\$ Commented Feb 27, 2019 at 15:11
-
\$\begingroup\$ Further, unless you can prove that operation on String objects are atomic (which seems unlikely) mutating them in an ISR event is unsafe. In short, this code creates several new problems while trying to solve one simple one. \$\endgroup\$ Commented Feb 27, 2019 at 15:18
-
\$\begingroup\$ @ChrisStratton what do you mean about "atomic"? I have never had any problems with this code before. It works simply fine, even in an expensive code. It's important to say this is one of the standard examples from the Arduino software, provided by the Arduino company. However, I'd like to see your improvement suggestions. Can you please give us some example? \$\endgroup\$ Commented Feb 27, 2019 at 15:48