I2C synchronous comm

Hi,

I got a problem. I am using I2C to communicate between my ESP8266 master, and several I2C slaves based on Arduino.

I am somehow stuck how can I make a blocking parametrized call to get me some data from slave.

In my ESP lib, I have a class MyClass method uint8_t getSomething(param1, param2). This should send both parameters to slave. The slave would somehow make response (read pin values, do some calc) and send me a variable length (5 or 6 byte command) in response.

There might be several MyClass instances which would communicate with different clients in same time.

I could probably go into while(!Wire.available) {} and just block, but its not safe in case a physical wire is severed.

Would putting a yield into the while block allow code to go on executing (meaning other classes might do their requests), or only system stuff would run?

Would Wire.available trigger also if stuff from other clients comes in? (i.E. I am communicating with client Address 0x88 in my class, and data from client 0x90 comes in?)

The part that the client somehow sends data is too vague.
Data does not come in and the Slave does not send data in a normal situation.
The Wire.available() can not be used on its own to check if someone has send something, it is not the same as Serial.available().

The ESP8266 runs at 3.3V and perhaps you use 5V Arduino boards. How did you connect the I2C bus ? Did you solve the voltage mismatch for SDA and SCL ?

If you don't use multitasking on the ESP8266, then all the Arduino code is run as a single task. There will be no trouble with multiple calls or multiple answers.

When the Arduino boards (Slaves) are allowed to send data on their own, then you have a Multi-Master bus. That will guaranteed go wrong. You can not do that. Never. For the Arduino it is only a theorical possibility that no one has implemented. I'm also not sure that the ESP8266 can be a Slave (to receive data).

Is it possible to do the calculation in the ESP8266 ? That is faster and easier.

Solution:
The ESP8266 should be the Master at all times. It can send data to Slaves and request data from Slaves.
If the Slave has the data ready after 1 ms, then you can wait in the Master.
If you don't know when the Slave has data ready, then you have a problem. You could add some kind of interrupt signal to let the Slaves tell the Master that the data is ready. It is also possible that the Master polls the Slave to see who has data.
If you want a quick respons then you have to solve that in the Slave.

The requestEvent() and receiveEvent() functions in a Arduino Slave are interrupt routines. You may not use large calculations or Serial functions or waiting loops or delay() in it.

The Master can send data with: beginTransmission - write - endTransmission
for sending data up to 32 byte (for an Arduino Uno).

The Master can request data with: requestFrom
After the Wire.requestFrom(), then the functions Wire.available() and Wire.read() can be used.

I know that you had a good idea with the common class that you wanted to automatically get data for more than one sensor, but this is how I2C works.

Thanks! Thats about the solution I implemented now, spent most morning to do this. More or less I am doing an arduino as port extender library for my IOT stuff. I know several exist, but I did not like any :wink:

Master is the ESP board. In case data are to be sent to slave, I am now first sending data, and then immediately request data. The client prepares the answer in the onReceive, and sends them out in onRequest.

Works well with a single master and a single slave now, I would test that with more then one.

The reason behind doing this is for model railway - I a station for my carpet railway made of tiny segments, ESP is sitting in the middle communicating with the automated train control (Roco Z21 and Rocrail). Each segment which has something useful in it (like signals, servos for track switches, track occupancy detectors) is to be steered over I2C/5V bus which is wired through each segment. A station head segment which branches the track into different station tracks could have 5-6 track switches, several 3 LED signals, and several track occupancy detectors, for a few dozen of pins, both IN and OUT. Most regular port expanders have only 16 IO pins which is not enough, a MEGA has 64 or so, and STM32 Nucleo 144 has 144... And a station has 2 such heads plus maybe a switch or two in the middle, a pair of controlled LEDs in the station building, etc.

Placed my solution on a github GitHub - PetrOsipov/ArduinoPortExtender: Use an Arduino as a port extender for ESP8266. Should also work with any kind of ESP8266, ESP32, STM32, ATTiny or ATMega boards.

When the answer is already waiting, then you can do a request directly after sending the command.
The Master determines which Slave is selected, either for sending a command or requesting data. You can mix everything and even get data from a sensor in between and it will be allright.

The code in the receiveEvent() is okay. I think the Arduino rushes fast enough through that. But you have to remove the Serial.println() from the processMode() function.

