I2C Slave Mode

dkaufmann
Posts: 11
Joined: Mon Sep 26, 2016 10:02 pm

Re: I2C Slave Mode

Postby dkaufmann » Tue Nov 22, 2016 3:44 pm

Hi Rudi,

i am very interested in these ASM instruction to run the I2C faster. I'd like to run in high speed mode or at least >100kHz.

is this line to read the inputs? how exactly?
_l8ui a0, a11, GPIO_OFFSET_INPUT

"if i delete the debug prints i can go on to 400 KHz with 160 MHz CPU":
the interrupt delay or the GPIO read is too slow. even without debug prints. how do you read/set the GPIO in assembler? in your code I see you use the "normal" GPIO_INPUT_GET function. this is much too slow even with 160MHz. Do you agree? Can you show me how you do it faster?

Thank you

Dominik

User avatar
rudi
Posts: 197
Joined: Fri Oct 24, 2014 7:55 pm

Re: I2C Slave Mode

Postby rudi » Sat Nov 26, 2016 5:31 am

dkaufmann wrote:Hi Rudi,

i am very interested in these ASM instruction to run the I2C faster. I'd like to run in high speed mode or at least >100kHz.

is this line to read the inputs? how exactly?
_l8ui a0, a11, GPIO_OFFSET_INPUT



hi dominik

ah you use the code from CNLohr :D
yes, asked you, do you know about this in prev postings..


the thing is not bad, but the 17 cycles is too much too.
CNLohr on esp8266.com wrote: I could respond to interrupt requests in .28 us!


then you know about my state to this too

rudi on esp8266.com wrote:with 80MHz it is 12.5 ns
with 160 MHz it is 6.5 ns ( had measuring on time this by 6.2 ns - see picture )

fastest:
instructions: 4

80 MHz : 12.5ns * 4 = 50 ns
160 MHz : 6.5 ns * 4 = 26 ns ( 6.25 * 4 = 25 ns )

optimated
instructions: 8

80 MHz : 12.5ns * 8 = 100 ns
160 MHz : 6.5 ns * 8 = 52 ns ( 6.25 * 8 = 50 ns )

and does a little be more better as your cool thing with .28 us ( 280 ns )

..

state to high speed usb:
-------------------------------

without DMA and Core 160MHz
without drops - tricks
and with asm blocks

custom Instructions: 6.25ns
bit flow for 1 bit data read:
---------------------------

ISR Fired:
fired 6.25
nop 6.25
clear 6.25
nop 6.25
attach 6.25
nop 6.25


Clock:
low 6.25
nop 6.25
high 6.25
nop 6.25



Data read:
get pin 6.25
nop 6.25
shift pin 6.25
nop 6.25

-----------------------------

14 * 6.5 87.5 ns ( full clock )

87.5 ns * 8 = 700 ns

8 Bit = 0.700 us

1 ms = 1000 * 8 / 0.700 = 11428 Bit / ms

1 s 11428 * 1000 = 11 428 000 Bit/s

11 428 000 Bit/s = 11.428 MBit/s






example:
you can work helped with
"for while (..)"

Code: Select all

static inline unsigned get_ccount(void)
{
        unsigned r;
        asm volatile ("rsr %0, ccount" : "=r"(r));
        return r;
}




backgrounds:

-> http://naberius.de/2015/05/14/esp8266-g ... rformance/
-> viewtopic.php?t=979

the interrupt delay or the GPIO read is too slow. even without debug prints. how do you read/set the GPIO in assembler? in your code I see you use the "normal" GPIO_INPUT_GET function. this is much too slow even with 160MHz. Do you agree?


Yes - there are to much instructions

Can you show me how you do it faster?


using DMA and ticks on 6.5 ns

a good example is done by CNLohr with Broadcast TV and driving WS2812

i am just in time on esp32 CAN theme
perhabs will come back again to this i2c slave theme..

best wishes
rudi ;-)

-------------------------------------
love it, change it or leave it.
-------------------------------------
問候飛出去的朋友遍全球魯迪

dkaufmann
Posts: 11
Joined: Mon Sep 26, 2016 10:02 pm

Re: I2C Slave Mode

Postby dkaufmann » Tue Nov 29, 2016 8:54 pm

Hi Rudi,

