Memory on Arduino does not seem to clear, causing i2c problem receiving string

I have connected a master Raspberry Pi and a slave Arduino Uno using their respective SDA and SCL pins to enable i2c data transfer. All is well, except that if I transmit a string from RPi to Arduino such as 'Hello123' to the Arduino and later transmit a shorter string such as 'Hello', the Arduino will not recognize the shorter string and will persist with the longer string. If I instead transmit a shorter string such as 'Duh', the resulting string value on the Arduino will by "Duhlo123'. I have tried nulling both the receiving character array and the string, both before and after the interrupt, but no luck. I have tried using both volatile char array and char array, getting the same result. I have tried iteratively nulling the receiving array by inserting (char)0 at each indexed location, but no luck. The transmission of the string back to the RPi from the Arduino has no problems.

Here is the Arduino code:

#include <Wire.h>

#define SLAVE_ADDRESS 0x08

//byte b[12] = {237, 255, 255};
// data buffer
int data[9];
char str0[12];
String dummy1;
String cleanString;

char str[12] = "everything ";

void convertToCleanString (volatile char dummy[]) {
cleanString = "";
cleanString = dummy;

cleanString.remove(0, 1); //remove first character artifact
cleanString.trim();//remove empty space in String

}

/*
This works to receive a string up to 12 characters.
*/
void receiveData(int byteCount) {
// volatile char stro[12];
int counter = 0;
while (Wire.available()) {
str0[counter] = (char)Wire.read();
counter ++;
}
}

void sendData() {
// Wire.write(b, 12);
// 12 character limit set by python data = bus.read_i2c_block_data(address, 99, 12)
// leading or trailing empty characters truncated in Python by string.strip()
Wire.write(str, 12);
// Serial.println("data sent");
}

void setup() {
Serial.begin(9600); // start serial for output
Wire.begin(SLAVE_ADDRESS);
Wire.onReceive(receiveData);
Wire.onRequest(sendData);
// Serial.println("I2C Ready!");
}

void loop() {
delay(1000);
cleanString = "";
if (cleanString != "") {
convertToCleanString(str0);
}
// dummy1 = str0;
// Serial.print(" dummy1 = ");
// Serial.println(dummy1);
Serial.print("cleanString ");
Serial.println(cleanString);
if (cleanString == "Hello") {
Serial.println("Success");
}
char str0[1];
cleanString = "";
Serial.println("pip");
}

Here is the Raspberry Pi Python code.

#!/usr/bin/env python
from time import sleep
import smbus2
bus = smbus2.SMBus(1)
address = 0x08

str1 = 'HelloLLL'

#Sending a string from RPi to Arduino - Working Code - Arduino Stack Exchange
def StringToBytes(val):
retVal = []
for c in val:
retVal.append(ord(c))
return retVal

#http://www.raspberry-projects.com/pi/programming-in-python/i2c-programming-in-$
def writeData(value):
byteValue = StringToBytes(value)
bus.write_i2c_block_data(address,0x00,byteValue) #first byte is 0=comma$
return -1

Get the ASCII number of a character

number = ord(char)

Get the character given by an ASCII number

char = chr(number)

Give the I2C device time to settle

sleep(2)

while 1:
print("Sending ", str1)
writeData(str1)

bus.write_i2c_block_data(address, 0, str1)

sleep(.5)

data = bus.read_i2c_block_data(address, 99, 12)
#slave i2c must not exceed 12 bytes or message will be truncated.
sleep(.5)
print(data)
str1 = ""

convert to string and print result.

Converting between ASCII numbers and characters « Python recipes « ActiveState Code

-between-ascii-numbers-and-characters/

for x in data:
str1 = str1 + chr(x)
str1.strip()
print(str1)

sleep(0.5)
break

I don't understand where or how the Arduino is holding the undesired characters in memory. Thanks for any help.

If you post your code as described in the how to use this forum sticky, more forum members will read it.

You are mixing String class strings (training wheels) with real C strings (char arrays).

You are correctly reading the data one character at a time, but I can't figure out what your convertToCleanString function is doing.

Why not just insert a zero at the end of the received characters?