The I2C bus is a "bus", however it is not designed to go through a cable or long wires. A rule of thumb is a maximum of 50 cm total length. The crosstalk between SDA and SCL is the worst, so don't put those next to each other in a flat ribbon cable.

Which Arduino boards do you use as a Slave and if they are 5V boards then what have you done to solve the voltage mismatch for SDA and SCL. What are your pullup resistors and where are they ?

You have added a checkum, but there is something that can go wrong. When the command checksum was wrong, then the Slave did not accept it. The Master requests data anyway, without knowing that the command was not accepted.
It is possible to detect that if you add a identifier to the command (for example a value that increments for every command), then you can check in the requested data if it has the same identifier.

You don't check the error returned by Wire.endTransmission() or if Wire.requestFrom() failed. Your ::digitalRead() and ::analogRead() have no way to tell if there was an error, because the return value is already used for data. What if a Slave is not connected to the I2C bus at all ?

This is an alternative, using 'howMany':

void receiveEvent(int howMany)
{
  if( howMany > 0 && howMany <= 6) {      // also check if not zero (extra safety check)
    uint8_t buf[6];
    Wire.readBytes( buf, howMany);

    if( validateChecksum((uint8_t*) buf, (uint8_t) howMany)) {
      switch( buf[0]) {
        case PIN_WRITE:
          processWrite((uint8_t*) buf, (uint8_t) howMany);
          break;
        case PIN_READ:
          processRead((uint8_t*) buf, (uint8_t) howMany);
          break;
        case PIN_MODE:
          processMode((uint8_t*) buf, (uint8_t) howMany);
          break;
      }
    }
  }
}

The Wire.readBytes() is from the higher level Stream class. You can also use a for-loop up to 'howMany' and do Wire.read().

Thanks alot! Will tackle the issues tomorrow! Definitely worth fixes!

Hi,

Luckily I speak a bit of spanish, so I was able to understand right, I hope.
I2C is a bus, so you just connect all devices SCLs and all devices SCK pins together. As I understood above, only one device can be master in the Arduino Wire implementation. But, it can send data to different addresses. So you could first send data to another Arduino Mega (address, say, 0x20), then to a display (say, 0x30), then to something else in any order.

While I fix other issues now, these two here can be answered instantly.

Koepel:
The I2C bus is a "bus", however it is not designed to go through a cable or long wires. A rule of thumb is a maximum of 50 cm total length. The crosstalk between SDA and SCL is the worst, so don't put those next to each other in a flat ribbon cable.

Thats good to know. Official documentation says that 1 meter is ok, and up to 10 meters would work, if speed is reduced. One of my model railway colleagues actually wired his 6x3m layout with a single I2C net, and has no problem, but it would not go that far. I am making fully cableless stations (fed by LiFePo4 battery) in a lego-style principle, small tiles clipped together. HT5.08 6pin connectors with I2C, 5V/GND and DCC will be used for link between tiles. Middle tile will have the central station computer (NodeMCU) driving 2 arduinos sitting below the tiles with track switches, a pair of displays on I2C (digital train destination), sound module for ambient sound, lighting, etc. As my tiles are 20x33 cm, and max. 2 tile borders are to be bridged, I would be at around 50 cm to the most distant I2C device, and surely under 1 meter. I would place the I2C wires on the outermost pins of the ribbon cable.

Koepel:
Which Arduino boards do you use as a Slave and if they are 5V boards then what have you done to solve the voltage mismatch for SDA and SCL. What are your pullup resistors and where are they ?

I directly wired them, pin to pin, no resistors or so. Both Wemos and Nodemcu boards I have had no problems with that, also confirmed several times even by developers of ESP chip as written here Is ESP8266 I/O really 5V tolerant? - Digital Me

I used several Arduino clones from Aliexpress for my tests, Nano, Uno (I own at least 5 different Uno clone variants from Aliexpress, with different layout of board, one I used now is with a full size USB Type B connector, SMD Mega328P, no LED on any pin, and no power led, at least 3 years old), and a MEGA2560. I tried to compile the code for ESP8266 as after reopening the environment the selected board was wrong :wink:

