Asked — Edited
Resolved Resolved by ptp!

I2c Response Not Recognized

I am trying to connect my IoTiny to an Arduino Pro Mini via I2C. I am able to successfully write for the IoTiny to the Arduino but when I try to request a response from the Arduino the data see corrupt.

I've tried very basic ez-script:

print(i2cRead( 0, 0xA,1 ))

And Arduino code:


  String outtoezb;

  outtoezb = "R";
  
  char  tempout[outtoezb.length() + 1];
  outtoezb.toCharArray(tempout,outtoezb.length() + 1); 

  Wire.write(tempout); 
  Serial.print("Response Sent: ");
  Serial.println(outtoezb);

When executed I get the follow on the IoTiny:


Start
> 
Done (00:00:00.0230307)

And I see that the request get through to the arduion:


My i2c address: 10
Response Sent: R

Any thoughts?


ARC Pro

Upgrade to ARC Pro

Become a Synthiam ARC Pro subscriber to unleash the power of easy and powerful robot programming

PRO
Synthiam
#1  

You are not sending an ascii string. You are sending an int or a byte or what ever tempout is.

If you look at the variable watcher, and read the data into a variable, you will see it's not an ascii value.

You're expecting a string holding a number - but it's an int value. see in your arduino code where you convert the int to a string to view it with print? Well, you'd have to do something similar in ARC.

However, Because you are not sending ascii, the incorrect i2c read command is used. Use the i2cReadBinary() because it's not ascii.

There's examples of communicating with an arduino in the learn section user tutorials. But Ive pretty much answered the question :)

#2  

I've simplified the code even further. In EZ-Script I wrote:


$msg = i2cRead(0,0xA,5)

and the Arduino code simple has:


  Wire.write("HELLO");

the variable watcher returned $msg as " "

I then replaced the EZ-Script with:


i2cReadBinary(0,0xA,5,$msg)

and the Arduino code was the same. This time the variable watcher returned:

$msg[0] as 0 $msg[1] as 255 $msg[2] as 255 $msg[3] as 255 $msg[4] as 255

I tried the code from the Arduino example with the same results. Again, writing data via I2C from EZ-Script to the Arduino works fine.

PRO
Synthiam
#3  

Oh, and i believe with i2c you can't simply read without writing first. Doesn't the host need to write an address? And the slave returns a value for each ACK?

Looks like your trying to use i2c as a serial connection.

Let me find an example for you. I know I made one for someone before

PRO
Synthiam
#4  

Yeah, apologies for leading you on a wild goose chase. I wasn't thinking clearly on my previojs responses because I've been doing yard work today :)

So your code is trying to use i2c as if it is a uart connection. And that won't work, as you see. The arduino is a slave, which means it must send data as requested. It doesnt simply send a string, because that's not how i2c works

take a look at this URL. The Slave code is what you want to see. It demonstrates the interrupt that is raised on the arduino for queries of a register: http://forum.arduino.cc/index.php?topic=38411.0

The i2cRead() on ARC can still be used if you're responding with ascii. The arduino code responds to the register query from the interrupt.

I can write some more code for you, if you want to post your complete arduino code. Because what I feel you are doing is wanting the ezb to query the arduino for sensor data? Specifically temperature?

#5  

I was just putting in the main commands of my code, not the whole thing which looks like this:


#include <Wire.h> 

boolean goti2c = false;
int myi2c = 10;


//------------------------------------------------------
void setup() {
  Serial.begin(57600); // Debug Console
  
  Serial.print("My i2c address: ");
  Serial.println(myi2c);
  Wire.begin(myi2c);   
  Wire.onRequest(receivei2cEvent); 

}

//------------------------------------------------------
// i2c Command Event Handler
void receivei2cEvent(int howMany) {
  goti2c=true;
}

void processi2c() {

  goti2c = false;
  Wire.write('HELLO'); 

  }

//-----------------------------------------------------------
void loop() {

  delay(100);

  if (goti2c) processi2c();
   
}



I'm pretty sure that it is implemented correctly.

PRO
Synthiam
#6  

No - that won't work at all. There's a 100ms delay and the logic isn't logical :). The i2c will timeout below the 100ms delay. If you read about i2c or checked the link i provided, there can only be one byte sent per request. i2c doesn't have a buffer. Again, i can't stress this enough but i2c is absolutely nothing like serial uart.

This is more like it...


#include <Wire.h> 

int _myi2c = 10;
int _pos = 0;
String _toSend = "Hello World";

void setup() {

  Serial.begin(57600); // Debug Console
  
  Serial.print("My i2c address: " );
  Serial.println(_myi2c);
  Wire.begin(_myi2c);   
  Wire.onRequest(receivei2cEvent); 
}

void receivei2cEvent() {

 while (Wire.available()) { 
    char c = Wire.read(); 
  }

  Wire.send(_toSend[_pos]);

  _pos++;

  if (_pos == 11)
    _pos = 0;
}

void loop() {
   
}

PRO
Synthiam
#7  

These links will help a bunch - it's good to know how i2c works, because once you know, it'll make a lot more sense.

  1. In the learn section for the ez-b/iotiny there is an introduction of port types: https://synthiam.com/Tutorials/Lesson/72?courseId=4

  2. The i2c description on the above link provides a link to addressing here: https://synthiam.com/Community/Questions/322

  3. The above link has a More Reading link that points to here: http://www.robot-electronics.co.uk/i2c-tutorial

The link #3 is the best for a technical understanding. But it might be too much to dive into right away. So start with #1, #2 and then #3 :D

When the master (ez-b) sends a request to READ, it sends the register that it wishes to read, and the slave sends one byte. If the master requests to read more than one byte, the master will continue reading with an ACK. Each ACK means "i'm ready for another byte".

Unlike Serial UART, which is what you're attempting to reproduce, i2c doesn't have an input buffer. In fact, there's no way for the slave to SEND data to the Master. The master must request data... one byte at a time.

i2c was created to access memory. Think of an eprom - a master can request to begin reading at a specific address and each consecutive read from that request will increment the memory address by one.

So if the master requests to read 0x20 with a size of 5... The master sends the slave address with a byte that says "i'm going to read from you". The slave then puts the data stored in memory address 0x20 on the wire. The master receives it. The master sends an ACK, meaning "i want another byte". The slave puts the data stored in 0x21 on the wire. The master receives it. The master sends an ACK, meaning "i want another byte". The slave puts the data store din 0x22 on the wire. The master receives it. The master sends an ACK, meaning "I want another byte". etc etc etc etc

PRO
Synthiam
#8  

Lastly, if you're really curious about i2c and communication types, I recommend getting a logic analyzer. I know the guys at Saleae and they're wonderful - their product is magical. I have both the Logic 8 and Logic Pro 16

However, you don't need to spend that much and get a logic 8 because you're most likely only going to monitor a few wires at a time (most likely 2 for your situation). So the affordable Logic 4 ($109 USD) is a good product for you.

What this will do is show the data on the wires. This is the most useful way to understand communication protocols. It's absolutely amazing how much you learn with a logic analyzer. I have reverse engineered dozens of protocols with the logic 8 and logic 16.

With a Logic 4, you will be able to see the communication and it'll make a lot more sense how i2c works. Specifically if you watch one of ez-robot's i2c devices (such as the RGB Eyes).

#9  

Well interesting.... I replaced my code with yours and I get a "H" as the first letter with 10 special characters (see attached) and the wire hangs. I have to disconnect and reconnect IoTiny to resume. If I only request one byte then the "H" is returned, the wire is closed and all is good. Not sure why requesting multiple bytes is freezing things up confused I'll continue to review the links and look into getting an analyzer.

User-inserted image

*** UPDATE ***

Okay much closer! We both had logic issues with our code but here is what I have so far....


#include <Wire.h> 

int _myi2c = 10;
int _pos = 0;
String _toSend = "Hello World";

void setup() {

  Serial.begin(57600); // Debug Console
  
  Serial.print("My i2c address: " );
  Serial.println(_myi2c);
  Wire.begin(_myi2c);   
  Wire.onRequest(receivei2cEvent); 
}

void receivei2cEvent() {
 //_pos = Wire.available();

  while (Wire.available()) { 
    char c = Wire.read();          // Read the bytes from Master (these show up as special characters on the wire if you skip this step)
  }
  for( int i =0; i <  _toSend.length(); i++)
     {  Wire.write(_toSend[i]);   // Loop through to send each character in the msg
      }
  Serial.println("event processed");
}

void loop() {
   
}


Now I get the full msg returned BUT the wire still remains open until I disconnect/reconnect the IoTiny.

PRO
USA
#10  

@msriddle68,

I have a working code from a previous integration, I'll post in 5 minutes.

PRO
USA
#11  

I tested the code with an Iotiny and arduino mini.

Note: Please use the code and do not change anything, and let me know the results.

Arduino code:


#include <Wire.h>

#define CMD_SAVE_BYTES                21
#define CMD_READ_BYTES                22

#define  SLAVE_MEMORY_MAX_READ_SIZE   16
#define  SLAVE_MEMORY_SIZE            256
uint8_t  slave_memory[SLAVE_MEMORY_SIZE]; 
volatile int last_cmd, last_cmd_addr, last_cmd_size;
//#define DEBUG

