I2C Slave Mode
Re: I2C Slave Mode
Postby dkaufmann » Tue Nov 22, 2016 3:44 pm
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
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

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.
-------------------------------------
問候飛出去的朋友遍全球魯迪
Re: I2C Slave Mode
Postby dkaufmann » Tue Nov 29, 2016 8:54 pm
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
Re: I2C Slave Mode
Postby MikeFair » Wed Nov 30, 2016 7:54 pm
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.
Re: I2C Slave Mode
Postby dkaufmann » Thu Jan 05, 2017 4:36 pm
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.
Re: I2C Slave Mode
Postby Divingshrek » Mon Feb 06, 2017 9:21 am
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

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);
}
Re: I2C Slave Mode
Postby bjoham » Mon Feb 13, 2017 2:33 pm
Re: I2C Slave Mode
Postby biccius » Sat May 20, 2017 10:01 pm
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);
}
-
- Posts: 1
- Joined: Thu Mar 08, 2018 8:56 am
Re: I2C Slave Mode
Postby Driftmonster » Fri Mar 09, 2018 8:10 am
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

Who is online
Users browsing this forum: No registered users and 1 guest
Login
Newbies Start Here
Are you new to ESP8266?
Unsure what to do?
Dunno where to start?
Start right here!
Latest SDK
Documentation
Complete listing of the official ESP8266 related documentation release by ESPRESSIF!
Must read here!
- All times are UTC+08:00
- Top
- Delete all board cookies
About Us
Espressif Systems is a fabless semiconductor company providing cutting-edge low power WiFi SoCs and wireless solutions for wireless communications and Internet of Things applications. We are the manufacturer of ESP8266EX.