Merge pull request #582 from espway/i2c-timing-calculation
i2c: revise timing calculation
This commit is contained in:
commit
a89417e26e
2 changed files with 90 additions and 36 deletions
104
extras/i2c/i2c.c
104
extras/i2c/i2c.c
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue