Skip to content

Commit

Permalink
I2C: Add clock stretching support. (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
r12f authored Dec 30, 2022
1 parent 2c78177 commit 0038d20
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ hydrafwEm.elay
*.hdr
/src/.settings
/src/.idea/
.vscode/
src/hydrafw Debug.launch
1 change: 1 addition & 0 deletions src/common/mode_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ typedef struct {
uint32_t dev_speed;
uint8_t ack_pending : 1;
uint8_t dev_mode;
uint32_t dev_clock_stretch_timeout;
} i2c_config_t;

typedef struct {
Expand Down
79 changes: 68 additions & 11 deletions src/drv/stm32cube/bsp_i2c_master.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "bsp.h"
#include "bsp_print_dbg.h"
#include "bsp_i2c_master.h"
#include "bsp_i2c_conf.h"

Expand All @@ -29,6 +31,7 @@ const int i2c_speed[I2C_SPEED_MAX] = {
};
int i2c_speed_delay;
bool i2c_started;
uint32_t i2c_clock_strech_timeout;

/* Set SCL LOW = 0/GND (0/GND => Set pin = logic reversed in open drain) */
#define set_scl_low() (gpio_set_pin(BSP_I2C1_SCL_SDA_GPIO_PORT, BSP_I2C1_SCL_PIN))
Expand All @@ -43,6 +46,9 @@ bool i2c_started;
/* Get SDA pin state 0 or 1 */
#define get_sda() (gpio_get_pin(BSP_I2C1_SCL_SDA_GPIO_PORT, BSP_I2C1_SDA_PIN))

/* Get SCL pin state 0 or 1 */
#define get_scl() (gpio_get_pin(BSP_I2C1_SCL_SDA_GPIO_PORT, BSP_I2C1_SCL_PIN))

/* wait I2C half clock delay */
#define i2c_sw_delay() (wait_delay(i2c_speed_delay))

Expand Down Expand Up @@ -100,6 +106,8 @@ bsp_status_t bsp_i2c_master_init(bsp_dev_i2c_t dev_num, mode_config_proto_t* mod
else
return BSP_ERROR;

i2c_clock_strech_timeout = mode_conf->config.i2c.dev_clock_stretch_timeout;

/* Init the I2C */
switch(mode_conf->config.i2c.dev_gpio_pull) {
case MODE_CONFIG_DEV_GPIO_PULLUP:
Expand Down Expand Up @@ -192,6 +200,43 @@ bsp_status_t bsp_i2c_stop(bsp_dev_i2c_t dev_num)
return BSP_OK;
}

/** \brief Set SCL to float and wait for slave device to be ready
*/
static bsp_status_t i2c_master_set_scl_float_and_wait_ready(void)
{
uint32_t clock_stretch_tick_count;
unsigned char scl_val;

set_scl_float();
i2c_sw_delay();

// If we are failing to pull up the clock during I2C write, it means the target device is doing clock streching and force
// pulling down clock line to slow the bus. In this case, we will have to wait until the target device to be ready again.
scl_val = get_scl();
if (scl_val == 0) {
clock_stretch_tick_count = 0;

// Clock streching doesn't have any defined maximum time limit in I2C and can hang the bus indefinitely, so we will
// have to put a timer to avoid dead loop here. However, when this happens (usually a faulty device), there is nothing
// we could do in master, but fail and move on.
while (scl_val == 0 && clock_stretch_tick_count < i2c_clock_strech_timeout) {
// We always wait for a full clock cycle before checking the clock line.
i2c_sw_delay();
i2c_sw_delay();

scl_val = get_scl();
++clock_stretch_tick_count;
}

if (i2c_clock_strech_timeout != 0 && clock_stretch_tick_count == i2c_clock_strech_timeout) {
printf_dbg("\nI2C clock streching timeout: waited tick count = %u\n", clock_stretch_tick_count);
return BSP_TIMEOUT;
}
}

return BSP_OK;
}

/** \brief Sends a Byte in blocking mode and set the status.
*
* \param dev_num bsp_dev_i2c_t: I2C dev num.
Expand All @@ -205,6 +250,7 @@ bsp_status_t bsp_i2c_master_write_u8(bsp_dev_i2c_t dev_num, uint8_t tx_data, uin
(void)dev_num;
int i;
unsigned char ack_val;
bsp_status_t status;

/* Write 8 bits */
for(i = 0; i < 8; i++) {
Expand All @@ -215,8 +261,10 @@ bsp_status_t bsp_i2c_master_write_u8(bsp_dev_i2c_t dev_num, uint8_t tx_data, uin

i2c_sw_delay();

set_scl_float();
i2c_sw_delay();
status = i2c_master_set_scl_float_and_wait_ready();
if (status != BSP_OK) {
return status;
}

set_scl_low();
tx_data <<= 1;
Expand All @@ -226,8 +274,10 @@ bsp_status_t bsp_i2c_master_write_u8(bsp_dev_i2c_t dev_num, uint8_t tx_data, uin
set_sda_float();
i2c_sw_delay();

set_scl_float();
i2c_sw_delay();
status = i2c_master_set_scl_float_and_wait_ready();
if (status != BSP_OK) {
return status;
}

ack_val = get_sda();

Expand All @@ -249,9 +299,10 @@ bsp_status_t bsp_i2c_master_write_u8(bsp_dev_i2c_t dev_num, uint8_t tx_data, uin
* \return void
*
*/
void bsp_i2c_read_ack(bsp_dev_i2c_t dev_num, bool enable_ack)
bsp_status_t bsp_i2c_read_ack(bsp_dev_i2c_t dev_num, bool enable_ack)
{
(void)dev_num;
bsp_status_t status;

/* Write 1 bit ACK or NACK */
if(enable_ack == TRUE)
Expand All @@ -261,10 +312,14 @@ void bsp_i2c_read_ack(bsp_dev_i2c_t dev_num, bool enable_ack)

i2c_sw_delay();

set_scl_float();
i2c_sw_delay();
status = i2c_master_set_scl_float_and_wait_ready();
if (status != BSP_OK) {
return status;
}

set_scl_low();

return BSP_OK;
}

/** \brief Read a Byte in blocking mode and set the status.
Expand All @@ -279,15 +334,18 @@ bsp_status_t bsp_i2c_master_read_u8(bsp_dev_i2c_t dev_num, uint8_t* rx_data)
(void)dev_num;
unsigned char data;
int i;
bsp_status_t status;

/* Read 8 bits */
data = 0;
for(i = 0; i < 8; i++) {
set_sda_float();
i2c_sw_delay();

set_scl_float();
i2c_sw_delay();
status = i2c_master_set_scl_float_and_wait_ready();
if (status != BSP_OK) {
return status;
}

data <<= 1;
if(get_sda())
Expand All @@ -301,5 +359,4 @@ bsp_status_t bsp_i2c_master_read_u8(bsp_dev_i2c_t dev_num, uint8_t* rx_data)
/* Do not Send ACK / NACK because sent by bsp_i2c_read_ack() */

return BSP_OK;
}

}
2 changes: 1 addition & 1 deletion src/drv/stm32cube/bsp_i2c_master.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ bsp_status_t bsp_i2c_stop(bsp_dev_i2c_t dev_num);

bsp_status_t bsp_i2c_master_write_u8(bsp_dev_i2c_t dev_num, uint8_t tx_data, uint8_t* tx_ack_flag);
bsp_status_t bsp_i2c_master_read_u8(bsp_dev_i2c_t dev_num, uint8_t* rx_data);
void bsp_i2c_read_ack(bsp_dev_i2c_t dev_num, bool enable_ack);
bsp_status_t bsp_i2c_read_ack(bsp_dev_i2c_t dev_num, bool enable_ack);

#endif /* _BSP_I2C_MASTER_H_ */
6 changes: 6 additions & 0 deletions src/hydrabus/commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const t_token_dict tl_dict[] = {
{ T_CONVENTION, "convention" },
{ T_DELAY, "delay" },
{ T_MMC, "mmc" },
{ T_CLOCK_STRETCH, "clock-stretch" },
/* Developer warning add new command(s) here */

/* BP-compatible commands */
Expand Down Expand Up @@ -913,6 +914,11 @@ t_token tokens_can[] = {
T_FREQUENCY,\
.arg_type = T_ARG_FLOAT,\
.help = "Bus frequency"\
},\
{\
T_CLOCK_STRETCH,\
.arg_type = T_ARG_UINT,\
.help = "Max clock stretch tick count. (0 = Disabled, n = Ticks)"\
},

t_token tokens_mode_i2c[] = {
Expand Down
1 change: 1 addition & 0 deletions src/hydrabus/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ enum {
T_CONVENTION,
T_DELAY,
T_MMC,
T_CLOCK_STRETCH,
/* Developer warning add new command(s) here */

/* BP-compatible commands */
Expand Down
50 changes: 43 additions & 7 deletions src/hydrabus/hydrabus_mode_i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ static void init_proto_default(t_hydra_console *con)
proto->config.i2c.dev_speed = 1;
proto->config.i2c.ack_pending = 0;
proto->config.i2c.dev_mode = DEV_MASTER;
proto->config.i2c.dev_clock_stretch_timeout = 0;
}

static void show_params(t_hydra_console *con)
{
uint8_t i, cnt;
float clock_stretch_timeout_time_in_microseconds;
mode_config_proto_t* proto = &con->mode->proto;

cprintf(con, "GPIO resistor: %s\r\nMode: %s\r\nFrequency: ",
Expand All @@ -85,6 +87,11 @@ static void show_params(t_hydra_console *con)
print_freq(con, speeds[i]);
}
cprintf(con, ")\r\n");

clock_stretch_timeout_time_in_microseconds = proto->config.i2c.dev_clock_stretch_timeout * 1000000.0 / speeds[proto->config.i2c.dev_speed];
cprintf(con, "Clock stretch timeout: %d ticks / %.2lf us (0 = Disabled)\r\n",
proto->config.i2c.dev_clock_stretch_timeout,
clock_stretch_timeout_time_in_microseconds);
}

static int init(t_hydra_console *con, t_tokenline_parsed *p)
Expand Down Expand Up @@ -153,6 +160,15 @@ static int exec(t_hydra_console *con, t_tokenline_parsed *p, int token_pos)
return t;
}
break;
case T_CLOCK_STRETCH:
t += 2;
memcpy(&proto->config.i2c.dev_clock_stretch_timeout, p->buf + p->tokens[t], sizeof(int));
bsp_status = bsp_i2c_master_init(proto->dev_num, proto);
if( bsp_status != BSP_OK) {
cprintf(con, str_bsp_init_err, bsp_status);
return t;
}
break;
case T_SCAN:
scan(con, p);
break;
Expand Down Expand Up @@ -196,6 +212,17 @@ static void stop(t_hydra_console *con)
cprintf(con, str_i2c_stop_br);
}

static bool i2c_io_failed(t_hydra_console *con, bsp_status_t status) {
if (status == BSP_TIMEOUT) {
cprintf(con, "\r\ni2c clock stretching timed out. Please consider increase the timeout with clock-stretch command to see if it helps.\r\n");
return true;
} else if (status != BSP_OK) {
return true;
}

return false;
}

static uint32_t write(t_hydra_console *con, uint8_t *tx_data, uint8_t nb_data)
{
int i;
Expand All @@ -205,7 +232,10 @@ static uint32_t write(t_hydra_console *con, uint8_t *tx_data, uint8_t nb_data)

if(proto->config.i2c.ack_pending) {
/* Send I2C ACK */
bsp_i2c_read_ack(I2C_DEV_NUM, TRUE);
status = bsp_i2c_read_ack(I2C_DEV_NUM, TRUE);
if (i2c_io_failed(con, status))
return status;

cprintf(con, str_i2c_ack_br);
proto->config.i2c.ack_pending = 0;
}
Expand All @@ -224,9 +254,10 @@ static uint32_t write(t_hydra_console *con, uint8_t *tx_data, uint8_t nb_data)
cprintf(con, str_i2c_nack);

cprintf(con, " ");
if(status != BSP_OK)
break;
if (i2c_io_failed(con, status))
return status;
}

cprintf(con, hydrabus_mode_str_mul_br);

return status;
Expand All @@ -242,7 +273,10 @@ static uint32_t read(t_hydra_console *con, uint8_t *rx_data, uint8_t nb_data)
for(i = 0; i < nb_data; i++) {
if(proto->config.i2c.ack_pending) {
/* Send I2C ACK */
bsp_i2c_read_ack(I2C_DEV_NUM, TRUE);
status = bsp_i2c_read_ack(I2C_DEV_NUM, TRUE);
if (i2c_io_failed(con, status))
break;

cprintf(con, str_i2c_ack);
cprintf(con, hydrabus_mode_str_mul_br);
}
Expand All @@ -252,7 +286,7 @@ static uint32_t read(t_hydra_console *con, uint8_t *rx_data, uint8_t nb_data)
/* Read 1 data */
cprintf(con, hydrabus_mode_str_mul_read);
cprintf(con, hydrabus_mode_str_mul_value_u8, rx_data[0]);
if(status != BSP_OK)
if (i2c_io_failed(con, status))
break;

proto->config.i2c.ack_pending = 1;
Expand All @@ -269,13 +303,15 @@ static uint32_t dump(t_hydra_console *con, uint8_t *rx_data, uint8_t nb_data)
for(i = 0; i < nb_data; i++) {
if(proto->config.i2c.ack_pending) {
/* Send I2C ACK */
bsp_i2c_read_ack(I2C_DEV_NUM, TRUE);
status = bsp_i2c_read_ack(I2C_DEV_NUM, TRUE);
if (i2c_io_failed(con, status))
break;
}

status = bsp_i2c_master_read_u8(proto->dev_num, &tmp);
rx_data[i] = tmp;
/* Read 1 data */
if(status != BSP_OK)
if (i2c_io_failed(con, status))
break;

proto->config.i2c.ack_pending = 1;
Expand Down

0 comments on commit 0038d20

Please sign in to comment.