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.
This commit is contained in:
Our Air Quality 2018-06-15 08:22:29 +10:00
parent d9c5f2eaa2
commit c8c7f97290
2 changed files with 42 additions and 23 deletions

View file

@ -66,7 +66,7 @@ typedef struct i2c_bus_description
bool started; bool started;
bool flag; bool flag;
bool force; bool force;
uint32_t clk_stretch; TickType_t clk_stretch;
} i2c_bus_description_t; } i2c_bus_description_t;
static i2c_bus_description_t i2c_bus[I2C_MAX_BUS]; 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; 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; i2c_bus[bus].clk_stretch = clk_stretch;
} }
@ -235,13 +235,46 @@ static inline void clear_sda(uint8_t bus)
#endif #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 #if I2C_USE_GPIO16 == 1
gpio_write(i2c_bus[bus].g_scl_pin, 1); gpio_write(i2c_bus[bus].g_scl_pin, 1);
#else #else
GPIO.OUT_SET = i2c_bus[bus].g_scl_mask; GPIO.OUT_SET = i2c_bus[bus].g_scl_mask;
#endif #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) static inline void set_sda(uint8_t bus)
@ -265,10 +298,7 @@ void i2c_start(uint8_t bus)
// Set SDA to 1 // Set SDA to 1
set_sda(bus); set_sda(bus);
i2c_delay(bus); i2c_delay(bus);
uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
set_scl(bus); set_scl(bus);
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);
} }
@ -286,14 +316,10 @@ void i2c_start(uint8_t bus)
// Output stop condition // Output stop condition
bool i2c_stop(uint8_t bus) bool i2c_stop(uint8_t bus)
{ {
uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
// Set SDA to 0 // Set SDA to 0
clear_sda(bus); clear_sda(bus);
i2c_delay(bus); i2c_delay(bus);
// Clock stretching
set_scl(bus); set_scl(bus);
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
@ -315,17 +341,13 @@ bool i2c_stop(uint8_t bus)
// Write a bit to I2C bus // Write a bit to I2C bus
static void i2c_write_bit(uint8_t bus, bool bit) static void i2c_write_bit(uint8_t bus, bool bit)
{ {
uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
if (bit) { if (bit) {
set_sda(bus); set_sda(bus);
} else { } else {
clear_sda(bus); clear_sda(bus);
} }
i2c_delay(bus); i2c_delay(bus);
// Clock stretching
set_scl(bus); set_scl(bus);
while (read_scl(bus) == 0 && clk_stretch--)
;
// SCL is high, now data is valid // SCL is high, now data is valid
// If SDA is high, check that nobody else is driving SDA // If SDA is high, check that nobody else is driving SDA
if (bit && read_sda(bus) == 0) { 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 // Read a bit from I2C bus
static bool i2c_read_bit(uint8_t bus) static bool i2c_read_bit(uint8_t bus)
{ {
uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
bool bit; bool bit;
// Let the slave drive data // Let the slave drive data
set_sda(bus); set_sda(bus);
i2c_delay(bus); i2c_delay(bus);
set_scl(bus); set_scl(bus);
// Clock stretching
while (read_scl(bus) == 0 && clk_stretch--)
;
// SCL is high, now data is valid // SCL is high, now data is valid
bit = read_sda(bus); bit = read_sda(bus);
i2c_delay(bus); i2c_delay(bus);

View file

@ -32,6 +32,8 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <errno.h> #include <errno.h>
#include <FreeRTOS.h>
#include <task.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -51,7 +53,8 @@ extern "C" {
#define I2C_USE_GPIO16 0 #define I2C_USE_GPIO16 0
#endif #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 /* SCL speed settings. 160 MHz sysclk frequency will result in improved
* timing accuracy. Greater bitrates will have poorer accuracy. 1000K is the * 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 scl_pin SCL pin for I2C
* @param sda_pin SDA pin for I2C * @param sda_pin SDA pin for I2C
* @param freq frequency of bus (ex : I2C_FREQ_400K) * @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 * @return Non-zero if error occured
*/ */
int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq); 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 scl_pin SCL pin for I2C
* @param sda_pin SDA pin for I2C * @param sda_pin SDA pin for I2C
* @param freq frequency of bus in hertz * @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 * @return Non-zero if error occured
*/ */
int i2c_init_hz(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, uint32_t freq); 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 * Change clock stretch
* @param bus I2C bus * @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. * Write a byte to I2C bus.