okay. I still have no idea how to enter the interrupt routine faster. If I enter the ISR() on falling edge SDA and measure the SCL line, SCL has already changed to the next state. It is just not a real "interrupt" for me if it takes that long. DMA is no option for a simple I2C slave.

best regards
Dominik

MikeFair

Re: I2C Slave Mode

Postby MikeFair » Wed Nov 30, 2016 7:54 pm

Dominik,

What I noticed about your code is you were using a full blown C function call to request the pin value instead of the ASM call that the examples were using; that "CALL" and "RTN" will add time.

I'm not sure it will help, but I've read that also using the IRAM_ATTR flag is typically considered good practice for fast/short ISRs.

dkaufmann
Posts: 11
Joined: Mon Sep 26, 2016 10:02 pm

Re: I2C Slave Mode

Postby dkaufmann » Thu Jan 05, 2017 4:36 pm

Hi Mike,

yes that's true. But this is depending on the used SDK if this IRAM_ATTR is necessary. As far as I know the IRAM_ATTR is the default for NONOS SDK. if you use RTOS SDK it must be added.

Divingshrek

Re: I2C Slave Mode

Postby Divingshrek » Mon Feb 06, 2017 9:21 am

Thought you might be interested in this I2C Slave code I wrote for the Arduino IDE. Everything is driven by CHANGE interrupts onthe SDA and SCL pins.
Still have a few bits and pieces to test so consider it a work in progress. My intention was to turn it into a library at some point hence the long public method names beginning with "ESP_I2CSlave". Looking at this forum it would appear I'll need to change the GPIO reads/writes into ASM for speed...but the code is prob too long for an ISR anyway.

Any constructive comments welcome...and any hints how to join the forum too :D

Cheers
Ian


Code: Select all

/* I2C Slave
 * 

 */

// States
// ======
#define IDL 0x01        // IDL - Idle state
#define SLA 0x02        // SLA - Receiving SLA from master
#define ACK 0x04        // ACK - Sending ACK tomaster
#define NAK 0x08        // NAK - Sending NACK to master
#define RAK 0x10        // RAK - Receiving ACK/NACK from master
#define RCD 0x20        // RCD - Receiving data from master
#define SND 0x40        // SND - Sending data to master

// Flags
// =====
#define STOP 0x01       // STOP - STOP signal detected
#define STRT 0x02       // STRT - START signal detected
#define SLAW 0x04       // SLAW - SLA+W matched our slave address
#define SLAR 0x08       // SLAR - SLA+R matched our slave address
#define BFUL 0x10       // BFUL - I2C buffer full
#define BEMP 0x20       // BEMP - I2C buffer empty

// Pin Defs
unsigned int SCL_pin=4;       // SCL pin GPIO
unsigned int SDA_pin=5;       // SCL pin GPIO

// macros to avoid using digital write and read (speed up as everything is interrupt driven)
#define SCL_LOW GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS,1<< SCL_pin)
#define SCL_HIGH GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS,1<< SCL_pin)
#define SDA_LOW GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS,1<< SDA_pin)
#define SDA_HIGH GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS,1<< SDA_pin)
#define READ_SCL GPIO_REG_READ(GPIO_IN_ADDRESS)&(1<<SCL_pin)
#define READ_SDA GPIO_REG_READ(GPIO_IN_ADDRESS)&(1<<SDA_pin)

// buffer defs
#define BUFF_ERR 0xffffffff       // error code to return if trying to read from an empty buffer or write to full buffer
#define I2CBUFFLEN 32            // buffer size
#define BUFF_READ 0x01            // read buffer item
#define BUFF_WRITE 0x02           // write to buffer
#define BUFF_AVAIL 0x03           // how many items in the buffer
#define BUFF_FLUSH 0x04           // flush the buffer

// misc defs
#define FUNC_RST 1                // used to reset static vars in send/receive functions
#define FUNC_NOT_RST 0            // opposite of FUNC_RST

// global vars (native 32 bit and unsigned for speed)
volatile unsigned int Flags=STOP, State=IDL;
void (*user_onRequest)(void);
void (*user_onReceive)(int);
volatile unsigned int _I2CAddress=0x3f;


void setup() {
}

void loop() {
}

