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 "i2c.h"
#include <esp8266.h> #include <esp8266.h>
#include <espressif/esp_misc.h> // sdk_os_delay_us
#include <espressif/esp_system.h> #include <espressif/esp_system.h>
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include <task.h> #include <task.h>
@ -38,25 +37,42 @@
#define debug(fmt, ...) #define debug(fmt, ...)
#endif #endif
//#define CLK_STRETCH (10) // The following array contains delay values for different frequencies.
// These were tuned to match the specified SCL frequency on average.
// Following array contain delay values for different frequencies // The tuning was done using GCC 5.2.0 with -O2 optimization.
// Warning: 1 is minimal, that mean at 80MHz clock, frequency max is 320kHz
const static uint8_t i2c_freq_array[][2] = { const static uint8_t i2c_freq_array[][2] = {
[I2C_FREQ_80K] = {255, 35}, #if I2C_USE_GPIO16 == 1
[I2C_FREQ_100K] = {100, 20}, [I2C_FREQ_80K] = {230, 107},
[I2C_FREQ_400K] = {10, 1}, [I2C_FREQ_100K] = {180, 82},
[I2C_FREQ_500K] = {6, 1} [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 // Bus settings
typedef struct i2c_bus_description typedef struct i2c_bus_description
{ {
#if I2C_USE_GPIO16 == 1
uint8_t g_scl_pin; // SCL pin uint8_t g_scl_pin; // SCL pin
uint8_t g_sda_pin; // SDA 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 i2c_freq_t frequency; // Frequency
uint8_t delay;
bool started; bool started;
bool flag; bool flag;
bool force; 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; 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].started = false;
i2c_bus[bus].flag = false; i2c_bus[bus].flag = false;
#if I2C_USE_GPIO16 == 1
i2c_bus[bus].g_scl_pin = scl_pin; i2c_bus[bus].g_scl_pin = scl_pin;
i2c_bus[bus].g_sda_pin = sda_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].frequency = freq;
i2c_bus[bus].clk_stretch = I2C_DEFAULT_CLK_STRETCH; i2c_bus[bus].clk_stretch = I2C_DEFAULT_CLK_STRETCH;
// Just to prevent these pins floating too much if not connected. // 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(scl_pin, 1, 1);
gpio_set_pullup(i2c_bus[bus].g_sda_pin, 1, 1); gpio_set_pullup(sda_pin, 1, 1);
gpio_enable(i2c_bus[bus].g_scl_pin, GPIO_OUT_OPEN_DRAIN); gpio_enable(scl_pin, GPIO_OUT_OPEN_DRAIN);
gpio_enable(i2c_bus[bus].g_sda_pin, GPIO_OUT_OPEN_DRAIN); gpio_enable(sda_pin, GPIO_OUT_OPEN_DRAIN);
// I2C bus idle state. // I2C bus idle state.
gpio_write(i2c_bus[bus].g_scl_pin, 1); gpio_write(scl_pin, 1);
gpio_write(i2c_bus[bus].g_sda_pin, 1); gpio_write(sda_pin, 1);
// Prevent user, if frequency is high // Prevent user, if frequency is high
if (sdk_system_get_cpu_freq() == SYS_CPU_80MHZ) if (sdk_system_get_cpu_freq() == SYS_CPU_80MHZ)
if (i2c_freq_array[i2c_bus[bus].frequency][1] == 1) { if (i2c_freq_array[i2c_bus[bus].frequency][1] == 0) {
debug("Max frequency is 320Khz at 80MHz"); debug("Frequency not supported");
return -ENOTSUP; 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) static inline void i2c_delay(uint8_t bus)
{ {
uint32_t delay; uint32_t delay = i2c_bus[bus].delay;
if (freq == SYS_CPU_160MHZ)
{
delay = i2c_freq_array[i2c_bus[bus].frequency][0];
__asm volatile ( __asm volatile (
"1: addi %0, %0, -1" "\n" "1: addi %0, %0, -1" "\n"
"bnez %0, 1b" "\n" "bnez %0, 1b" "\n"
:: "a" (delay)); : "=a" (delay) : "0" (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));
}
} }
// 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) static inline bool read_scl(uint8_t bus)
{ {
gpio_write(i2c_bus[bus].g_scl_pin, 1); #if I2C_USE_GPIO16 == 1
return gpio_read(i2c_bus[bus].g_scl_pin); // Clock high, valid ACK 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) static inline bool read_sda(uint8_t bus)
{ {
gpio_write(i2c_bus[bus].g_sda_pin, 1); #if I2C_USE_GPIO16 == 1
// TODO: Without this delay we get arbitration lost in i2c_stop return gpio_read(i2c_bus[bus].g_sda_pin);
i2c_delay(bus); #else
return gpio_read(i2c_bus[bus].g_sda_pin); // Clock high, valid ACK return GPIO.IN & i2c_bus[bus].g_sda_mask;
#endif
} }
// Actively drive SCL signal low // Actively drive SCL signal low
static inline void clear_scl(uint8_t bus) static inline void clear_scl(uint8_t bus)
{ {
#if I2C_USE_GPIO16 == 1
gpio_write(i2c_bus[bus].g_scl_pin, 0); 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 // Actively drive SDA signal low
static inline void clear_sda(uint8_t bus) static inline void clear_sda(uint8_t bus)
{ {
#if I2C_USE_GPIO16 == 1
gpio_write(i2c_bus[bus].g_sda_pin, 0); 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 // Output start condition
void i2c_start(uint8_t bus) 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 if (i2c_bus[bus].started) { // if started, do a restart cond
// Set SDA to 1 // Set SDA to 1
(void) read_sda(bus); set_sda(bus);
i2c_delay(bus); i2c_delay(bus);
uint32_t clk_stretch = i2c_bus[bus].clk_stretch; uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
set_scl(bus);
while (read_scl(bus) == 0 && clk_stretch--) while (read_scl(bus) == 0 && clk_stretch--)
; ;
// Repeated start setup time, minimum 4.7us // Repeated start setup time, minimum 4.7us
i2c_delay(bus); i2c_delay(bus);
} }
i2c_bus[bus].started = true; i2c_bus[bus].started = true;
set_sda(bus);
if (read_sda(bus) == 0) { if (read_sda(bus) == 0) {
debug("arbitration lost in i2c_start from bus %u", bus); debug("arbitration lost in i2c_start from bus %u", bus);
} }
@ -198,11 +253,15 @@ bool i2c_stop(uint8_t bus)
clear_sda(bus); clear_sda(bus);
i2c_delay(bus); i2c_delay(bus);
// Clock stretching // Clock stretching
set_scl(bus);
while (read_scl(bus) == 0 && clk_stretch--) while (read_scl(bus) == 0 && clk_stretch--)
; ;
// Stop bit setup time, minimum 4us // Stop bit setup time, minimum 4us
i2c_delay(bus); i2c_delay(bus);
// SCL is high, set SDA from 0 to 1 // 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) { if (read_sda(bus) == 0) {
debug("arbitration lost in i2c_stop from bus %u", bus); 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; uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
if (bit) { if (bit) {
(void) read_sda(bus); set_sda(bus);
} else { } else {
clear_sda(bus); clear_sda(bus);
} }
i2c_delay(bus); i2c_delay(bus);
// Clock stretching // Clock stretching
set_scl(bus);
while (read_scl(bus) == 0 && clk_stretch--) while (read_scl(bus) == 0 && clk_stretch--)
; ;
// SCL is high, now data is valid // 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; uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
bool bit; bool bit;
// Let the slave drive data // Let the slave drive data
(void) read_sda(bus); set_sda(bus);
i2c_delay(bus); i2c_delay(bus);
set_scl(bus);
// Clock stretching // Clock stretching
while (read_scl(bus) == 0 && clk_stretch--) while (read_scl(bus) == 0 && clk_stretch--)
; ;

View file

@ -44,14 +44,31 @@ extern "C" {
#define I2C_MAX_BUS 2 #define I2C_MAX_BUS 2
#endif #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) #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 typedef enum
{ {
I2C_FREQ_80K = 0,//!< I2C_FREQ_80K I2C_FREQ_80K = 0,
I2C_FREQ_100K, //!< I2C_FREQ_100K I2C_FREQ_100K,
I2C_FREQ_400K, //!< I2C_FREQ_400K I2C_FREQ_400K,
I2C_FREQ_500K, //!< I2C_FREQ_500K I2C_FREQ_500K,
I2C_FREQ_600K,
I2C_FREQ_800K,
I2C_FREQ_1000K,
#if I2C_USE_GPIO16 == 0
I2C_FREQ_1300K
#endif
} i2c_freq_t; } i2c_freq_t;
/** /**