/*
   This works to receive a string up to 12 characters.
*/
void receiveData(int byteCount) {
  //  volatile char stro[12];
  int counter = 0;
  while (Wire.available()) {
    str0[counter] = (char)Wire.read();
    counter ++;
  }
str0[counter] =0;
}

By the way, str0[12] can hold eleven characters because the zero terminator is the last element in the array.

The I2C bus is good for reading fixed size packages from a sensor.

The I2C bus of the Arduino Uno is a I2C bus with 5V signals. The Raspberry Pi has 3.3V signals. That means you have a voltage mismatch on the I2C bus. See also this page that I wrote.

When you connect the Arduino to the USB of the Raspberry Pi, then you can use the Serial/UART communication between the Arduino and the Raspberry Pi. That will also avoid a voltage level mismatch.

Steve, Thanks. New to this. I never saw a 'how to use this forum' sticky. I'll keep looking, but if you can suggest a link I'd be happy to take a look. I haven't yet been able to make your suggestion work. I'll keep trying.

Koepel, your understanding of i2c seems different than mine. The connection works fine.

You posted in the "Programming Questions".
If you look at the "Programming Questions" section here: Programming Questions - Arduino Forum, then the top subject is called: "How to get the best out of this forum (short version)". It it pinned to the top, so it stays there. That is also called a 'forum sticky'.

FPerkins:
Koepel, your understanding of i2c seems different than mine.

That is a understatement. I would say that I live in a different world than you :wink:
Can you at least add a I2C level shifter between them ?

When you connect the Arduino with a USB cable to the Raspberry Pi and use Serial/UART communication. That can't be bad, is it ?

Thanks again.

Koepel, please see Raspberry Pi and Arduino Connected Using I2C - Oscar Liang. My configuration is perfectly safe.

Steve, unfortunately your suggestion does not work. Looking into this some more, it got weirder. If I comment out the 'data = bus.read.....' line in the python, the data transmission doesn't happen. If I follow your suggestion, the str0 character array does not convert into a String. I remain puzzled. I will put the code here, now properly (and thanks for your help).

#include <Wire.h>

#define SLAVE_ADDRESS 0x08

//byte b[12] = {237, 255, 255};
// data buffer
int data[9];
char str0[12];
String dummy1;
String cleanString;


char str[12] = "everything  ";


void convertToCleanString (volatile char dummy[]) {
  cleanString = "";
  cleanString = dummy;
  cleanString.remove(0, 1); //remove first character artifact
  cleanString.trim();//remove empty space in String
  }
  

/*
   This works to receive a string up to 12 characters.
*/
void receiveData(int byteCount) {
  int counter = 0;
  while (Wire.available()) {
    str0[counter] = (char)Wire.read();
    counter ++;
  }  
//  str0[counter] = 0;
  convertToCleanString(str0);
}

void sendData() {
  //    Wire.write(b, 12);
  // 12 character limit set by python data = bus.read_i2c_block_data(address, 99, 12)
  // leading or trailing empty characters truncated in Python by string.strip()
  Wire.write(str, 12);
  //  Serial.println("data sent");
}

void setup() {
  Serial.begin(9600); // start serial for output
  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
  //  Serial.println("I2C Ready!");
}

void loop() {
  delay(3000);
  Serial.print("cleanString               ");
  Serial.println(cleanString);
  //  if (cleanString == "Hello") {
  //    Serial.println("Success");
  //  }

  //  Serial.println("pip");
}

And the Raspberry Pi Python code. in the next frame. It doesn't seem to fit here.

#!/usr/bin/env python
from time import sleep
import smbus2
bus = smbus2.SMBus(1)
address = 0x08
 
str1 = 'Hell'
 
#https://arduino.stackexchange.com/questions/47947/sending-a-string-from-rpi-to$
def StringToBytes(val):
        retVal = []
        for c in val:
                retVal.append(ord(c))
        return retVal
 
#http://www.raspberry-projects.com/pi/programming-in-python/i2c-programming-in-$
def writeData(value):
        byteValue = StringToBytes(value)
        bus.write_i2c_block_data(address,0x00,byteValue) #first byte is 0=comma$
        return -1
 
 
## Get the ASCII number of a character
# number = ord(char)
# Get the character given by an ASCII number
# char = chr(number)
 
