I2C optimization (#503)

Fix of #480
This commit is contained in:
Sakari Kapanen 2017-12-09 19:27:39 +02:00 committed by Ruslan V. Uss
parent 4ae2a6cdf0
commit d41c0f1d72
2 changed files with 134 additions and 56 deletions

View file

@ -25,7 +25,6 @@
#include "i2c.h"
#include <esp8266.h>
#include <espressif/esp_misc.h> // sdk_os_delay_us
#include <espressif/esp_system.h>
#include <FreeRTOS.h>
#include <task.h>
@ -38,25 +37,42 @@
#define debug(fmt, ...)
#endif
//#define CLK_STRETCH (10)
// Following array contain delay values for different frequencies
// Warning: 1 is minimal, that mean at 80MHz clock, frequency max is 320kHz
// The following array contains delay values for different frequencies.
// These were tuned to match the specified SCL frequency on average.
// The tuning was done using GCC 5.2.0 with -O2 optimization.
const static uint8_t i2c_freq_array[][2] = {
[I2C_FREQ_80K] = {255, 35},
[I2C_FREQ_100K] = {100, 20},
[I2C_FREQ_400K] = {10, 1},
[I2C_FREQ_500K] = {6, 1}
#if I2C_USE_GPIO16 == 1
[I2C_FREQ_80K] = {230, 107},
[I2C_FREQ_100K] = {180, 82},
[I2C_FREQ_400K] = {30, 7},
[I2C_FREQ_500K] = {20, 1},
[I2C_FREQ_600K] = {13, 0},
[I2C_FREQ_800K] = {5, 0},
[I2C_FREQ_1000K] = {1, 0}
#else
[I2C_FREQ_80K] = {235, 112},
[I2C_FREQ_100K] = {185, 88},
[I2C_FREQ_400K] = {36, 13},
[I2C_FREQ_500K] = {25, 8},
[I2C_FREQ_600K] = {20, 5},
[I2C_FREQ_800K] = {11, 1},
[I2C_FREQ_1000K] = {5, 0},
[I2C_FREQ_1300K] = {1, 0}
#endif
};
static uint8_t freq; // Store CPU frequency for optimisation speed in delay function (Warning: Don't change CPU frequency during a transaction)
// Bus settings
typedef struct i2c_bus_description
{
#if I2C_USE_GPIO16 == 1
uint8_t g_scl_pin; // SCL pin
uint8_t g_sda_pin; // SDA pin
#else
uint32_t g_scl_mask; // SCL pin mask
uint32_t g_sda_mask; // SDA pin mask
#endif
i2c_freq_t frequency; // Frequency
uint8_t delay;
bool started;
bool flag;
bool force;
@ -77,28 +93,47 @@ int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq)
return -EINVAL;
}
#if I2C_USE_GPIO16 == 1
const int I2C_MAX_PIN = 16;
#else
const int I2C_MAX_PIN = 15;
#endif
if (scl_pin > I2C_MAX_PIN || sda_pin > I2C_MAX_PIN)
{
debug("Invalid GPIO number. All pins must be less than or equal to %d",
I2C_MAX_PIN);
return -EINVAL;
}
i2c_bus[bus].started = false;
i2c_bus[bus].flag = false;
#if I2C_USE_GPIO16 == 1
i2c_bus[bus].g_scl_pin = scl_pin;
i2c_bus[bus].g_sda_pin = sda_pin;
#else
i2c_bus[bus].g_scl_mask = BIT(scl_pin);
i2c_bus[bus].g_sda_mask = BIT(sda_pin);
#endif
i2c_bus[bus].frequency = freq;
i2c_bus[bus].clk_stretch = I2C_DEFAULT_CLK_STRETCH;
// Just to prevent these pins floating too much if not connected.
gpio_set_pullup(i2c_bus[bus].g_scl_pin, 1, 1);
gpio_set_pullup(i2c_bus[bus].g_sda_pin, 1, 1);
gpio_set_pullup(scl_pin, 1, 1);
gpio_set_pullup(sda_pin, 1, 1);
gpio_enable(i2c_bus[bus].g_scl_pin, GPIO_OUT_OPEN_DRAIN);
gpio_enable(i2c_bus[bus].g_sda_pin, GPIO_OUT_OPEN_DRAIN);
gpio_enable(scl_pin, GPIO_OUT_OPEN_DRAIN);
gpio_enable(sda_pin, GPIO_OUT_OPEN_DRAIN);
// I2C bus idle state.
gpio_write(i2c_bus[bus].g_scl_pin, 1);
gpio_write(i2c_bus[bus].g_sda_pin, 1);
gpio_write(scl_pin, 1);
gpio_write(sda_pin, 1);
// Prevent user, if frequency is high
if (sdk_system_get_cpu_freq() == SYS_CPU_80MHZ)
if (i2c_freq_array[i2c_bus[bus].frequency][1] == 1) {
debug("Max frequency is 320Khz at 80MHz");
if (i2c_freq_array[i2c_bus[bus].frequency][1] == 0) {
debug("Frequency not supported");
return -ENOTSUP;
}
@ -117,70 +152,90 @@ void i2c_set_clock_stretch(uint8_t bus, uint32_t clk_stretch)
static inline void i2c_delay(uint8_t bus)
{
uint32_t delay;
if (freq == SYS_CPU_160MHZ)
{
delay = i2c_freq_array[i2c_bus[bus].frequency][0];
uint32_t delay = i2c_bus[bus].delay;
__asm volatile (
"1: addi %0, %0, -1" "\n"
"bnez %0, 1b" "\n"
:: "a" (delay));
}
else
{
delay = i2c_freq_array[i2c_bus[bus].frequency][1];
__asm volatile (
"1: addi %0, %0, -1" "\n"
"bnez %0, 1b" "\n"
:: "a" (delay));
}
: "=a" (delay) : "0" (delay));
}
// Set SCL as input, allowing it to float high, and return current
// level of line, 0 or 1
static inline bool read_scl(uint8_t bus)
{
gpio_write(i2c_bus[bus].g_scl_pin, 1);
return gpio_read(i2c_bus[bus].g_scl_pin); // Clock high, valid ACK
#if I2C_USE_GPIO16 == 1
return gpio_read(i2c_bus[bus].g_scl_pin);
#else
return GPIO.IN & i2c_bus[bus].g_scl_mask;
#endif
}
// Set SDA as input, allowing it to float high, and return current
// level of line, 0 or 1
static inline bool read_sda(uint8_t bus)
{
gpio_write(i2c_bus[bus].g_sda_pin, 1);
// TODO: Without this delay we get arbitration lost in i2c_stop
i2c_delay(bus);
return gpio_read(i2c_bus[bus].g_sda_pin); // Clock high, valid ACK
#if I2C_USE_GPIO16 == 1
return gpio_read(i2c_bus[bus].g_sda_pin);
#else
return GPIO.IN & i2c_bus[bus].g_sda_mask;
#endif
}
// Actively drive SCL signal low
static inline void clear_scl(uint8_t bus)
{
#if I2C_USE_GPIO16 == 1
gpio_write(i2c_bus[bus].g_scl_pin, 0);
#else
GPIO.OUT_CLEAR = i2c_bus[bus].g_scl_mask;
#endif
}
// Actively drive SDA signal low
static inline void clear_sda(uint8_t bus)
{
#if I2C_USE_GPIO16 == 1
gpio_write(i2c_bus[bus].g_sda_pin, 0);
#else
GPIO.OUT_CLEAR = i2c_bus[bus].g_sda_mask;
#endif
}
static inline void set_scl(uint8_t bus)
{
#if I2C_USE_GPIO16 == 1
gpio_write(i2c_bus[bus].g_scl_pin, 1);
#else
GPIO.OUT_SET = i2c_bus[bus].g_scl_mask;
#endif
}
static inline void set_sda(uint8_t bus)
{
#if I2C_USE_GPIO16 == 1
gpio_write(i2c_bus[bus].g_sda_pin, 1);
#else
GPIO.OUT_SET = i2c_bus[bus].g_sda_mask;
#endif
}
// Output start condition
void i2c_start(uint8_t bus)
{
freq = sdk_system_get_cpu_freq();
if (sdk_system_get_cpu_freq() == SYS_CPU_160MHZ)
i2c_bus[bus].delay = i2c_freq_array[i2c_bus[bus].frequency][0];
else
i2c_bus[bus].delay = i2c_freq_array[i2c_bus[bus].frequency][1];
if (i2c_bus[bus].started) { // if started, do a restart cond
// Set SDA to 1
(void) read_sda(bus);
set_sda(bus);
i2c_delay(bus);
uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
set_scl(bus);
while (read_scl(bus) == 0 && clk_stretch--)
;
// Repeated start setup time, minimum 4.7us
i2c_delay(bus);
}
i2c_bus[bus].started = true;
set_sda(bus);
if (read_sda(bus) == 0) {
debug("arbitration lost in i2c_start from bus %u", bus);
}
@ -198,11 +253,15 @@ bool i2c_stop(uint8_t bus)
clear_sda(bus);
i2c_delay(bus);
// Clock stretching
set_scl(bus);
while (read_scl(bus) == 0 && clk_stretch--)
;
// Stop bit setup time, minimum 4us
i2c_delay(bus);
// SCL is high, set SDA from 0 to 1
set_sda(bus);
// additional delay before testing SDA value to avoid wrong state
i2c_delay(bus);
if (read_sda(bus) == 0) {
debug("arbitration lost in i2c_stop from bus %u", bus);
}
@ -220,12 +279,13 @@ static void i2c_write_bit(uint8_t bus, bool bit)
{
uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
if (bit) {
(void) read_sda(bus);
set_sda(bus);
} else {
clear_sda(bus);
}
i2c_delay(bus);
// Clock stretching
set_scl(bus);
while (read_scl(bus) == 0 && clk_stretch--)
;
// SCL is high, now data is valid
@ -243,8 +303,9 @@ static bool i2c_read_bit(uint8_t bus)
uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
bool bit;
// Let the slave drive data
(void) read_sda(bus);
set_sda(bus);
i2c_delay(bus);
set_scl(bus);
// Clock stretching
while (read_scl(bus) == 0 && clk_stretch--)
;

View file

@ -44,14 +44,31 @@ extern "C" {
#define I2C_MAX_BUS 2
#endif
/* Set this to 1 if you intend to use GPIO 16 for I2C. It is not recommended
* and will result in degradation of performance and timing accuracy.
*/
#ifndef I2C_USE_GPIO16
#define I2C_USE_GPIO16 0
#endif
#define I2C_DEFAULT_CLK_STRETCH (10)
/* SCL speed settings. 160 MHz sysclk frequency will result in improved
* timing accuracy. Greater bitrates will have poorer accuracy. 1000K is the
* maximum SCL speed at 80 MHz sysclk.
*/
typedef enum
{
I2C_FREQ_80K = 0,//!< I2C_FREQ_80K
I2C_FREQ_100K, //!< I2C_FREQ_100K
I2C_FREQ_400K, //!< I2C_FREQ_400K
I2C_FREQ_500K, //!< I2C_FREQ_500K
I2C_FREQ_80K = 0,
I2C_FREQ_100K,
I2C_FREQ_400K,
I2C_FREQ_500K,
I2C_FREQ_600K,
I2C_FREQ_800K,
I2C_FREQ_1000K,
#if I2C_USE_GPIO16 == 0
I2C_FREQ_1300K
#endif
} i2c_freq_t;
/**