Skip to content

Commit

Permalink
cpu/esp8266: i2c fix
Browse files Browse the repository at this point in the history
  • Loading branch information
gschorcht committed Oct 8, 2018
1 parent 1e3b3c0 commit 87b0221
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 33 deletions.
3 changes: 2 additions & 1 deletion cpu/esp8266/periph/gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ int gpio_init(gpio_t pin, gpio_mode_t mode)

case GPIO_IN_PU: iomux_conf |= IOMUX_PIN_PULLUP;
iomux_conf |= IOMUX_PIN_PULLUP_SLEEP;
case GPIO_IN: GPIO.ENABLE_OUT_CLEAR = BIT(pin);
case GPIO_IN: GPIO.CONF[pin] |= GPIO_CONF_OPEN_DRAIN;
GPIO.ENABLE_OUT_CLEAR = BIT(pin);
break;

case GPIO_IN_PD: LOG_ERROR("GPIO mode GPIO_IN_PD is not supported.\n");
Expand Down
146 changes: 114 additions & 32 deletions cpu/esp8266/periph/i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,17 @@ static _i2c_bus_t _i2c_bus[] =
#endif
};

/* to ensure that I2C is always optimized with -O2 to use the defined delays */
#pragma GCC optimize ("O2")

static const uint32_t _i2c_delays[][2] =
{
/* values specify one half-period and are only valid for -O2 option */
/* value = [period - 0.5us(160MHz) or 1.0us(80MHz)] * cycles per second / 2 */
/* cycles per us = ca. 20 (80 MHz) / ca. 40 (160 MHz) */
[I2C_SPEED_LOW] = {1990, 990}, /* 10 kbps (period 100 us) */
[I2C_SPEED_NORMAL] = { 190, 90}, /* 100 kbps (period 10 us) */
[I2C_SPEED_FAST] = { 40, 17}, /* 400 kbps (period 2.5 us) */
[I2C_SPEED_LOW] = {1990, 989}, /* 10 kbps (period 100 us) */
[I2C_SPEED_NORMAL] = { 190, 89}, /* 100 kbps (period 10 us) */
[I2C_SPEED_FAST] = { 40, 16}, /* 400 kbps (period 2.5 us) */
[I2C_SPEED_FAST_PLUS] = { 13, 0}, /* 1 Mbps (period 1 us) */
[I2C_SPEED_HIGH] = { 0, 0} /* 3.4 Mbps (period 0.3 us) is not working */
};
Expand All @@ -129,6 +132,7 @@ static int _i2c_write_byte (_i2c_bus_t* bus, uint8_t byte);
static int _i2c_read_byte (_i2c_bus_t* bus, uint8_t* byte, bool ack);
static int _i2c_arbitration_lost (_i2c_bus_t* bus, const char* func);
static void _i2c_abort (_i2c_bus_t* bus, const char* func);
static void _i2c_clear (_i2c_bus_t* bus);

/* implementation of i2c interface */
void i2c_init(i2c_t dev)
Expand Down Expand Up @@ -159,14 +163,31 @@ void i2c_init(i2c_t dev)
DEBUG ("%s: scl=%d sda=%d speed=%d\n", __func__,
_i2c_bus[dev].scl, _i2c_bus[dev].sda, _i2c_bus[dev].speed);

/* configure SDA and SCL pin as GPIO in open-drain mode with enabled pull-ups */
gpio_init (_i2c_bus[dev].scl, GPIO_OD_PU);
gpio_init (_i2c_bus[dev].sda, GPIO_OD_PU);
/*
* Configure and initialize SDA and SCL pin.
* Note: Due to critical timing required by the I2C software
* implementation, the ESP8266 GPIOs can not be used directly in GPIO_OD_PU
* mode. Instead, the GPIOs are configured in GPIO_IN_PU mode with
* open-drain output driver. Signal levels are then realized as following:
*
* - HIGH: The GPIO is used in the configured GPIO_IN_PU mode. In this
* mode, the output driver is in open-drain mode and pulled-up.
* - LOW : The GPIO is temporarily switched to GPIO_OD_PU mode. In this
* mode, the output value 0, which is written during
* initialization, actively drives the output to low.
*/
gpio_init (_i2c_bus[dev].scl, GPIO_IN_PU);
gpio_init (_i2c_bus[dev].sda, GPIO_IN_PU);
gpio_clear (_i2c_bus[dev].scl);
gpio_clear (_i2c_bus[dev].sda);

