Faster ESP I2C

Experimenting with I2c on the ESP8266? I am – and I’m having a great time with it. If you’re using my code you don’t really need to think about it but if you’re hunting around for better I2c for the ESP – or maybe interfacing the ESP8266 to Arduino – then you’ll love the stuff I’m doing right now – and yes, I AM looking for I2c experts to tell me if I’m doing something wrong because it is all going rather too smoothly.

I decided to take the end from the info from the last blog entry and separate it off as I learn more about I2c (which to recall is a simple 2-wire multi-drop serial communication protocol with separate clock and data).

Why read this? Well, up to now because I spotted a howling issue in the WIRE library for Arduino, found an improvement on the ESP8266 SDK I2C code in the ESPRUINO changes to the Espressif I2c code - I think I found a mistake in it, fixed it then SAILED past the original speeds, reducing a 15ms package best case to well under 3ms  while adding clock stretching into the bargain.  I should clarify at this point that I am NOT an expert in I2C but hey – I can’t get this to fail so I might be onto something! Read on!

So firstly let’s recall -  I’ve been trying to make an Arduino peripheral for the ESP8266 Home Control 2016 project.  I started with the WIRE library in Arduino, working on a small NANO device. I discovered that there are two buffers in WIRE that are 32 bytes in length and hence limit packages to less than that  - I replaced them with a couple of 128 byte buffers and solved lots of issues I’d been having – that is detailed in the last blog.

Where we left off, I’d made a test setup to send a message to the newly-created NANO peripheral which right now on my bench handles a QTECH display and some IO. The ESP8266 I2C is also talking to a BME280 temperature/pressure/humidity sensor and a PWM expander board – so there are 3 devices – some input, some output.

In the last blog entry I’d added a test command for the ESP which could be sent to the board serially or by MQTT and went like this…

{nano:9,7,"how are you today and the weather is nice - NO REALLY it certainly is.",0}

Simple enough – send a message to device 9 (my chosen default address for the NANO peripheral), command 7, a string – and a dummy 0 on the end to tell the device not to bother sending any return info.

As you can see below, this operation takes, using the standard Espressif Ic2 Master code, 15ms in total – not stunningly fast but ok.i2c_thumb2

Flush with the success of finding that buffer issue, I decided to go into the ESP I2C library and have a tinker – changing all the delays into ‘'#defines so I could mess with them. So without any real effort I could reduce delays from 14ms to 6ms – a very worthwhile improvement. bear in mind however that the ESP code has no clock-stretching and hence no way for the peripheral to slow things down. I am told that the call-back routine within WIRE holds the clock down until it is finished, in order to slow things down – this would do nothing but cause havoc for the Espressif code.

Below is a read operation which I got to work properly after reading  this video – as I had no idea what a re-start was – see the little green circle just after the third byte.

read_thumb2

As you can see in the image above I address device 9 (18 as it is shifted left by one – i.e. 7 bit addresses but the bottom bit is reserved to indicate a read or write) – I fire command 2 to read port 4 – then with an I2s restart (start without a preceding stop), I  resend to the device with the LSB set (ie 19) and get a value 0 back (from a pin)  All in a matter of a millisecond.

I was at this point getting worried about lack of clock-stretching – in which a slave can hold the clock low to “stretch” things out a bit – about the only control the slave has!

It looked as if this forum might have some answers as their modification of the ESPRESSIF code certainly had clock stretching in it. It involved some changes and I decided to pick the best bits from their modification.

So they’ve simplified the code a little – while adding in clock-stretching – by replacing a simple delay with a check for clock low – easy enough when you look at it… but they also seemed to have completely messed up master_start – at least, they had it the opposite way to Espressif – and accordingly, repeated starts – as I use in getting data out of a Nano above – simply failed. The logic probe said this was all wrong.

So I reversed master_start back to the way Espressif have it  –so I ended up with a mix of the old and new – and the result – well, up to now everything works – and that long 15ms string send was now reduced to under 5ms from 15ms.

5ms string_thumb[2]

I tested the code with the PWM chip I’ve been playing with and the BME280 – so that is both reading and writing and up to now both were working perfectly. 

It was notable that the clock low period was now much longer than the clock high period and I wondered if there is any way to shorten that.  You know when you get deeply into code and all of a sudden it all becomes clear.  I realised that setting and checking ACK signals was a function – with all that entails – I changed that to a macro. I realised that delays in loops were really un-necessary as the loop itself would cause delays.. I took them out.

At this point I had backed everything up expecting my experiments to fail miserably. I tested the new code and everything worked. I got the logic probe out again.

3ms

My original test string at 15ms was now down to 3ms and according to the probe – all is well. I DID get it down to 2.5ms but the probe said I was doing restarts in the middle of the string – I’ll find another way around that one. Current state of the art – 2.9ms

As for reads – my original byte read was taking somewhat over 1ms – it now takes 0.25ms.