void setup() 
{
  //slave address
  Wire.begin(8);
  
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent); 
  Serial.begin(115200);
  Serial.println("Ready" ) ;

  memset(&slave_memory, 0, sizeof(slave_memory));
}

void loop() 
{
  delay(2000);
}

void requestEvent() 
{
  switch(last_cmd)
  {
    case CMD_READ_BYTES:
    {
      #ifdef DEBUG
      Serial.print("Request/Read addr=" ) ;
      Serial.print(last_cmd_addr ) ;
      Serial.print(" size=" ) ;
      Serial.print(last_cmd_size ) ;
      #endif
      if (last_cmd_size<=SLAVE_MEMORY_MAX_READ_SIZE && last_cmd_addr+last_cmd_size<SLAVE_MEMORY_SIZE)
      {
        Wire.write(&slave_memory[last_cmd_addr], last_cmd_size);
      }
      else
      {
        #ifdef DEBUG
        Serial.print("**INVALID ARGS**");        
        #endif
      }
      #ifdef DEBUG
      Serial.println();
      #endif
      break;
    }
    default:
    {
      #ifdef DEBUG
      Serial.print("Invalid Request last_cmd=" );
      Serial.print(last_cmd);
      Serial.println();
      #endif
      break;
    }
  }
}

void receiveEvent(int bytes_read) 
{
  if (Wire.available()<1)
  {
    return;
  }
  
  last_cmd = Wire.read();
  
  switch(last_cmd)
  {
    case CMD_SAVE_BYTES:
    {
      int addr = Wire.available()>0 ? Wire.read() : 0;
      #ifdef DEBUG
      Serial.print("Save bytes addr=" );
      Serial.print(addr);
      Serial.print(" size=" );
      Serial.print(bytes_read-2);
      Serial.print(" bytes=[" );
      #endif
      int ix=0;
      while (Wire.available()>0 && addr<SLAVE_MEMORY_SIZE) 
      { 
        int b = Wire.read();
        slave_memory[addr++] = b;
        #ifdef DEBUG
        if (ix++>0) 
        { 
          Serial.print(" "); 
        }
        Serial.print(b);
        #endif
      }
      #ifdef DEBUG
      Serial.println("]" );
      #endif
      break;
    }
    case CMD_READ_BYTES:
    {
      last_cmd_addr = Wire.available()>0 ? Wire.read() : -1;
      last_cmd_size = Wire.available()>0 ? Wire.read() : -1;
      #ifdef DEBUG
      Serial.print("Read bytes addr=" );
      Serial.print(last_cmd_addr);
      Serial.print(" size=");
      Serial.print(last_cmd_size);
      #endif
      if (last_cmd_size<=SLAVE_MEMORY_MAX_READ_SIZE && last_cmd_addr+last_cmd_size<SLAVE_MEMORY_SIZE)
      {
        #ifdef DEBUG
        Serial.print(" OK" );        
        #endif
      }
      else
      {
        #ifdef DEBUG
        Serial.print(" **INVALID ARGS**" );        
        #endif
      }
      #ifdef DEBUG
      Serial.println();
      #endif
      break;
    }
  }
}

EZ-Scripts

Script 1: Write to the slave:


# writes a msg to the slave:
# msg[0] = 21 = CMD_SAVE_BYTES
# msg[1] = Address position
# msg[2..n] = msg content
#
# Note: Some i2c implementations (Wire) have buffer size constrains.
# I'll break the msg content in 2 write operations

# --- part 1 ---
$text="The quick brown fox"
$text_len=length($text)
DefineArray($numeric_array, $text_len+2, 0)
$numeric_array[0]=21
$numeric_array[1]=0
repeat ($ix1, 0, $text_len-1, 1)
  $numeric_array[$ix1+2]=GetByteAt($text,$ix1)
endrepeat
print("Status: Sending addr="+$numeric_array[1]+" textSize="+$text_len)
I2CWriteBinary(0, 0x08, $numeric_array)

# --- part 2 ---
$text=" over the lazy dog"
$text_len=length($text)
DefineArray($numeric_array, $text_len+2, 0)
$numeric_array[0]=21
$numeric_array[1]=$ix1+1
repeat ($ix2, 0, $text_len-1, 1)
  $numeric_array[$ix2+2]=GetByteAt($text,$ix2)
endrepeat
print("Status: Sending addr="+$numeric_array[1]+" textSize="+$text_len)
I2CWriteBinary(0, 0x08, $numeric_array)

Script 2: Read from the slave:


I2CClockSpeed(0, 10000)
$result="" 

#send to slave addr=8 cmd=22(ReadBytes) Addr=29 length=4
I2CWrite(0, 0x08, 22, 29, 4) 
$result=i2cRead(0, 0x08, 4)
print("Read: [" + $result + "]") 