/* set SDA and SCL to be floating and pulled-up to high */
_i2c_sda_high (&_i2c_bus[dev]);
_i2c_scl_high (&_i2c_bus[dev]);

/* clear the bus if necessary (SDA is driven permanently low) */
_i2c_clear (&_i2c_bus[dev]);

i2c_release (dev);

return;
Expand Down Expand Up @@ -243,7 +264,7 @@ int /* IRAM */ i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t len,

/* send STOP condition if I2C_NOSTOP flag is not set */
if (!(flags & I2C_NOSTOP)) {
_i2c_stop_cond (bus);
res = _i2c_stop_cond (bus);
}

return res;
Expand Down Expand Up @@ -304,7 +325,7 @@ int /* IRAM */ i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_

/* send STOP condition if I2C_NOSTOP flag is not set */
if (!(flags & I2C_NOSTOP)) {
return _i2c_stop_cond (bus);
res = _i2c_stop_cond (bus);
}

return res;
Expand Down Expand Up @@ -337,51 +358,98 @@ static inline void _i2c_delay (_i2c_bus_t* bus)
}

/*
* Please note: SDA and SDL pins are used in GPIO_OD_PU mode
* (open-drain with pull-ups).
*
* Setting a pin which is in open-drain mode leaves the pin floating and
* the signal is pulled up to high. The signal can then be actively driven
* to low by a slave. A read operation returns the current signal at the pin.
* Note: Due to critical timing required by the I2C software implementation,
* the ESP8266 GPIOs can not be used directly in GPIO_OD_PU mode. Instead,
* the GPIOs are configured in GPIO_IN_PU mode with open-drain output driver.
* Signal levels are then realized as following:
*
* Clearing a pin which is in open-drain mode actively drives the signal to
* low.
* - HIGH: The GPIO is used in the configured GPIO_IN_PU mode. In this mode,
* the output driver is in open-drain mode and pulled-up.
* - LOW : The GPIO is temporarily switched to GPIO_OD_PU mode. In this mode,
* the output value 0, which is written during initialization,
* actively drives the output to low.
*/

static inline bool _i2c_scl_read(_i2c_bus_t* bus)
{
/* read SCL status (pin is in open-drain mode and set) */
/* read SCL status */
return GPIO.IN & bus->scl_bit;
}

static inline bool _i2c_sda_read(_i2c_bus_t* bus)
{
/* read SDA status (pin is in open-drain mode and set) */
/* read SDA status */
return GPIO.IN & bus->sda_bit;
}

static inline void _i2c_scl_low(_i2c_bus_t* bus)
{
/*
* set SCL signal low (switch temporarily to GPIO_OD_PU where the
* written output value 0 drives the pin actively to low)
*/
GPIO.ENABLE_OUT_SET = bus->scl_bit;
}

static inline void _i2c_scl_high(_i2c_bus_t* bus)
{
/* set SCL signal high (pin is in open-drain mode and pulled-up) */
GPIO.OUT_SET = bus->scl_bit;
/*
* set SCL signal high (switch back to GPIO_IN_PU mode, that is the pin is
* in open-drain mode and pulled-up to high)
*/
GPIO.ENABLE_OUT_CLEAR = bus->scl_bit;
}

static inline void _i2c_scl_low(_i2c_bus_t* bus)
static inline void _i2c_sda_low(_i2c_bus_t* bus)
{
/* set SCL signal low (actively driven to low) */
GPIO.OUT_CLEAR = bus->scl_bit;
/*
* set SDA signal low (switch temporarily to GPIO_OD_PU where the
* written output value 0 drives the pin actively to low)
*/
GPIO.ENABLE_OUT_SET = bus->sda_bit;
}

