Merge pull request #582 from espway/i2c-timing-calculation

i2c: revise timing calculation
This commit is contained in:
Ruslan V. Uss 2018-03-13 18:35:41 +05:00 committed by GitHub
commit a89417e26e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 90 additions and 36 deletions

View file

@ -37,29 +37,18 @@
#define debug(fmt, ...) #define debug(fmt, ...)
#endif #endif
// The following array contains delay values for different frequencies. // Delay loop takes four CPU clock cycles per round
// These were tuned to match the specified SCL frequency on average. #define DELAY_LOOPS_PER_US_160MHZ 40
// The tuning was done using GCC 5.2.0 with -O2 optimization. // The value for 80 MHz is half the above
const static uint8_t i2c_freq_array[][2] = { // Constant overhead per I2C clock cycle in terms of delay loop rounds.
// If timing is changed by some code change, these will require tuning.
#if I2C_USE_GPIO16 == 1 #if I2C_USE_GPIO16 == 1
[I2C_FREQ_80K] = {230, 107}, #define DELAY_OVERHEAD_80MHZ 18
[I2C_FREQ_100K] = {180, 82}, #define DELAY_OVERHEAD_160MHZ 20
[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 #else
[I2C_FREQ_80K] = {235, 112}, #define DELAY_OVERHEAD_80MHZ 12
[I2C_FREQ_100K] = {185, 88}, #define DELAY_OVERHEAD_160MHZ 14
[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 #endif
};
// Bus settings // Bus settings
typedef struct i2c_bus_description typedef struct i2c_bus_description
@ -71,7 +60,8 @@ typedef struct i2c_bus_description
uint32_t g_scl_mask; // SCL pin mask uint32_t g_scl_mask; // SCL pin mask
uint32_t g_sda_mask; // SDA pin mask uint32_t g_sda_mask; // SDA pin mask
#endif #endif
i2c_freq_t frequency; // Frequency uint8_t delay_80;
uint8_t delay_160;
uint8_t delay; uint8_t delay;
bool started; bool started;
bool flag; bool flag;
@ -86,7 +76,28 @@ inline bool i2c_status(uint8_t bus)
return i2c_bus[bus].started; return i2c_bus[bus].started;
} }
static uint32_t freq_t_to_hz(i2c_freq_t freq)
{
switch (freq)
{
case I2C_FREQ_80K: return 80000;
case I2C_FREQ_100K: return 100000;
case I2C_FREQ_400K: return 400000;
case I2C_FREQ_500K: return 500000;
case I2C_FREQ_600K: return 600000;
case I2C_FREQ_800K: return 800000;
case I2C_FREQ_1000K: return 1000000;
case I2C_FREQ_1300K: return 1300000;
}
return 80000;
}
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)
{
return i2c_init_hz(bus, scl_pin, sda_pin, freq_t_to_hz(freq));
}
int i2c_init_hz(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, uint32_t freq)
{ {
if (bus >= I2C_MAX_BUS) { if (bus >= I2C_MAX_BUS) {
debug("Invalid bus"); debug("Invalid bus");
@ -115,8 +126,6 @@ int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq)
i2c_bus[bus].g_scl_mask = BIT(scl_pin); i2c_bus[bus].g_scl_mask = BIT(scl_pin);
i2c_bus[bus].g_sda_mask = BIT(sda_pin); i2c_bus[bus].g_sda_mask = BIT(sda_pin);
#endif #endif
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.
@ -130,19 +139,48 @@ int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq)
gpio_write(scl_pin, 1); gpio_write(scl_pin, 1);
gpio_write(sda_pin, 1); gpio_write(sda_pin, 1);
// Prevent user, if frequency is high // Inform user if the desired frequency is not supported.
if (sdk_system_get_cpu_freq() == SYS_CPU_80MHZ) if (i2c_set_frequency_hz(bus, freq) != 0) {
if (i2c_freq_array[i2c_bus[bus].frequency][1] == 0) { debug("Frequency not supported");
debug("Frequency not supported"); return -ENOTSUP;
return -ENOTSUP; }
}
return 0; return 0;
} }
void i2c_set_frequency(uint8_t bus, i2c_freq_t freq) int i2c_set_frequency(uint8_t bus, i2c_freq_t freq)
{ {
i2c_bus[bus].frequency = freq; return i2c_set_frequency_hz(bus, freq_t_to_hz(freq));
}
int i2c_set_frequency_hz(uint8_t bus, uint32_t freq)
{
if (freq == 0) return -EINVAL;
uint32_t tick_count = (1000000UL * DELAY_LOOPS_PER_US_160MHZ) / (2 * freq);
bool not_ok = false;
int32_t delay_80 = tick_count / 2 - DELAY_OVERHEAD_80MHZ;
if (delay_80 > 255) {
delay_80 = 255;
not_ok = true;
} else if (delay_80 < 0) {
delay_80 = 0;
not_ok = true;
}
int32_t delay_160 = tick_count - DELAY_OVERHEAD_160MHZ;
if (delay_160 > 255) {
delay_160 = 255;
not_ok = true;
} else if (delay_160 < 0) {
delay_160 = 0;
not_ok = true;
}
i2c_bus[bus].delay_80 = delay_80;
i2c_bus[bus].delay_160 = delay_160;
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, uint32_t clk_stretch)
@ -219,9 +257,9 @@ static inline void set_sda(uint8_t bus)
void i2c_start(uint8_t bus) void i2c_start(uint8_t bus)
{ {
if (sdk_system_get_cpu_freq() == SYS_CPU_160MHZ) if (sdk_system_get_cpu_freq() == SYS_CPU_160MHZ)
i2c_bus[bus].delay = i2c_freq_array[i2c_bus[bus].frequency][0]; i2c_bus[bus].delay = i2c_bus[bus].delay_160;
else else
i2c_bus[bus].delay = i2c_freq_array[i2c_bus[bus].frequency][1]; i2c_bus[bus].delay = i2c_bus[bus].delay_80;
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

View file

@ -66,9 +66,7 @@ typedef enum
I2C_FREQ_600K, I2C_FREQ_600K,
I2C_FREQ_800K, I2C_FREQ_800K,
I2C_FREQ_1000K, I2C_FREQ_1000K,
#if I2C_USE_GPIO16 == 0
I2C_FREQ_1300K I2C_FREQ_1300K
#endif
} i2c_freq_t; } i2c_freq_t;
/** /**
@ -93,12 +91,30 @@ typedef struct i2c_dev
*/ */
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);
/**
* Init bitbanging I2C driver on given pins
* @param bus Bus i2c selection
* @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);
/** /**
* Change bus frequency * Change bus frequency
* @param bus Bus i2c selection * @param bus Bus i2c selection
* @param freq frequency of bus (ex : I2C_FREQ_400K) * @param freq frequency of bus (ex : I2C_FREQ_400K)
*/ */
void i2c_set_frequency(uint8_t bus, i2c_freq_t freq); int i2c_set_frequency(uint8_t bus, i2c_freq_t freq);
/**
* Change bus frequency
* @param bus Bus i2c selection
* @param freq frequency of bus in hertz
*/
int i2c_set_frequency_hz(uint8_t bus, uint32_t freq);
/** /**
* Change clock stretch * Change clock stretch