////////////////////////////////////////////////////////////////////////
//
// PRIVATE METHODS
//
////////////////////////////////////////////////////////////////////////

unsigned int PRIV_I2CBuffer(unsigned int _action, unsigned int &_val)
{
  volatile static unsigned int _BuffHead=0, _BuffTail=0, _Buffer[I2CBUFFLEN];

  switch (_action) {
    case BUFF_READ:
      if (Flags & BEMP) return false;
      _val = _Buffer[_BuffTail];
      _BuffTail++;
      if (_BuffTail >= I2CBUFFLEN) _BuffTail = 0;
      if (_BuffTail == _BuffHead) Flags |= BEMP;
      else Flags &= ~BEMP;
      Flags &= ~BFUL;
      break;
    case BUFF_WRITE:
      if (Flags & BFUL) return false;
      _Buffer[_BuffHead] = _val;
      _BuffHead++;
      if (_BuffHead >= I2CBUFFLEN) _BuffHead = 0;
      if (_BuffHead == _BuffTail) Flags |= BFUL;
      else Flags &= ~BFUL;
      Flags &= ~BEMP;
      break;
    case BUFF_AVAIL:
       if (Flags & BFUL) _val = I2CBUFFLEN;
       else _val = (_BuffHead >= _BuffTail)?_BuffHead - _BuffTail:I2CBUFFLEN - _BuffTail + _BuffHead;
      break; 
    case BUFF_FLUSH:
      _BuffHead = 0;
      _BuffTail = 0;
      Flags |= BEMP;
      Flags &= ~BFUL;
      break;
  }
  return true;
}

void onReceiveService(int numBytes)
{
  // don't bother if user hasn't registered a callback
  if(!user_onReceive) return;
  // alert user program
  user_onReceive(numBytes);
}

void onRequestService(void){
  // // don't bother if user hasn't registered a callback
  if(!user_onRequest) return;
  // alert user program
  user_onRequest();
}

void PRIV_RAKhandler()
{
  // nice and easy if we get ACK send more data otherwise stop
  if (READ_SDA) PRIV_STOPhandler();
  else State = SND;
}

void PRIV_NAKhandler()
{
  // nice and simple if we NACK the next response should
  // be stop or start both of which will reset the state
  SDA_HIGH;
}

void PRIV_ACKhandler()
{
  volatile static unsigned int _firstpass = 1;
  if (_firstpass) {
    SDA_LOW;
    _firstpass = 0;
  }
  else {
    SDA_HIGH;
    _firstpass = 1;
    if (Flags & SLAW) State = RCD;
    else {
      State = SND;
      PRIV_SNDhandler(FUNC_NOT_RST);
    }
  }
}

void PRIV_RCDhandler(unsigned int _reset)
{
  volatile static unsigned int _bitcount=0, _i2cbyte=0, _firstpass=1;
  unsigned int _val;
  if (_reset) {
    _firstpass = 1;
    _bitcount = 0;
    _i2cbyte = 0;
    return;
  }
  if (_firstpass) {
    _firstpass = 0;
    _bitcount = 0;
    _i2cbyte = 0;
  }
  _i2cbyte <<= 1;
  if (READ_SDA) _i2cbyte |= 1;
  _bitcount++;
  if (_bitcount > 7) {
    _firstpass = 1;
    _val = _i2cbyte;
    if (!PRIV_I2CBuffer(BUFF_WRITE, _val)) State = NAK;
    else {
      if (Flags & BFUL) State = NAK;
      else State = ACK;
    }
  }
}

void PRIV_SLAhandler(unsigned int _reset)
{
  volatile static unsigned int _bitcount=0, _i2cbyte, _firstpass = 1;
    if (_reset) {
    _firstpass = 1;
    _bitcount = 0;
    _i2cbyte = 0;
    return;
  }
  if (_firstpass) {
    _firstpass =0;
    _bitcount = 0;
    _i2cbyte = 0;
  }
  _i2cbyte <<= 1;
  if (READ_SDA) _i2cbyte |= 1;
  _bitcount++;
  if (_bitcount > 7) {
    _firstpass = 1;
     if ((_i2cbyte >> 1) == _I2CAddress) {
      if (_i2cbyte & 0x01) {
        Flags |= SLAR;
        onRequestService();
        if (Flags & BEMP) State = NAK;
        else State = ACK;
      }
      else Flags|= SLAW;
      if (Flags & BFUL) State = NAK;
      else State = ACK;
    }
  }
}

