diff --git a/lib_i2c/api/i2c.h b/lib_i2c/api/i2c.h index 0bf6ff3..a9d8707 100644 --- a/lib_i2c/api/i2c.h +++ b/lib_i2c/api/i2c.h @@ -26,6 +26,8 @@ typedef enum { I2C_NACK, ///< the slave has NACKed the last byte I2C_ACK, ///< the slave has ACKed the last byte + I2C_SCL_PULLUP_MISSING, ///< SCL pull-up resistor not detected + I2C_SDA_PULLUP_MISSING, ///< SDA pull-up resistor not detected } i2c_res_t; #if(defined __XC__ || defined __DOXYGEN__) @@ -114,7 +116,9 @@ typedef interface i2c_master_if { typedef enum { I2C_REGOP_SUCCESS, ///< the operation was successful I2C_REGOP_DEVICE_NACK, ///< the operation was NACKed when sending the device address, so either the device is missing or busy - I2C_REGOP_INCOMPLETE ///< the operation was NACKed halfway through by the slave + I2C_REGOP_INCOMPLETE, ///< the operation was NACKed halfway through by the slave + I2C_REGOP_SCL_PULLUP_MISSING, ///< SCL pull-up resistor not detected + I2C_REGOP_SDA_PULLUP_MISSING, ///< SDA pull-up resistor not detected } i2c_regop_res_t; #ifndef __DOXYGEN__ @@ -152,12 +156,28 @@ extends client interface i2c_master_if : { size_t n; i2c_res_t res; res = i.write(device_addr, a_reg, 1, n, 0); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (n != 1) { result = I2C_REGOP_DEVICE_NACK; i.send_stop_bit(); return 0; } res = i.read(device_addr, data, 1, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (res == I2C_ACK) { result = I2C_REGOP_SUCCESS; } else { @@ -183,7 +203,13 @@ extends client interface i2c_master_if : { { uint8_t a_data[2] = {reg, data}; size_t n; - i.write(device_addr, a_data, 2, n, 1); + i2c_res_t res = i.write(device_addr, a_data, 2, n, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + return I2C_REGOP_SCL_PULLUP_MISSING; + } + if (res == I2C_SDA_PULLUP_MISSING) { + return I2C_REGOP_SDA_PULLUP_MISSING; + } if (n == 0) { return I2C_REGOP_DEVICE_NACK; } @@ -222,13 +248,29 @@ extends client interface i2c_master_if : { uint8_t data[1]; size_t n; i2c_res_t res; - i.write(device_addr, a_reg, 2, n, 0); + res = i.write(device_addr, a_reg, 2, n, 0); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (n != 2) { result = I2C_REGOP_DEVICE_NACK; i.send_stop_bit(); return 0; } res = i.read(device_addr, data, 1, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (res == I2C_NACK) { result = I2C_REGOP_DEVICE_NACK; } else { @@ -255,7 +297,13 @@ extends client interface i2c_master_if : { uint8_t data) { uint8_t a_data[3] = {reg >> 8, reg, data}; size_t n; - i.write(device_addr, a_data, 3, n, 1); + i2c_res_t res = i.write(device_addr, a_data, 3, n, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + return I2C_REGOP_SCL_PULLUP_MISSING; + } + if (res == I2C_SDA_PULLUP_MISSING) { + return I2C_REGOP_SDA_PULLUP_MISSING; + } if (n == 0) { return I2C_REGOP_DEVICE_NACK; } @@ -294,13 +342,29 @@ extends client interface i2c_master_if : { uint8_t data[2]; size_t n; i2c_res_t res; - i.write(device_addr, a_reg, 2, n, 0); + res = i.write(device_addr, a_reg, 2, n, 0); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (n != 2) { result = I2C_REGOP_DEVICE_NACK; i.send_stop_bit(); return 0; } res = i.read(device_addr, data, 2, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (res == I2C_NACK) { result = I2C_REGOP_DEVICE_NACK; } else { @@ -332,7 +396,13 @@ extends client interface i2c_master_if : { uint16_t data) { uint8_t a_data[4] = {reg >> 8, reg, data >> 8, data}; size_t n; - i.write(device_addr, a_data, 4, n, 1); + i2c_res_t res = i.write(device_addr, a_data, 4, n, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + return I2C_REGOP_SCL_PULLUP_MISSING; + } + if (res == I2C_SDA_PULLUP_MISSING) { + return I2C_REGOP_SDA_PULLUP_MISSING; + } if (n == 0) { return I2C_REGOP_DEVICE_NACK; } @@ -370,13 +440,29 @@ extends client interface i2c_master_if : { uint8_t data[2]; size_t n; i2c_res_t res; - i.write(device_addr, a_reg, 1, n, 0); + res = i.write(device_addr, a_reg, 1, n, 0); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (n != 1) { result = I2C_REGOP_DEVICE_NACK; i.send_stop_bit(); return 0; } res = i.read(device_addr, data, 2, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + result = I2C_REGOP_SCL_PULLUP_MISSING; + return 0; + } + if (res == I2C_SDA_PULLUP_MISSING) { + result = I2C_REGOP_SDA_PULLUP_MISSING; + return 0; + } if (res == I2C_NACK) { result = I2C_REGOP_DEVICE_NACK; } else { @@ -406,7 +492,13 @@ extends client interface i2c_master_if : { uint16_t data) { uint8_t a_data[3] = {reg, data >> 8, data}; size_t n; - i.write(device_addr, a_data, 3, n, 1); + i2c_res_t res = i.write(device_addr, a_data, 3, n, 1); + if (res == I2C_SCL_PULLUP_MISSING) { + return I2C_REGOP_SCL_PULLUP_MISSING; + } + if (res == I2C_SDA_PULLUP_MISSING) { + return I2C_REGOP_SDA_PULLUP_MISSING; + } if (n == 0) { return I2C_REGOP_DEVICE_NACK; } diff --git a/lib_i2c/src/i2c_master.xc b/lib_i2c/src/i2c_master.xc index 89481e8..1d9f95a 100644 --- a/lib_i2c/src/i2c_master.xc +++ b/lib_i2c/src/i2c_master.xc @@ -6,6 +6,7 @@ #include #include "xassert.h" +#include "i2c_pullup_common.h" /* NOTE: the kbits_per_second needs to be passed around due to the fact that the * compiler won't compute a new static const from a static const. @@ -54,9 +55,9 @@ static void release_clock_and_wait( unsigned delay, static const unsigned kbits_per_second) { + timer tmr; p_scl when pinseq(1) :> void; - timer tmr; unsigned time; tmr when timerafter(fall_time + delay) :> time; @@ -203,6 +204,10 @@ void i2c_master( int locked_client = -1; p_scl :> void; p_sda :> void; + + // Check for pull-up resistors at startup + i2c_res_t bus_error = check_pullups_two_port(p_scl, p_sda); + while (1) { select { @@ -211,6 +216,12 @@ void i2c_master( c[i].read(uint8_t device, uint8_t buf[m], size_t m, int send_stop_bit) -> i2c_res_t result: + // Return error immediately if bus has problems + if (bus_error != I2C_ACK) { + result = bus_error; + break; + } + const int stopped = (locked_client == -1); unsigned fall_time = last_fall_time; start_bit(p_scl, p_sda, kbits_per_second, fall_time, stopped); @@ -258,6 +269,14 @@ void i2c_master( c[i].write(uint8_t device, uint8_t buf[n], size_t n, size_t &num_bytes_sent, int send_stop_bit) -> i2c_res_t result: + + // Return error immediately if bus has problems + if (bus_error != I2C_ACK) { + result = bus_error; + num_bytes_sent = 0; + break; + } + unsigned fall_time = last_fall_time; const int stopped = locked_client == -1; start_bit(p_scl, p_sda, kbits_per_second, fall_time, stopped); diff --git a/lib_i2c/src/i2c_master_async.xc b/lib_i2c/src/i2c_master_async.xc index fabcb28..73cf39b 100644 --- a/lib_i2c/src/i2c_master_async.xc +++ b/lib_i2c/src/i2c_master_async.xc @@ -201,6 +201,21 @@ void i2c_master_async_comb( int stopped = 1; i2c_res_t res = I2C_ACK; + // Check for pull-up resistors at startup (simplified version) + // This checks if the lines are naturally high (indicating pull-ups are present) + i2c_res_t bus_error = I2C_ACK; + { + // If either line is stuck low at startup, assume missing pull-up + unsigned scl_val, sda_val; + p_scl :> scl_val; + p_sda :> sda_val; + if (!scl_val) { + bus_error = I2C_SCL_PULLUP_MISSING; + } else if (!sda_val) { + bus_error = I2C_SDA_PULLUP_MISSING; + } + } + /* These select cases represent the main state machine for the I2C master component. The state machine will change state based on a timer event to progress the transaction or on an event from the SCL line when waiting @@ -472,6 +487,15 @@ void i2c_master_async_comb( case i[int j].write(uint8_t device_addr, uint8_t buf0[n], size_t n, int _send_stop_bit): + // Return error immediately if bus has problems + if (bus_error != I2C_ACK) { + res = bus_error; + bytes_sent = 0; + cur_client = j; + i[cur_client].operation_complete(); + break; + } + data = (device_addr << 1) | 0; bitnum = 0; optype = WRITE; @@ -492,6 +516,15 @@ void i2c_master_async_comb( break; case i[int j].read(uint8_t device_addr, size_t n, int _send_stop_bit): + // Return error immediately if bus has problems + if (bus_error != I2C_ACK) { + res = bus_error; + bytes_sent = 0; + cur_client = j; + i[cur_client].operation_complete(); + break; + } + data = (device_addr << 1) | 1; bitnum = 0; optype = READ; diff --git a/lib_i2c/src/i2c_master_single_port.xc b/lib_i2c/src/i2c_master_single_port.xc index 3a1aefa..e6220d6 100644 --- a/lib_i2c/src/i2c_master_single_port.xc +++ b/lib_i2c/src/i2c_master_single_port.xc @@ -9,6 +9,7 @@ #include #include "xassert.h" +#include "i2c_pullup_common.h" #define SDA_LOW 0 #define SCL_LOW 0 @@ -103,6 +104,7 @@ static void high_pulse_drive( p_i2c <: SCL_LOW | sdaValue | other_bits_mask; tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void; p_i2c <: SCL_HIGH | sdaValue | other_bits_mask; + // TODO: This call may hang if no pull-up present - to be addressed in future update wait_for_clock_high(p_i2c, scl_bit_position, fall_time, (bit_time * 3) / 4, kbits_per_second); fall_time = fall_time + bit_time; tmr when timerafter(fall_time) :> void; @@ -126,6 +128,7 @@ static int high_pulse_sample( p_i2c <: SCL_LOW | SDA_HIGH | other_bits_mask; tmr when timerafter(fall_time + compute_low_period_ticks(kbits_per_second)) :> void; p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask; + // TODO: This call may hang if no pull-up present - to be addressed in future update wait_for_clock_high(p_i2c, scl_bit_position, fall_time, (bit_time * 3) / 4, kbits_per_second); int sample_value = peek(p_i2c); @@ -225,6 +228,9 @@ void i2c_master_single_port( set_port_drive_low(p_i2c); p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask; + // Check for pull-up resistors at startup + i2c_res_t bus_error = check_pullups_single_port(p_i2c, scl_bit_position, sda_bit_position, other_bits_mask); + while (1) { select { case (size_t i = 0; i < n; i++) @@ -232,6 +238,12 @@ void i2c_master_single_port( c[i].read(uint8_t device, uint8_t buf[m], size_t m, int send_stop_bit) -> i2c_res_t result: + // Return error immediately if bus has problems + if (bus_error != I2C_ACK) { + result = bus_error; + break; + } + const int stopped = locked_client == -1; unsigned fall_time = last_fall_time; start_bit(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time, stopped); @@ -282,6 +294,13 @@ void i2c_master_single_port( size_t &num_bytes_sent, int send_stop_bit) -> i2c_res_t result: + // Return error immediately if bus has problems + if (bus_error != I2C_ACK) { + result = bus_error; + num_bytes_sent = 0; + break; + } + const int stopped = locked_client == -1; unsigned fall_time = last_fall_time; start_bit(p_i2c, kbits_per_second, scl_bit_position, sda_bit_position, other_bits_mask, fall_time, stopped); diff --git a/lib_i2c/src/i2c_pullup_common.h b/lib_i2c/src/i2c_pullup_common.h new file mode 100644 index 0000000..03e0ca8 --- /dev/null +++ b/lib_i2c/src/i2c_pullup_common.h @@ -0,0 +1,44 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef _i2c_pullup_common_h_ +#define _i2c_pullup_common_h_ + +#include +#include + +/** Check if pull-up resistors are present on SCL and SDA lines for two-port I2C. + * This function drives the lines low briefly, then releases them + * and checks if they return to high state within a reasonable time. + * + * \param p_scl SCL port + * \param p_sda SDA port + * + * \returns I2C_ACK if both pull-ups are present, + * I2C_SCL_PULLUP_MISSING if SCL pull-up is missing, + * I2C_SDA_PULLUP_MISSING if SDA pull-up is missing + */ +i2c_res_t check_pullups_two_port( + port p_scl, + port p_sda); + +/** Check if pull-up resistors are present on SCL and SDA lines for single-port I2C. + * This function drives the lines low briefly, then releases them + * and checks if they return to high state within a reasonable time. + * + * \param p_i2c single port containing both SCL and SDA + * \param scl_bit_position bit position of SCL on the port + * \param sda_bit_position bit position of SDA on the port + * \param other_bits_mask mask for other bits that should be preserved + * + * \returns I2C_ACK if both pull-ups are present, + * I2C_SCL_PULLUP_MISSING if SCL pull-up is missing, + * I2C_SDA_PULLUP_MISSING if SDA pull-up is missing + */ +i2c_res_t check_pullups_single_port( + port p_i2c, + static const unsigned scl_bit_position, + static const unsigned sda_bit_position, + static const unsigned other_bits_mask); + +#endif // _i2c_pullup_common_h_ \ No newline at end of file diff --git a/lib_i2c/src/i2c_pullup_common.xc b/lib_i2c/src/i2c_pullup_common.xc new file mode 100644 index 0000000..ccf511f --- /dev/null +++ b/lib_i2c/src/i2c_pullup_common.xc @@ -0,0 +1,128 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include + +/** Check if pull-up resistors are present on SCL and SDA lines for two-port I2C. + * This function drives the lines low briefly, then releases them + * and checks if they return to high state within a reasonable time. + * + * \param p_scl SCL port + * \param p_sda SDA port + * + * \returns I2C_ACK if both pull-ups are present, + * I2C_SCL_PULLUP_MISSING if SCL pull-up is missing, + * I2C_SDA_PULLUP_MISSING if SDA pull-up is missing + */ +i2c_res_t check_pullups_two_port( + port p_scl, + port p_sda) +{ + timer tmr; + unsigned start_time, timeout_time; + const unsigned PULLUP_TIMEOUT_TICKS = 1000; // 10us timeout for pull-up detection + + // Test SCL pull-up + p_scl <: 0; // Drive SCL low + delay_ticks(100); // Brief delay to ensure line is driven low + p_scl :> void; // Release SCL + + tmr :> start_time; + timeout_time = start_time + PULLUP_TIMEOUT_TICKS; + + // Use select with timeout to avoid hanging + select { + case p_scl when pinseq(1) :> void: + // SCL pull-up is working + break; + case tmr when timerafter(timeout_time) :> void: + return I2C_SCL_PULLUP_MISSING; + } + + // Test SDA pull-up + p_sda <: 0; // Drive SDA low + delay_ticks(100); // Brief delay to ensure line is driven low + p_sda :> void; // Release SDA + + tmr :> start_time; + timeout_time = start_time + PULLUP_TIMEOUT_TICKS; + + // Use select with timeout to avoid hanging + select { + case p_sda when pinseq(1) :> void: + // SDA pull-up is working + break; + case tmr when timerafter(timeout_time) :> void: + return I2C_SDA_PULLUP_MISSING; + } + + return I2C_ACK; +} + +/** Check if pull-up resistors are present on SCL and SDA lines for single-port I2C. + * This function drives the lines low briefly, then releases them + * and checks if they return to high state within a reasonable time. + * + * \param p_i2c single port containing both SCL and SDA + * \param scl_bit_position bit position of SCL on the port + * \param sda_bit_position bit position of SDA on the port + * \param other_bits_mask mask for other bits that should be preserved + * + * \returns I2C_ACK if both pull-ups are present, + * I2C_SCL_PULLUP_MISSING if SCL pull-up is missing, + * I2C_SDA_PULLUP_MISSING if SDA pull-up is missing + */ +i2c_res_t check_pullups_single_port( + port p_i2c, + static const unsigned scl_bit_position, + static const unsigned sda_bit_position, + static const unsigned other_bits_mask) +{ + const unsigned SCL_HIGH = BIT_MASK(scl_bit_position); + const unsigned SDA_HIGH = BIT_MASK(sda_bit_position); + const unsigned SCL_LOW = 0; + const unsigned SDA_LOW = 0; + + timer tmr; + unsigned start_time, timeout_time; + const unsigned PULLUP_TIMEOUT_TICKS = 1000; // 10us timeout for pull-up detection + + // Test SCL pull-up + p_i2c <: SCL_LOW | SDA_HIGH | other_bits_mask; // Drive SCL low, SDA high + delay_ticks(100); // Brief delay to ensure line is driven low + p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask; // Release SCL + + tmr :> start_time; + timeout_time = start_time + PULLUP_TIMEOUT_TICKS; + + unsigned val = peek(p_i2c); + while (!(val & SCL_HIGH)) { + tmr :> start_time; + if (start_time > timeout_time) { + return I2C_SCL_PULLUP_MISSING; + } + val = peek(p_i2c); + } + + // Test SDA pull-up + p_i2c <: SCL_HIGH | SDA_LOW | other_bits_mask; // Drive SDA low, SCL high + delay_ticks(100); // Brief delay to ensure line is driven low + p_i2c <: SCL_HIGH | SDA_HIGH | other_bits_mask; // Release SDA + + tmr :> start_time; + timeout_time = start_time + PULLUP_TIMEOUT_TICKS; + + val = peek(p_i2c); + while (!(val & SDA_HIGH)) { + tmr :> start_time; + if (start_time > timeout_time) { + return I2C_SDA_PULLUP_MISSING; + } + val = peek(p_i2c); + } + + return I2C_ACK; +} \ No newline at end of file