static inline void _i2c_sda_high(_i2c_bus_t* bus)
{
/* set SDA signal high (pin is in open-drain mode and pulled-up) */
GPIO.OUT_SET = bus->sda_bit;
/*
* set SDA signal high (switch back to GPIO_IN_PU mode, that is the pin is
* in open-drain mode and pulled-up to high)
*/
GPIO.ENABLE_OUT_CLEAR = bus->sda_bit;
}

static inline void _i2c_sda_low(_i2c_bus_t* bus)
static void _i2c_clear(_i2c_bus_t* bus)
{
/* set SDA signal low (actively driven to low) */
GPIO.OUT_CLEAR = bus->sda_bit;
DEBUG("%s: dev=%u\n", __func__, bus->dev);

/**
* Sometimes a slave blocks and drives the SDA line permanently low.
* Send some clock pulses in that case (10 at maximum)
*/

/*
* If SDA is low while SCL is high for 10 half cycles, it is not an
* arbitration lost but a bus lock.
*/
int count = 10;
while (!_i2c_sda_read (bus) && _i2c_scl_read (bus) && count) {
count--;
_i2c_delay (bus);
}

if (count) {
/* was not a bus lock */
return;
}

/* send 10 clock pulses in case of bus lock */
count = 10;
while (!_i2c_sda_read (bus) && count--) {
_i2c_scl_low (bus);
_i2c_delay (bus);
_i2c_scl_high (bus);
_i2c_delay (bus);
}
}

static void _i2c_abort(_i2c_bus_t* bus, const char* func)
Expand All @@ -394,6 +462,9 @@ static void _i2c_abort(_i2c_bus_t* bus, const char* func)

/* reset repeated start indicator */
bus->started = false;

/* clear the bus if necessary (SDA is driven permanently low) */
_i2c_clear(bus);
}

static /* IRAM */ int _i2c_arbitration_lost (_i2c_bus_t* bus, const char* func)
Expand All @@ -407,6 +478,9 @@ static /* IRAM */ int _i2c_arbitration_lost (_i2c_bus_t* bus, const char* func)
/* reset repeated start indicator */
bus->started = false;

/* clear the bus if necessary (SDA is driven permanently low) */
_i2c_clear(bus);

return -EAGAIN;
}

Expand Down Expand Up @@ -434,7 +508,9 @@ static /* IRAM */ int _i2c_start_cond(_i2c_bus_t* bus)

/* clock stretching, wait as long as clock is driven to low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (!_i2c_scl_read (bus) && stretch--) {}
while (stretch && !_i2c_scl_read (bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
Expand Down Expand Up @@ -489,7 +565,9 @@ static /* IRAM */ int _i2c_stop_cond(_i2c_bus_t* bus)

/* clock stretching, wait as long as clock is driven to low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (!_i2c_scl_read (bus) && stretch--) {}
while (stretch && !_i2c_scl_read (bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
Expand Down Expand Up @@ -550,7 +628,9 @@ static /* IRAM */ int _i2c_write_bit (_i2c_bus_t* bus, bool bit)

/* clock stretching, wait as long as clock is driven low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (!_i2c_scl_read (bus) && stretch--) {}
while (stretch && !_i2c_scl_read (bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
Expand Down Expand Up @@ -589,7 +669,9 @@ static /* IRAM */ int _i2c_read_bit (_i2c_bus_t* bus, bool* bit)

/* clock stretching, wait as long as clock is driven to low by the slave */
uint32_t stretch = I2C_CLOCK_STRETCH;
while (!_i2c_scl_read (bus) && stretch--) {}
while (stretch && !_i2c_scl_read (bus)) {
stretch--;
}
if (stretch == 0) {
DEBUG("%s: clock stretching timeout dev=%u\n", __func__, bus->dev);
res = -ETIMEDOUT;
Expand Down

0 comments on commit 87b0221

Please sign in to comment.