# Give the I2C device time to settle
sleep(2)
 
 
while 1:
        print("Sending ", str1)
        writeData(str1)
 
#       bus.write_i2c_block_data(address, 0, str1)
        sleep(.5)
 
        data = bus.read_i2c_block_data(address, 99, 12) 
        #slave i2c must not exceed 12 bytes or message will be truncated.
##      sleep(.5)
##      print(data)
##      str1 = ""
        # convert to string and print result.
        # https://code.activestate.com/recipes/65117-converting
        # -between-ascii-numbers-and-characters/
 
        #Convert bytes to characters and print result
##      for x in data:
##              str1 = str1 + chr(x)
##      str1.strip()
##      print(str1)
 
##      sleep(0.5)
        break

FPerkins:
Koepel, please see Raspberry Pi and Arduino Connected Using I2C - Oscar Liang. My configuration is perfectly safe.

"Safe", but poor noise margin for logic HIGH input to Arduino as it's looking for ~5V and SDA is only pulled up to 3.3V. Same store with SCL input to Arduino.

FPerkins:
see Raspberry Pi and Arduino Connected Using I2C - Oscar Liang

Wrong, wrong and wrong :sob:

Some things are mentioned in the comments, for example that the Arduino Uno requires at least 3.5V (it is in the datasheet). I think that I mention most things in the page that I wrote.

So who is right ? The datasheet is always right, so the Uno requires at least 3.5V for a high level of SDA and SCL.
Who knows how to write an interrupt routine ? Everyone on this forum with a little knowledge about interrupts and the Arduino libraries.
He uses the wrong level shifter in the wrong way. Sparkfun does not sell that questionable level converter anymore, but they still have the tutorial online. It is possible to use it for I2C, but then both channels have to be used: RETIRED - Using the Logic Level Converter - SparkFun Learn.

Adafruit's I2C Level Shifter works well in my projects.

Steve, unfortunately your suggestion does not work. If I follow your suggestion, the str0 character array does not convert into a String.

Please explain why you can't convert a null terminated character array to a String? Here's my test of convertToCleanString()

char str0[12]={'0','1','2','3','4','5','6','7','8','9','A','\0'};//11 chars plus null terminator
String dummy1;
String cleanString;

void convertToCleanString (volatile char dummy[]) {
  cleanString = "";
  cleanString = dummy;
  cleanString.remove(0, 1); //remove first character artifact
  cleanString.trim();//remove empty space in String
  }
  
void setup() {
  Serial.begin(115200);
  convertToCleanString(str0);
  Serial.println(cleanString);
  //EDIT: fixed typo in comparison characters
  if(cleanString = "123456789A") Serial.print("Yes its a String");
  else Serial.print("Not converted to String");
}
void loop() {}

FPerkins:
... If I follow your suggestion, the str0 character array does not convert into a String. I remain puzzled. I will put the code here, now properly (and thanks for your help).

I never said it converted it to a String (capital S, training wheels). It simply properly terminates the char array at the end of the data.

I still can't figure out why you need Strings.

Thanks for your help.

Voltage mismatch is not the issue. I switched to a 3.3V Arduino Pro Mini connected to the Raspberry Pi. Same result.

When the Raspberry Pi sends the initial message to the Arduino over i2c, everything is fine. But the communication only works as I'd hoped on the first message received. What I've seen is that when the (master) Raspberry Pi requests date, the (slave) Arduino once again executes the receiveData function (it seems as it should because the SDA line goes high and the wire becomes available on a request for data). Because of this, if there is a statement 'srt0[counter) = 0;' in the receiveData function, the program will of course do that, nulling the balance of the character array because there is no data to be received. This is an unfortunate result of the response to the interrupt when the SDA line goes high. Net of that characteristic is that unless the contents of the char array have already been processed, they will be lost when the SDA line goes high for the Wire.send execution.

On subsequent messages without the unproductive srt0[counter) = 0; instruction, the Arduino does not clear the values previously read into the str0 char array. On subsequent inputs from the Raspberry Pi, the Arduino reads the incoming message, but it does not null any of the contents of the str0 char array. It will only overwrite the array where there are new values. Consequently, if the original input is "Hello" and the second input is "Hell" (my current location, apparently) the net contents of the char array after the read is still "Hello". If I try a different, shorter input such as 'abc', the net contents of the char array becomes 'abclo'. If I input a longer message, the Arduino will overwrite all of the previous contents.

