I am communicating via I2C between a TinyAVR chip I've programmed in C and an Arduino. I have this system working with an Arduino Uno (Arduino is bus slave) using the same I2C code, and I am not able to get it working on the Yun.
First, I've already been through the mistake of the I2C pins being 2 and 3 instead of A4 and A5 on the Yun.
My wiring (Arduino Yun -> TinyAVR) is 3v3 -> Vcc, 3 -> SCL, 2 -> SDA, Gnd -> Gnd, and there are 4.7k pullups on SDA and SCL to 3v3. I have additionally stuck an LED + resistor between SDA and 3v3 so that it will blink when there is bus activity.
What I'm seeing now is that my Yun code enters the I2C event handler function and crashes. In the code I've included, which works on the Uno (pin differences excepted), I do see the LED turn on (pin 13), but the Console message is never sent and the LED never turns off (upon function exit). The crash occurs immediately following the bus monitor LED blinking; everything else in the code (I haven't included loop() below) runs as usual until an I2C bus event happens.
Any suggestions? Thanks!
void setup() {
...
pinMode(3, INPUT); // set pin to input
digitalWrite(3,HIGH);
pinMode(2, INPUT);
digitalWrite(2,HIGH);
Wire.begin(4); // join i2c bus with address #4
Wire.onReceive(receiveEvent); // register event
...
}
void receiveEvent(int howMany)
{
Console.println("Received data:");
digitalWrite(LED_PIN, HIGH);
while(Wire.available()) // loop through all but the last
{
char c = Wire.read(); // receive byte as a character
Console.println(String(c));
if (c == '\n') { // If we just recieved an end-of-line, dump the string w/ timestamp
Console.println(/*millis()/1000 + */strBuf); // print the whole line
strBuf = String(""); //String(", "); // clear the buffer
} else if (c == '\r') {
/* Ignore (drop) carriage return */
} else {
strBuf.concat(String(c)); // append incoming character to the buffer
}
}
Console.println(); // The I2C sender must send newlines
digitalWrite(LED_PIN, LOW);
}
First, I've already been through the mistake of the I2C pins being 2 and 3 instead of A4 and A5 on the Yun.
My wiring (Arduino Yun -> TinyAVR) is 3v3 -> Vcc, 3 -> SCL, 2 -> SDA, Gnd -> Gnd, and there are 4.7k pullups on SDA and SCL to 3v3.
There are separate pins on the UNO and the Yun called SDA/SCL that are wired to the correct pins on the processor for I2C communication.
I have additionally stuck an LED + resistor between SDA and 3v3 so that it will blink when there is bus activity.
What resistor value did you choose? That changes the pull-up resistor value is not a very good idea. You won't see the LED blinking anyway because it's much too fast to see.
What I'm seeing now is that my Yun code enters the I2C event handler function and crashes.
You do call Serial code inside the interrupt handler:
Console.println("Received data:");
That's a bad idea and logically wrong although it may work sometimes under special circumstances.
Wire.begin(4); // join i2c bus with address #4
Why is the Yun configured to be an I2C slave? Does it really make sense to make the Tiny the master and the Yun the slave? Please post the corresponding code on the Tiny.
Your code is just an excerpt of the complete code. Did you check that the error happens with exactly that code (and only that code)? If not, please post the complete code that really shows the symptoms you're describing. You may think that the rest is not relevant but that thinking may be wrong.
Try to eliminate the String class because it's cluttering up the memory with constant allocation and deallocation of buffers. Use c-style strings (character arrays) instead.
The green bus LED has a 220 ohm resistor and is configured to light up when the bus is pulled low. You're right that I'm not guaranteed to see it light up, but with my usage (sending a string in a burst) I do see what appears as one blink when it's working on the Uno. I'll pull it and just use a 10x scope probe.
The Yun is the slave because it never initiates communication. The TinyAVR spends most of its life asleep and is sending the information as it generates it intermittently - it's essentially a datalogger.
To be clear, the receive event handler is an event handler and not an interrupt handler (or called inside an interrupt handler), right? I understand that I shouldn't have a lot going on in there but it's not the same level of urgency as an interrupt vector, right?
Is there any reference example you'd recommend on achieving the same intent as my handler code without doing serial communication inside it?
I'm going to create a reduced example using a char array buffer and a boolean flag to let loop() observe and handle serial reporting of the received data, but this risks corruption if a new line of data comes in before loop() gets to it.
Ok, things seem to be working although I wouldn't yet rule out race conditions between loop() and my event handler.
I've removed any calls to serial comm in the event handler, instead using a flag & buffer to do it in loop(). I know that I could use a string instead of a String in the handler but I'd rather be lazy and quick than parsimonious but deal with null terminators. I am interested in any better way of doing this that isn't vulnerable to losing an incoming string because the buffer hasn't been cleared in loop() yet.
Thanks!
#define LED_PIN (13)
#define SAMPLE_DELAY_MS (100)
// (globals)
String i2cBuf = ""; // buffer for line of text
boolean i2cData = false; // flag set when line received
...
void setup()
{
...
pinMode(3, INPUT); // set pin to input
digitalWrite(3,HIGH);
pinMode(2, INPUT);
digitalWrite(2,HIGH);
Wire.begin(4); // join i2c bus with address #4
Wire.onReceive(receiveEvent); // register event
...
}
void loop() {
if (i2cData)
{
Console.println(getTimeStamp() + ", " + i2cBuf); // getTimeStamp() uses a Process object to call unix date function
i2cBuf = "";
i2cData = false;
}
... (doing some ADC reads and printing using Console)
delay(SAMPLE_DELAY_MS); // would be nice if an i2c event could interrupt this delay loop
}
void receiveEvent(int howMany)
{
digitalWrite(LED_PIN, HIGH);
while(Wire.available())
{
char c = Wire.read(); // receive byte as a character
if (c == '\n') { // If we just recieved an end-of-line, set buffer flag
i2cData = true;
} else if (c == '\r') {
/* Ignore (drop) carriage return */
} else {
i2cBuf += c; // this could write to the buffer while loop() is accessing it?
}
}
digitalWrite(LED_PIN, LOW);
}
To be clear, the receive event handler is an event handler and not an interrupt handler (or called inside an interrupt handler), right? I understand that I shouldn't have a lot going on in there but it's not the same level of urgency as an interrupt vector, right?
It's an event handler and is called from an interrupt handler in interrupt context. In a microcontroller and interrupt is not directly related to urgency but just how the hardware handles things. Almost anything called by a hardware event is called in interrupt context (on the Arduino there is one exception: the serial receive event which is called from the main loop).
Is there any reference example you'd recommend on achieving the same intent as my handler code without doing serial communication inside it?
I don't have an example at hand but just set a flag variable (take care to declare it "volatile") and react on that in the main loop() routine, there you can print to the serial interface without any problems. You got to the same conclusion and that's how it's done.
I've removed any calls to serial comm in the event handler, instead using a flag & buffer to do it in loop(). I know that I could use a string instead of a String in the handler but I'd rather be lazy and quick than parsimonious but deal with null terminators. I am interested in any better way of doing this that isn't vulnerable to losing an incoming string because the buffer hasn't been cleared in loop() yet.
You didn't tell us what other things you do in the loop (the sketch seems to be not complete), so it's hard to direct you the right way. How fast is the Tiny sending it's data? Does that happen faster than what one loop() run needs? If this is the case use multiple buffers to hold the data items you get from the tiny or use a ring buffer with several position pointers. There are many different ways to do this but first I would try to get rid of that String class. What kind of data is the Tiny transferring? Do you know how many bytes that are?
Pylon- thanks for your help. I know I still haven't told you what else is going on in loop() but you've given me enough ideas to solve my problems.
I am still a bit confused why it's so bad to use String instead of string (character array) in an event handler - is this more important if I'm short on RAM? Would you recommend any particular tools for better understanding memory usage, such as Visual Studio or Eclipse's debugging capabilities (that I am speculating them to have)? I've just used Atmel Studio
Would you recommend any particular tools for better understanding memory usage, such as Visual Studio or Eclipse's debugging capabilities (that I am speculating them to have)? I've just used Atmel Studio
Eclipse and least of all Visual Studio will give you any additional debugging functionality. They're designed to develop software for PCs, not microcontrollers. You might get a bit more support from Atmel Studio than you get from the Arduino IDE but on the Arduino you're usually limited to the serial debugging.
I am still a bit confused why it's so bad to use String instead of string (character array) in an event handler - is this more important if I'm short on RAM?
You are short on RAM on the Arduino. An UNO has 2kB of RAM while the Leonardo/Yun offers 2.5kB of it. Both sizes are ridiculous compared to today's PCs so you have to take special care for it. The String class is designed after implementations for PCs and therefore takes use of dynamic allocation of memory for it's internal buffers. A PC has a coprocessor specifically designed for such kind of memory handling (MMU, Memory Management Unit) but microcontrollers as the ATmega series doesn't offer such luxury stuff. So if you use the String class the available memory on the Arduino gets fragmented in no time and sooner or later a memory allocation fails. When this happens things start to get strange, the behavior might change randomly.
If you use C-style strings (character arrays), you, the programmer, take care to reserve enough memory for the task. This is usually done without using dynamic memory allocation calls (malloc(), free()), so the used memory stays as compact as possible and especially the usage is much more static and predictable.