When you mention that official documenation says that 1 meter is okay, then please give a link to that because I totally do not believe that. There is no such thing as "okay" for a length, it depends on too many things.
The PDF document at the bottom of this page is the official documentation.

I think that you can improve the I2C bus.
The 50 cm is just a rule of thumb, someone claims to get a 100 kHz I2C over 6 meters.
On the other hand, there are many ways that even the 50 cm is not okay.
The 50 cm is the total length, when you connect 10 different Slaves to a Master, then the wires can be 5 cm.

When the SDA and SCL are next to a GND wire, then they have a extra capacitance to GND. That is no problem, lower the clock of the Master and it will work.

The crosstalk between SDA and SCL can not be fixed by lowering the clock speed.

When you put SDA and SCL at the sides of a flat ribbon cable, then they receive the most electrical noise from relais, sparks, motors and so on.

You must use pullup resistors. They should not be too high and not too low in value. The sink current (to pull the SDA or SCL low) should not exceed 3 mA.
The pictures in the tutorial of Nick Gammon says it all: Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino.

The ESP8266 is "semi" 5V tolerant :wink: so it can read the 5V I2C bus from the Arduino.
That means you can even add pullup resistors to 5V to help the signal for the Arduino. That is a dirty trick, because when you add a sensor, that sensor might get damaged. Also when you replace the ESP8266 with a ESP32 the ESP32 gets damaged.

A 5V Arduino Uno requires 3.5V for a valid high in I2C mode. The 3.3V ESP8266 is below that. That means you are looking for trouble, and you should avoid trouble.

A I2C level shifter connects a 3.3V I2C bus to a 5V I2C bus.

Thanks! You are helping me really much!

I ref to here for cable length calculation I2C Bus Range and Electrical Specifications, Freescale 9S12 HCS12 MC9S12 I2C Hardware

Actually, how do I lower the I2C clock?

I have a variety of cables here, flat ribbon, CAT6, RJ12 capable.. which would be the most optimal one? Or would it be better to use a separate wire pair for I2C, and separate 20AWG shielded cable which would contain the DCC signal (rail track, quite powerful impulses at 20V and currents of several Amperes) and +5/GND?, which only would come close to each other in connectors?

I would employ resistors somewhere between 2.2 and 4.7k, I have to see which I have most :wink: Most likely a pair of either 1.3k in serial, or a pair of 5.1k in parallel, as I got these in thousand bags :wink:

I got a few level shifters here, I would check if they work ok. Generally, I would probably drive the I2C at 5V, and each 3.3V device which is not tolerant like ESP32 would get a level shifter in front of it.

I pushed the error handling fixes to GitHub

That link shows that when the capacitance is know of a cable, and the pullup resistor is known, then it is possible to calculate how long the cable can be for a certain speed. The allowed capacitance is in the official standard. That is not for your situation, because you have a mix of 5V and 3.3V on the I2C bus. Even with a level shifter it is still not for your situation, because a level shifter also weakens the signal a little.

If you have shielded Cat6, then you can use a twisted pair for SDA + GND and another twisted pair for SCL + GND. That will increase the capacitance to GND a lot. I read that others use other combinations with a Cat5 or Cat6 cable.
You can use a flat ribbon cable and put SDA between two GND and also SCL beteen two GND.
With a round 4 wire shielded 20 AWG cable, you could use two opposite wires for SCL and SDA and the others for GND. Then the SDA and SCL are as far away from each other as possible (even if it is less than 1mm, the GND wires nearby will prevent most crosstalk).

It is of course better to keep wires with high current away from it.

Do you feed the Arduino board with 5V to the 5V pin ? It is a output pin, not a input. It is possible to blow the voltage regulator with reverse current when you apply a super strong 5V suddenly to the 5V pin. With a normal 5V adapter and normal wires, it is possible to power an Arduino board through its 5V. I use that myself sometimes. That 5V could flow into the computer when the computer is connected with a USB cable. So far, my computers are still working.
When using the 4 wire AWG cable, you could replace one GND with 5V.

The Wire.setClock() can set the speed: Wire - Arduino Reference.

The ESP8266 runs at 3.3V and has a 3.3V I2C bus. That 5V tolerance on the pins does not make it a 5V I2C bus. When you add a level shifter, connect the ESP8266 to the 3.3V I2C bus.