void PRIV_SNDhandler(unsigned int _reset)
{
  volatile static unsigned int _bitcount=0, _i2cbyte, _releaseSDA=0, _firstpass = 1;
  unsigned int _val;
    if (_reset) {
    _firstpass = 1;
    _bitcount = 0;
    return;
  }
  if (_releaseSDA) {
    SDA_HIGH;
    State = RAK;
    _releaseSDA = 0;
    _firstpass = 1;
    return;
  }
  if (!_firstpass) {
    PRIV_I2CBuffer(BUFF_READ, _val);
    _i2cbyte = _val;
    _bitcount = 0;
    _firstpass = 0;
  }
  if (_i2cbyte & 1) SDA_HIGH;
  else SDA_LOW;
  _i2cbyte >>= 1;
  _bitcount++;
  if (_bitcount > 7) {
     _releaseSDA = 1;
  }
}

void PRIV_STRThandler()
{
  unsigned int _retval, _val;
  if (Flags & SLAW) {
    PRIV_I2CBuffer(BUFF_AVAIL, _val);
    if (_val) onReceiveService(_val);
  }
  if (Flags & SLAR) PRIV_I2CBuffer(BUFF_FLUSH, _val);
  Flags &= 0xfffffff0;
  Flags |= STRT;
  State = SLA;
}

void PRIV_STOPhandler()
{
  unsigned int _retval, _val;
  if (Flags & SLAW) {
    PRIV_I2CBuffer(BUFF_AVAIL, _val);
    if (_val) onReceiveService(_val);
    PRIV_RCDhandler(FUNC_RST);
    PRIV_SNDhandler(FUNC_RST);
    PRIV_SLAhandler(FUNC_RST);
  }
  if (Flags & SLAR) PRIV_I2CBuffer(BUFF_FLUSH, _val);
  Flags &= 0xfffffff0;
  Flags |= STOP;
  State = IDL;
}

void PRIV_SCLChangeISR()
{
  if (State == IDL) return;

  if (READ_SCL) {            //read on rising edge
    switch (State) {
      case RAK:
        // receiving ACK from master
        PRIV_RAKhandler();
        break;
      case RCD:
        // receiving data from master
        PRIV_RCDhandler(FUNC_NOT_RST);
        break;
      case SLA:
        // receiving SLA+R/W from master
        PRIV_SLAhandler(FUNC_NOT_RST);
        break;
    }
  }
  else {                    // Sending data to master
    SCL_LOW;                // Apply Clock Stretch
    switch (State) {
      case ACK:
        // send ack to master
        PRIV_ACKhandler();
        break;
      case NAK:
        // send nack to master
        PRIV_NAKhandler();
        break;
      case SND:
        // send data to master
        PRIV_SNDhandler(FUNC_NOT_RST);
        break;
    }
    SCL_HIGH;              // Release Clock Stretch
  }
}

void PRIV_SDAChangeISR()
{
  if (READ_SCL) {
    if (READ_SDA) PRIV_STOPhandler();
    else PRIV_STRThandler();
  }
}

////////////////////////////////////////////////////////////////////////////////
//
//   PUBLIC METHODS
//
///////////////////////////////////////////////////////////////////////////////

unsigned int ESP_I2CSlavewrite(unsigned int _val)
{
  unsigned int _retval;

  if (Flags & BFUL) return BUFF_ERR;
  _retval = PRIV_I2CBuffer(BUFF_WRITE, _val);
  return (_retval)?0:BUFF_ERR;
}

unsigned int ESP_I2CSlaveread()
{
  unsigned int _retval, _val;
  if (Flags & BEMP) return BUFF_ERR;
  _retval = PRIV_I2CBuffer(BUFF_READ, _val);
  return (_retval)?_val:BUFF_ERR;
}

void ESP_I2CSlaveflush()
{
  unsigned int _retval,  _val;
  PRIV_I2CBuffer(BUFF_FLUSH, _val);
}

unsigned int ESP_I2CSlaveavailable()
{
  unsigned int _retval, _val;
  _retval = PRIV_I2CBuffer(BUFF_AVAIL, _val);
  return _val;
}

