I2C does not work after save (Espruino Pico)

Posted on
  • Consider the I2Ctest script below. After a 'send to Espruino' and running 'onInit()' everything works fine. After a 'save()', I2C fails.

    My hardware is OK, I have 2 pullups on B6 and B7 and a single DS3231 on the bus. An oscilloscope does not show any activity on either SDA or SCL, so how can a timeout is detected...

    function init()
    {
      //I2C1.setup({ scl : B6, sda: B7 });
      try {
        I2C1.writeTo(0x68,0);
        var data=I2C1.readFrom(0x68,3);
        console.log(data);
    	LED2.set();
      } catch (error) {
        console.log(error);
    	LED1.set();
      }
    }
    
    function onInit()
    {
      I2C1.setup({ scl : B6, sda: B7 });
      LED1.reset();
      LED2.reset();
      setTimeout(init,2000);
    }
    Gives output :
    
    > |  __|___ ___ ___ _ _|_|___ ___
    |  __|_ -| . |  _| | | |   | . |
    |____|___|  _|_| |___|_|_|_|___|
             |_| espruino.com
     2v09 (c) 2021 G.Williams
    >
    >onInit()
    =undefined
    new Uint8Array([41, 85, 22])
    >save()
    =undefined
    Compacting Flash...
    Calculating Size...
    Writing..
    Compressed 81600 bytes to 3379
    Running onInit()...
    InternalError: InternalError: Timeout on I2C Write BUSY
    > 
    
    
    
    

    The behaviour is the same after power off/power on (with the console.logs removed). What am I doing wrong ?

  • When I use the software I2C (I2C1 in the code above replaced with i2c1 = new I2C() ), everything works fine. This proves my hardware setup is fully operational.

    It is of course not a real solution. When hardware I2C is available, I want to use it and not let the humble CPU consume bit-banging cycles for that...

  • Sat 2021.06.12

    Appears to be a timing issue.

    'After a 'save()', I2C fails'

    Is a full power off, power on cycle being performed?

    There doesn't appear to be any SDA logic high detection after uP reset.

    p.11 https://datasheets.maximintegrated.com/en/ds/DS3231.pdf
    'When the microcontroller resets, the DS3231 I2C interface may be placed into a known state by toggling SCL until SDA is observed to be at a high level. At that point the microcontroller should pull SDA low while SCL is high, generating a START condition.'

  • Check this module http://www.espruino.com/DS3231

  • and corresponding module source http://www.espruino.com/modules/DS3231.js

    Thanx @MaBe missed that tutorial option, . . . always an easier way . . .

  • Thanks for the tips. I went through the DS3231.js source and see nothing fundamentally different as compared to my test script.

    The situation remains at complete power down - power up. This puts the DS3231 in a known state. The 'init()' runs after 2 seconds, eliminating any power upt timing issues.

    I'm quiet experienced with I2C communications and added the usual generate-clock-until-SDA-high, followed by an I2C Stop. This puts any connected I2C device in a bus-free state. The next 'readFrom()' will/should then generate the I2C start for each transfer, does it not ?

    Anyhow, the thing I still do not understand is the lack of pulses on either SDA or SCL on the oscilloscope at B6 and B7.

  • Hmm, please share a link to that DS3231 you use. Got some Pico and some DS3231 and can run later a quick test.

  • I guess it's possible you are hitting issues because B6/B7 are used for the default serial console when USB is disconnected. While I don't think that should really happen, you could try Serial1.setup(9600,{rx:A10}); and see if that helps?

    How easy would it be for you to try using different pins for the hardware I2C and see if that fixes it?

  • OK, I'll look into your suggestions. Will be tomorrow until I can try them though. OK

  • Moved UART1 away from B6/B7. Good point - I overlooked that... Then, did some further testing (full test script source below) :

    
    function init()
    {
      try {
        I2C1.writeTo(0x68,0);
        var data=I2C1.readFrom(0x68,3);
        console.log(data);
    	LED2.set();
      } catch (error) {
        console.log(error);
    	LED1.set();
      }
    }
    
    function freeI2Cbus(clkPin, sdaPin)
    {
      var i;
      while (!sdaPin.read())
      {
    	 clkPin.set(); //about 150 us : OK
    	 clkPin.reset();
    	 clkPin.set();
      }
      clkPin.set();
      sdaPin.set();
      // 9 pulses will end any bus state
      for (i=0; i<10; i++)
      {
    	  clkPin.set();
    	  clkPin.reset();
      }
      sdaPin.reset();
      clkPin.set();
      sdaPin.set(); // 0->1 @ clk 1 : STOP
      clkPin.set(); // delay...
    }
    
    function onInit()
    {
      Serial1.setup(9600,{rx:A10, tx:A9}); // Move away from B6, B7
      freeI2Cbus(B6, B7);
      I2C1.setup({ scl : B6, sda: B7 });
      LED1.reset();
      LED2.reset();
      init();
      //setTimeout(init,2000);
    }
    

    Consider the attached scope waveforms : CH1 = SCL, CH2 = SDA. Top = the 'freeI2Cbus()' waveform, Middle = the same with slower timebase. No signals for the next 500ms (or more, no matter how long I wait). Looks like 'init()' does not generate any signal on SCL or SDA !

    The bottom waveform is the result of using the software 'new I2C()' class : everything perfect, proving the hardware is OK.

    var i2c;
    function onInit()
    {
      Serial1.setup(9600,{rx:A10, tx:A9}); // Move away from B6, B7
      freeI2Cbus(B6, B7);
      i2c = new I2C(); // Use bit bang ...
      i2c.setup({ scl : B6, sda: B7 });
      LED1.reset();
      LED2.reset();
      init();
      //setTimeout(init,2000);
    }
    ...
    
    function init()
    {
      try {
        i2c.writeTo(0x68,0);
        var data=i2c.readFrom(0x68,3);
        console.log(data);
    	LED2.set();
      } catch (error) {
        console.log(error);
    	LED1.set();
      }
    }
    
    

    Hardware is a DS3231 chip, not a module, with a direct connection with a 2x 2cm PCB trace and 2 3k3 pullups. DS3231 gets its power from the 3V3 output of the Pico, everything properly decoupled.

    Moving B6/B7 to B3/B10 is difficult since the design is already using the pins for other stuff... But if that is the only solution...

    All of this testing was done with the Pico connected to the IDE (over USB). So my comment about problems 'after save()' do not hold true anymore I'm afraid.


    1 Attachment

    • i2c1.jpg
  • So you're saying that middle waveform (with just the small spike) is the one from running the onInit code?

    And you have hardware pullups on the I2C bus - if not that can be an issue since I don't think Espruino is able to use the internal pullup on STM32s, and software I2C can.

    I can try checking with an I2C device on B6/B7 here and see if I can reproduce it. What happens if you do:

    function onInit()
    {
      setTimeout(function() {
        I2C1.setup({ scl : B6, sda: B7 });
      }, 1000);
      LED1.reset();
      LED2.reset();
      setTimeout(init,2000);
    }
    

    Just a note on hardware vs software I2C though - when using hardware, the software kicks off the I2C transmission, but then just busy-waits until it's finished - so you're not really saving many CPU cycles by going hardware vs software - it just means the waveform is a bit more regular (and it can be easier to hit 400kHz).

  • The middle waveform is identical to the first, but with a slower timebase. This to illustrate the lack of activity on the bus for a 'long' time.
    My bus has 2x3k3 pullup, more than adequate for a single device at 100kHz.

    Delaying the I2C1.setup() solves the issue. On several power down/up cycles I only had 1 failure. So you can consider this as solved - it works, but does not feel very robust though...

    As for the software I2C : it would of course be nice to extend the I2Cx objects with a 'transfer complete' event. The background operation of the hardware I2C controller could then be started and generate an interrupt when ready. JavaScript can then continue to do other work until I2Cx.on("complete",... ) triggers. But of course you already knew that...

    Thank you for the support.

  • Delaying the I2C1.setup() solves the issue.

    Ok, thanks - that's interesting.... So just to be sure I'm understanding from the traces you've put up:

    • The first slow pulse train is from freeI2Cbus
    • The first picture is with I2C1 - and there's absolutely no activity whatsoever
    • The third picture is software I2C and it all works fine

    How did you save your code - with save()? Or 'to flash' - http://www.espruino.com/Saving

    Either way something is odd with Espruino - I wonder if it's trying to restore pin state, and it's doing so after onInit() is called for some reason. I've filed an issue for this and I'll see if I can reproduce with any old I2C device I have kicking around: https://github.com/espruino/Espruino/issues/2016

    it would of course be nice to extend the I2Cx objects with a 'transfer complete' event

    Yes, absolutely. The vast majority of I2C transactions do tend to be just a few bytes though, so given the execution speed of Espruino you're probably not saving too much time (in most cases - I2C OLED is the one I know is a pain). A quick addition that would have a noticable impact in most cases would actually be just adding a I2c.readFromAddr function that wrote a single byte first.

    From my point of view adding an async SPI function is probably more of a priority, although it's not to say I2C won't get added as well. Once there's more of a framework in place for it, adding to different peripherals (ADC as well) wouldn't be such a big deal.

  • Your understanding of the waveforms is correct.

    I used save() : when I'm done debugging, I (again) download the code from the IDE and immediately issue a 'save()'. I assume that puts the Espruino JavaScript engine in a 'virgin' state without too much debug history left behind.

    I agree with your comment that there may not be much time gained implementing an I2C event (except for large data chunks such as displays). I find it always a challenge to take into account the lower speed of a small CPU compared to the desktop beasts we use to develop our hardware/firmware :).

    A combined 'writeTo - readFrom' might be an easier cycle-saver : an I2C read transaction almost always starts with write-register-address - but that's only something to add to the whish-list...

       i2c.writeTo(i2c_address, register_address);
       var data = i2c.readFrom(i2c_address, count);
    // whish :
      var data = i2c.readFromAddress(i2c_address, register_address, count);
    
  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

I2C does not work after save (Espruino Pico)

Posted by Avatar for jgw @jgw

Actions