fastish read

And of course I’ve concentrated on the read and write routines leaving the inter-byte handshaking pretty much along so maybe there’s another 10% improvement to make here without dipping into ESP assembly language.

https://bitbucket.org/scargill/esp-mqtt-dev/src 

The driver and firmware libraries in the repository above contain the modified Espressif i2c_master code – and also other I2c wrappers for sending packages – they are fine – any further optimisation needs a good solid look at the one file (and it’s header) for i2c_master.   Open to ideas (that don’t involve converting Arduino assembly language to work with the ESP SDK – been there, failed).

I have just noticed that in the write cycles, there is still more off-time than on for the clock – and so I just took a delay out – which SHOULD mean the off-time is way too slow but because of the time taken to call and process the function – we still end up with 0.9us on time and 1.3us off time – everything continues to work on the tests I have – yet the total time for that read drops from 0.25ms to 0.21ms and the string write time from 2.9ms to about 2.2ms – again – worthwhile improvements.

That simple change was made in the write routine…

void ICACHE_FLASH_ATTR
i2c_master_writeByte(uint8 wrdata)
{
    uint8 dat;
    sint8 i;

    for (i = 7; i >= 0; i--) {
        dat = wrdata >> i;
        I2C_MASTER_SET_DC(dat,0);
      //  i2c_master_wait(I2C_DELAY_5);
        I2C_MASTER_SET_DC(dat,1);
        while (!GPIO_INPUT_GET(GPIO_ID_PIN(I2C_MASTER_SCL_GPIO))) {}
       I2C_MASTER_SET_DC(dat,0);
    }
}

Logic analyser says yes, 3 test items say yes… how low can it go!

Facebooktwittergoogle_pluspinterestlinkedin

22 thoughts on “Faster ESP I2C

  1. Peter, without looking at your code (yet), what I assume is your fixes upped the clock frequency?
    This will depend on the device you communicate with (some I2C devices can handle higher speeds, always mentioned in the datasheet), and on the wire length (clock skew).

    See http://www.i2c-bus.org/speed/

    1. I think Espressif themselves have confirmed there is no hardware I2c pr at least nothing that has ever been implemented using hardware registers - and certainly the software implementation could use any old port bits. Perhaps they intended hardware I2c but then discovered it didn't work when the chips came out??

    2. Good point - well I'm assuming short wires - I would imagine the devices sitting right next to the ESP. Worth mentioning though. Up to now the devices I have seem to be keeping up.. (crossed fingers) including the Nano and that is the one I'd expect to fail first.

  2. One thing I don't like (not your change, original did it too) is that waiting for ack is just looping forever...

    If the other side goes dark (disconnect/crash/...) the code hangs

    I'd add a timeout to prevent this

    1. One COULD put a timeout on both the ack AND the clock stretcher - but that would add a significant time hit..

      I'm looking at the code - where is the holdup on ACK in my code?? Line # ??

      So for clockstretch..
      while ((!GPIO_INPUT_GET(GPIO_ID_PIN(I2C_MASTER_SCL_GPIO)))&&(--timeout)) {}

      But that would add an overhead.

  3. Could anyone offer me some pointers on how to get a global.set nodered value eg target temperature to display on the esp8266 using node red? I've got multiple esp8266's, one is running the excellent firmware provided by Peter, but I'm stumped as to how to get a node red value out to it in the format it expects?

    Thank you

      1. It's one of the 16x2 i2c lcd ones, have it connected to the esp displaying default text, and have alternated between IP address, time, temp, humidity no problem!

        What I'd like it to do, is be able to show for eg

        Set temp: 16
        Actual Temp:19

        1. So it seems to me from what you say that you know how to send text to the display so this is more a programming issue than the display? If you've been able to send time, temperature and other sensor information I don't understand the problem.

    1. My brain is starting to come to life here - this has nothing to do with the blog entry you've put this in - which caught me out.

      So I assume you're referring to this:

      Topic: freddy/toesp

      Payload: {hitachi:39,"$1MQTT test$2$i$3Time $t$4Date $d"}

      It's pretty simple - you're sending a string to the payload .... just make up a string that comprises whatever it is you want to send.

      Of COURSE, you can't put variables into an inject - you'd do that in a function node... for example.

      msg.topic="freddy/toesp";

      msg.payload="The temperature is " + myvar;

      return msg;

      There it is - simples...

      1. Hi Again,

        Hopefully you don't mind a silly question, but clearly I'm doing something wrong -

        My function node will not work with a +, so I assume I'm missing a declaration or similar? It'll only successfully deploy with this

        msg.topic="testnode/toesp";

        msg.payload={hitachi:63,"$s$F$1Set temp is " :"DesiredTemperature"};
        return msg;

        Which I assume is not right, as DesiredTemperature should currently give a figure of 15, instead is blank!

Leave a Reply

Your email address will not be published. Required fields are marked *