From c8c7f972900e573ae2f05fffc93b7421afeffa09 Mon Sep 17 00:00:00 2001 From: Our Air Quality Date: Fri, 15 Jun 2018 08:22:29 +1000 Subject: [PATCH] i2c: increase the default clock strech timeout to 250msec. This also redefines the timeout in FreeRTOS clock ticks, and implements a two stage wait: firstly spinning sampling frequently, and then falling back to a longer wait while sampling less frequently and yielding. --- extras/i2c/i2c.c | 54 ++++++++++++++++++++++++++++++++---------------- extras/i2c/i2c.h | 11 +++++----- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/extras/i2c/i2c.c b/extras/i2c/i2c.c index 204bc98..faa245a 100644 --- a/extras/i2c/i2c.c +++ b/extras/i2c/i2c.c @@ -66,7 +66,7 @@ typedef struct i2c_bus_description bool started; bool flag; bool force; - uint32_t clk_stretch; + TickType_t clk_stretch; } i2c_bus_description_t; static i2c_bus_description_t i2c_bus[I2C_MAX_BUS]; @@ -183,7 +183,7 @@ int i2c_set_frequency_hz(uint8_t bus, uint32_t freq) return not_ok ? -EINVAL : 0; } -void i2c_set_clock_stretch(uint8_t bus, uint32_t clk_stretch) +void i2c_set_clock_stretch(uint8_t bus, TickType_t clk_stretch) { i2c_bus[bus].clk_stretch = clk_stretch; } @@ -235,13 +235,46 @@ static inline void clear_sda(uint8_t bus) #endif } -static inline void set_scl(uint8_t bus) +#define I2C_CLK_STRETCH_SPIN 1024 + +static 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 + + // Clock stretching. + + // Spin sampling frequently. + uint32_t clk_stretch_spin = I2C_CLK_STRETCH_SPIN; + do { + if (read_scl(bus)) { + return; + } + + clk_stretch_spin--; + } while (clk_stretch_spin); + + // Fall back to a longer wait, sampling less frequently. + TickType_t clk_stretch = i2c_bus[bus].clk_stretch; + TickType_t start = xTaskGetTickCount(); + + do { + vTaskDelay(20 / portTICK_PERIOD_MS); + + if (read_scl(bus)) { + return; + } + + TickType_t elapsed = xTaskGetTickCount() - start; + if (elapsed > clk_stretch) { + break; + } + } while (1); + + debug("bus %u clock stretch timeout", bus); } static inline void set_sda(uint8_t bus) @@ -265,10 +298,7 @@ void i2c_start(uint8_t bus) // Set SDA to 1 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); } @@ -286,14 +316,10 @@ void i2c_start(uint8_t bus) // Output stop condition bool i2c_stop(uint8_t bus) { - uint32_t clk_stretch = i2c_bus[bus].clk_stretch; // Set SDA to 0 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 @@ -315,17 +341,13 @@ bool i2c_stop(uint8_t bus) // Write a bit to I2C bus static void i2c_write_bit(uint8_t bus, bool bit) { - uint32_t clk_stretch = i2c_bus[bus].clk_stretch; if (bit) { 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 // If SDA is high, check that nobody else is driving SDA if (bit && read_sda(bus) == 0) { @@ -338,15 +360,11 @@ static void i2c_write_bit(uint8_t bus, bool bit) // Read a bit from I2C bus static bool i2c_read_bit(uint8_t bus) { - uint32_t clk_stretch = i2c_bus[bus].clk_stretch; bool bit; // Let the slave drive data set_sda(bus); i2c_delay(bus); set_scl(bus); - // Clock stretching - while (read_scl(bus) == 0 && clk_stretch--) - ; // SCL is high, now data is valid bit = read_sda(bus); i2c_delay(bus); diff --git a/extras/i2c/i2c.h b/extras/i2c/i2c.h index 46f8468..9cd55f2 100644 --- a/extras/i2c/i2c.h +++ b/extras/i2c/i2c.h @@ -32,6 +32,8 @@ #include #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -51,7 +53,8 @@ extern "C" { #define I2C_USE_GPIO16 0 #endif -#define I2C_DEFAULT_CLK_STRETCH (10) +/* Default clock strech waiting time, 250 msec. */ +#define I2C_DEFAULT_CLK_STRETCH (250 / portTICK_PERIOD_MS) /* SCL speed settings. 160 MHz sysclk frequency will result in improved * timing accuracy. Greater bitrates will have poorer accuracy. 1000K is the @@ -86,7 +89,6 @@ typedef struct i2c_dev * @param scl_pin SCL pin for I2C * @param sda_pin SDA pin for I2C * @param freq frequency of bus (ex : I2C_FREQ_400K) - * @param clk_stretch I2C clock stretch. I2C_DEFAULT_CLK_STRETCH would be good in most cases * @return Non-zero if error occured */ int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq); @@ -97,7 +99,6 @@ int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq); * @param scl_pin SCL pin for I2C * @param sda_pin SDA pin for I2C * @param freq frequency of bus in hertz - * @param clk_stretch I2C clock stretch. I2C_DEFAULT_CLK_STRETCH would be good in most cases * @return Non-zero if error occured */ int i2c_init_hz(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, uint32_t freq); @@ -119,9 +120,9 @@ int i2c_set_frequency_hz(uint8_t bus, uint32_t freq); /** * Change clock stretch * @param bus I2C bus - * @param clk_stretch I2C clock stretch. I2C_DEFAULT_CLK_STRETCH by default + * @param clk_stretch I2C clock stretch, in ticks. I2C_DEFAULT_CLK_STRETCH by default */ -void i2c_set_clock_stretch(uint8_t bus, uint32_t clk_stretch); +void i2c_set_clock_stretch(uint8_t bus, TickType_t clk_stretch); /** * Write a byte to I2C bus.