I don't understand what's going on here in hell.

Thanks again.

The 3.3V Arduino board is also better for the Raspberry Pi.

Question: When the Raspberry Pi asks for a number of bytes, how does the Arduino know how many bytes the Raspberry Pi wants ?

If you want to continue to use the I2C bus for text of variable length, then you need to describe what will be sent over the I2C bus. Also give a few examples. Explain when a zero-terminator is at the end or why not. Once we know that, we can help to make a sketch that does that.

FInally got it working the way I want! The complementary Arduino and Raspberry Pi Python code are included below. This arrangement allows me to send a variable length character array bidirectionally between a Raspberry Pi and an Arduino where their conversion to Strings is convenient for humans. The earlier attempts failed to clear the Arduino's volatile memory properly, and there may have been timing issues as well. In any case this now works.Thanks for the help.

#include <Wire.h>
#define SLAVE_ADDRESS 0x08

/*
 * Matched to sketch_feb_08d_string2string_works.py
 */

//byte b[12] = {237, 255, 255};
// data buffer
int data[9];
char str0[12];
String dummy1;
String cleanString;
unsigned long printTimer ;
int printInterval = 2000;

char str[12] = "everything  ";


void convertToCleanString (volatile char dummy[]) {
  cleanString = "";
  cleanString = dummy;

  cleanString.remove(0, 1); //remove first character artifact
  cleanString.trim();//remove empty space in String

}

/*
   This works to receive a string up to 12 characters.
*/
void receiveData(int byteCount) {
  //  volatile char stro[12];
  int counter = 0;

  while (Wire.available()) {
    str0[counter] = (char)Wire.read();
    counter ++;
  }
}

void sendData() {
  //    Wire.write(b, 12);
  // 12 character limit set by python data = bus.read_i2c_block_data(address, 99, 12)
  // leading or trailing empty characters truncated in Python by string.strip()
  Wire.write(str, 12);
  //  Serial.println("data sent");
}

void setup() {
  Serial.begin(9600); // start serial for output
  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
  //  Serial.println("I2C Ready!");
  printTimer = millis();
}

void loop() {
  convertToCleanString(str0);
  if ((millis() - printTimer) >= printInterval) {
    Serial.print("pip!     cleanString =  ");
    Serial.println(cleanString);
    if (cleanString == "Hello") {
      Serial.println("Success");
      cleanString = "";
    }
    printTimer = millis();
    for (int i = 0; i < sizeof(str0) ; i++) {
      str0[i] = 0;
    }
  }
}
#!/usr/bin/env python

//matched with sketch_feb_08d_string2string_works.py

from time import sleep
import smbus2
bus = smbus2.SMBus(1)
address = 0x08

str1 = 'Hello'

#https://arduino.stackexchange.com/questions/47947/sending-a-string-from-rpi-to$
def StringToBytes(val):
        retVal = []
        for c in val:
                retVal.append(ord(c))
        return retVal

#http://www.raspberry-projects.com/pi/programming-in-python/i2c-programming-in-$
def writeData(value):
        byteValue = StringToBytes(value)
        bus.write_i2c_block_data(address,0x00,byteValue) #first byte is 0=comma$
        return -1


## Get the ASCII number of a character
# number = ord(char)
# Get the character given by an ASCII number
# char = chr(number)

# Give the I2C device time to settle
sleep(2)


while 1:
        print("Sending ", str1)
        writeData(str1)

#       bus.write_i2c_block_data(address, 0, str1)
        sleep(.5)

        data = bus.read_i2c_block_data(address, 99, 12) 
        #slave i2c must not exceed 12 bytes or message will be truncated.
        sleep(.5)
        print(data)
        str1 = ""
        # convert to string and print result.
        # https://code.activestate.com/recipes/65117-converting
        # -between-ascii-numbers-and-characters/

        #Convert bytes to characters and print result
        for x in data:
                str1 = str1 + chr(x)
        str1.strip()
        print(str1)

        sleep(0.5)
        break

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.