void ESP_I2CSlaveonReceive( void (*function)(int) ){
  user_onReceive = function;
}

void ESP_I2CSlaveonRequest( void (*function)(void) ){
  user_onRequest = function;
}

void ESP_I2CSlavebegin(int scl_pin, int sda_pin, byte address)
{
  _I2CAddress = address & 0x7f;
  SCL_pin = scl_pin & 0xf;
  SDA_pin = sda_pin & 0xf;
  pinMode(SCL_pin, OUTPUT_OPEN_DRAIN);
  pinMode(SDA_pin, OUTPUT_OPEN_DRAIN);
  SCL_HIGH;
  SDA_HIGH;
  attachInterrupt(digitalPinToInterrupt(SCL_pin), PRIV_SCLChangeISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(SDA_pin), PRIV_SDAChangeISR, CHANGE);
}


bjoham

Re: I2C Slave Mode

Postby bjoham » Mon Feb 13, 2017 2:33 pm


biccius
Posts: 1
Joined: Wed May 17, 2017 5:07 am

Re: I2C Slave Mode

Postby biccius » Sat May 20, 2017 10:01 pm

I'm trying to communicate from a Arduino Pro Mini (3.3V, Atmega328 8Mhz) to ESP8266 (nodemcu) using the code by Divingshrek but i can't get any data transfer.

What's wrong?

connections:
ESP8266 <--> GPIO 4 (D2) SCL <==> SCL (A4) <--> PRO-MINI
ESP8266 <--> GPIO 5 (D1) SDA <==> SDA (A5) <--> PRO-MINI
pull-up 5K to VCC


MASTER (Arduino Pro Mini)

Code: Select all

#include <Wire.h>

int x = 0;
void setup() {

 Serial.begin(9600);
  Wire.begin(8); 
}

void loop()
{
     
  Wire.beginTransmission(8);
  Wire.write(x);
  Wire.endTransmission();
  Serial.println(x);
  x++;
  delay(100);
 
}



SLAVE (ESP8266)

Code: Select all


... code above...

unsigned int SCL_pin=4;       // SCL pin GPIO
unsigned int SDA_pin=5;       // SCL pin GPIO


int address = 8;

void receiveEvent(int howMany)
{
  Serial.println("receiveEvent()");
  while (1 < ESP_I2CSlaveavailable())
  {
    char c = ESP_I2CSlaveread();
    Serial.print(c);       
  }
}

void requestEvent()
{
  Serial.println("requestEvent()");
}


void setup()
{
    ESP_I2CSlavebegin(SCL_pin,SDA_pin,address);
    ESP_I2CSlaveonRequest(requestEvent);
    ESP_I2CSlaveonReceive(receiveEvent);
    Serial.begin(9600);
}

void loop()
{
  delay(100);
}


Driftmonster
Posts: 1
Joined: Thu Mar 08, 2018 8:56 am

Re: I2C Slave Mode

Postby Driftmonster » Fri Mar 09, 2018 8:10 am

Hello rudi and dkaufmann,

i already read the whole thread and i am on my way to write my own I2C Slave library, mine is interrupt driven,too. I can reach up to 100 kHz, but i want to reach Fast Mode (400 kHz). My biggest problem is, that my Interrupts get triggeres waaayy too slow. I am also reaching 2 µs (with 160 MHz CPU Speed) until i am able to write a GPIO, which is too slow to repsond fast enough and i would prefer running the ESP8266 at 80 MHz again.

So my question is: Is there meanwhile any faster way to run my ISR?

On Github i found the very intersting part from CNLohr, where he uses ASM instructions, but i am not able to understand and adopt these to my needs. I would basically like to use my current ISR and simply jump there from the ASM instructions, that runs as soon as the Interrupt flag is set.
https://github.com/cnlohr/espusb/blob/master/user/usb_asm_1bit.S

I hope my english is understandable, since i am from germany ;)

RobertDot
Posts: 1
Joined: Thu Jul 05, 2018 3:32 pm

I2C Slave Mode

Postby RobertDot » Thu Jul 05, 2018 3:55 pm

Please fix this bug, it’s important for my setup that Trig 2 works in Midi Slave Mode.

Who is online

Users browsing this forum: No registered users and 1 guest