I prefer to check if I have received the same amount as requested:

int rcvcount = Wire.requestFrom(client, 6);
  if (rcvcount != 6) return 0xFFFF; //ERROR

Sometimes you can read that a Slave could send more or less bytes, but that is impossible. When the Master does a Wire.requestFrom(), the Master makes the clock and a acknowledge for each databyte. The Slave can not stop that to send less bytes, neither can the Slave send more bytes.

Thanks!
Right now, both ESP-12E NodeMCU, WeMos D1 R2 and Arduinos are powered from USB.

My final plan is to power everything by 6.6 or 9.9V LiFePo4 batteries (I have a ton of that for my RC ships). For supply distribution I have considered several options. I would also place a socket for normal wall outlet power supply (9 or 12V, 3A or similar)

Variant 1: I considered to let the ESP board to draw over the Vin and GND directly from the battery, and use a stepdown module to supply 5V to the module bus. Minus is - onboard the NodeMCU it is a NCP1117 - linear regulator which is not best solution.

Variant 2: Use a 3.3V stepdown for ESP, 5V stepdowns for bus

Variant 3: Run Vin through the bus instead of 5V, and supply each board or electronics piece by its own switched stepdown

My main interest is the I2C bus and millis(), but I can tell what I know.
When using VIN or the barrel jack for Arduino Uno, then I prefer to use 7.5V. It should not be too low, and a higher voltage will only make the voltage regulator warmer.
Sometimes I feed 5V power to the 5V pin as I wrote, despite the disadvantages.
The ESP8266 needs peaks of 250mA, so I run them at 5V.

Some things require a lot of current. For example RGB leds or led strips. A small servo motor can require a peak current of 500mA (and that is for a small servo motor).
A relais module that uses 5V for the relais also requires some current for all the relais coils.
A relais module that uses 12V for the relais but has optocouplers might need 10mA or 20mA per channel for the leds in the optocouplers. That can add up if you have a module of 16 channels and that is also current from the 5V.
If you want to power those with 5V, then you need to use a DC/DC-converter close to them and run a higher voltage through a cable.

When the Arduino boards don't need a lot of current, then you can run everything from a single 5V DC/DC-converter.

Do those battery packs have protection against deep discharge ? Do you charge them all two or three cells in series or individual ?
I'm making a project with a Raspberry Pi and Arduino, but I need a higher voltage to get enough power into speakers. I want to add a battery so I can listen to the local radio when the mains power fails.

Hi,

Thanks! I will see what I can do there with DC. Would 6.6 V be enough to feed an Arduino through the supply?

I think driving the battery current through the bus and individual DC stepdowns is the right solution then. I have some smaller ones like 2A micro parts, but I also have some which can deliver 15A at 5 V

I use LiFePo4 batteries, mine are all from HobbyKing. These are charged as one with a balance charger. LiFePo4 ones are much safer then normal LiPos in terms of fire, but weight slightly more for same capacity and slightly less voltage. They are not protected from over-discharge, so you have to monitor. In my RC ships I have an arduino with a voltage divider on analog pin which reports over IBUS to my controls. In this thing, I plan to take Arduino Nano (or use the ESP8266 if have capacity to do that there), also let it monitor voltage, and use a spare phone speaker (I have a ton of them from Aliexpress, to add sound to my railroad trains, good ones go in trains, bad ones can be used for a beeper) as a beeper if its under 6.6V. LiFePos have a very good discharge characteristics for that - nearly horizontal at 3.3V pro cell, then dropping very steep.

Thanks. I do not have RC parts, but I will look into it.

The minimum voltage for an Arduino Uno is 7 Volt. Perhaps a cheap clone does not have a low-voltage-drop voltage regulator and needs more than 7V. When the voltage is below 7V, it is possible that the USB power is not turned off and current from the battery can flow into the computer USB port.
The 6.6V (is that 6.4V ?) will probably work, no bad things will happen, but I think 9.9V is better.

Hi,

Sorry for a slow answer :wink:

I could in such case use a DC-DC up/down buck. I have several up/down power modules, they take anything between 3 and 30 V and make stable 3.3, 5, or 9 V out of it (depending on the version). Makes very fine 5V out of 6.6, too.

But yes, 9.9V is probably easier.