#send to slave addr=8 cmd=22(ReadBytes) Addr=16 length=8
I2CWrite(0, 0x08, 22, 16, 8) 
$result=i2cRead(0, 0x08, 8)
print("Read: [" + $result + "]") 

Steps:

  1. Flash the arduino code
  2. ARC: Run script1 then script2

expected results: User-inserted image

PRO
Synthiam
#12  

Ah! Receive and request interrupt events with an event type variable. Now that's how it should be done! I didn't see any of that in the documentation for arduino when I googled. Super awesome it exists and you know about it - because I was wondering how it would magically exist with only one interrupt and no event type.

Thanks for helping! We'll have to turn this into a tutorial

PRO
USA
#13  

@DJ,

Correct, let's wait to see if it works for the OP. I would like to share more details.

Like you said before I2C is not a serial protocol and is very sensitive.

I2C protocol is inherently half-duplex, basically the master controls the clock and sends the first byte (7 addr bit) with 1 bit (read/write). So the slave obey promptly to the master and performs two different and isolated operations: receiving and sending data.

PRO
Synthiam
#14  

Why do I see people use delay() in the arduino loop when there's no code? I see it everywhere but I don't know why it's there - it creates extra processing, and the stack nests. So when the interrupts raise, the stack has to be backed up and stopping the demay() anyway. Which slows down the interrupt from starting.

PRO
USA
#15  

@DJ,

There is no need/excuse for a delay an empty loop function is enough for this particular example.

The original code before the cut, is a man-in-middle implementation to debug a micro-controller firmware. So i used a micro-controller with my code to simulate the same behavior and every 2 seconds sends a serial debug message. So the delay is left overs :)

BUT

I was lazy... the correct way is to measure time deltas and execute when the condition is true.

#16  

Awesome guys! This sample works and releases the wire correctly. I can break it down to figure out my implementation. I really appreciate all the support!:):) :)

PRO
USA
#17  

@msriddle68,

Arduino hides some code complexity, and sometimes creates an illusion of event programming, simple call back functions, nice code abstraction etc.

BUT

you can't forget you are not coding for the PC, although it seems stupid simple, the things can break easily.

All details are important, so i'll share some details:

  1. Interrupt/Callback functions use the KISS rule, keep it simple, objective, pragmatic. 1.1) Don't use delays or other similar mechanisms, you need to hurry up. 1.2) Avoid Serial.Print code. If you need for debug purposes check if is not slowing you down, even if is working remove it from the production/final code.

=============================== 2) I2C

2.1) The protocol is half duplex expect read/write individual operations

2.2) Arduino and EZRobot makes your life easy, but under the hood there are some limits, for example there are buffers and the buffers have fixed limits. So if you want to send 100 bytes or 1000 bytes you need first check if EZ-Robot firmware has a limitation and secondly if your Arduino code uses a library (e.g. Wire) with limits.

2.3) CLOCK

2.3.1) The master controls the clock, the default ARC frequency is 100K. High frequency means faster bus. You can change adjust the clock speed via project settings or ez-script.

2.3.1) If you noticed my code slows down to 10K on IoTinty, but i got it working with 50K on the EZB, so the question is why not 50k or 100K.

2.3.1.1) You have an Arduino mini with atmel chip, 5 Volts version is 16MHZ, 3.3v is 8 Mhz.So running your code in Atmel 8MHZ half speed is not the same for example running in a Teensyduino (ARM Cortex M4) 3.3.v, Arduino Cortex M0 different micro-controllers same framework "Arduino".

2.3.1.2) Iotiny 10K frequency vs EZB 50K. Iotinty is a STM32F4 (Cortex M4) 100 Mhz clock EZB is a STM32F205 (Cortex M3) 120 Mhz clock.

edited

ARM instructions are compatible

I don't know if the firmware code is relevant or not, DJ's mentioned the Iotiny has a RTOS, and i believe uses a vendor specific framework. Although iotiny has a more powerful core (M4), EZB has a faster clock.

Something makes the difference.

2.3.2) The I2C implementation allows a slave to slow down the I2C bus to allow some extra processing that process is called "clock stretching".

Arduino Wire library does that while you are in the call back function, some sensors (IMUs) do that too, but not all the masters will support clock stretching.

I believe (I'm not 100% sure) EZB/Iotiny does not support clock stretching, so you need to answer faster as you can otherwise you lose the BUS.

Supporting clock stretching does not mean waiting milliseconds, we are talking microseconds, for example the Arduino Wire library has a timeout limit (wait for the slave) when Arduino is playing the master.

Even in those scenarios you need to increase the timeout, or change the slave implementation.

Some people will say this work, others will blame the hardware, but the success relies on a good hardware/software marriage and attention to the tiny details.