aboutsummaryrefslogtreecommitdiffstats
path: root/firmware/e300/rev_c/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/e300/rev_c/i2c.c')
-rw-r--r--firmware/e300/rev_c/i2c.c518
1 files changed, 518 insertions, 0 deletions
diff --git a/firmware/e300/rev_c/i2c.c b/firmware/e300/rev_c/i2c.c
new file mode 100644
index 000000000..70a28e61a
--- /dev/null
+++ b/firmware/e300/rev_c/i2c.c
@@ -0,0 +1,518 @@
+#include "config.h"
+#include "i2c.h"
+
+#include <util/delay.h>
+
+#include "io.h"
+#include "debug.h"
+
+/*
+ - Reset bus on failure (lack of ACK, etc)
+ - Clock stretching
+ - In pull-up mode, much code was commented out to ever avoid driving the bus (for a fleeting moment) as this was visible on the scope as short peaks (instead the line will briefly go Hi Z).
+*/
+
+volatile bool _i2c_disable_ack_check = false;
+
+// FIXME: Follow magic numbers should be in a struct that is passed into each function
+
+#define I2C_DEFAULT_RETRY_DELAY 1 // us MAGIC
+#define I2C_DEFAULT_MAX_ACK_RETRIES 10 // * I2C_DEFAULT_RETRY_DELAY us
+
+#define I2C_DEFAULT_BUS_WAIT 10 // us MAGIC
+#define I2C_DEFAULT_MAX_BUS_RETRIES 10
+
+#define I2C_DEFAULT_SCL_LOW_PERIOD 2 // 1.3 us
+#define I2C_DEFAULT_SCL_HIGH_PERIOD 1 // 0.6 us
+#define I2C_DEFAULT_BUS_FREE_TIME 2 // 1.3 us
+#define I2C_DEFAULT_STOP_TIME 1 // 0.6 us
+
+#define I2C_DELAY _delay_us // _delay_ms
+
+static bool _i2c_start_ex(io_pin_t sda, io_pin_t scl, bool pull_up)
+{
+ // Assumes: SDA/SCL are both inputs
+
+ uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES;
+ while ((io_test_pin(sda) == false) || (io_test_pin(scl) == false))
+ {
+ I2C_DELAY(I2C_DEFAULT_BUS_WAIT);
+ if (retries-- == 0)
+ {
+debug_log("I2C:S1");
+ return false;
+ }
+ }
+
+ // START condition
+// if (pull_up == false)
+ io_clear_pin(sda); // Set LOW before switching to output
+ io_output_pin(sda);
+// if (pull_up)
+// io_clear_pin(sda);
+ I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD); // Thd, sta
+
+ retries = I2C_DEFAULT_MAX_BUS_RETRIES;
+ while (io_test_pin(scl) == false) // SCL should remain high
+ {
+ I2C_DELAY(I2C_DEFAULT_BUS_WAIT);
+ if (retries-- == 0)
+ {
+ io_input_pin(sda);
+debug_log_ex("I2C:S2", false);
+debug_log_hex(scl);
+ return false;
+ }
+ }
+
+// if (pull_up == false)
+ io_clear_pin(scl);
+ io_output_pin(scl);
+// if (pull_up)
+// io_clear_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD / 2); // MAGIC
+
+ return true;
+}
+
+static bool _i2c_stop_ex(io_pin_t sda, io_pin_t scl, bool pull_up)
+{
+ // Assumes:
+ // SCL is output & LOW
+ // SDA is input (Hi-Z, or pull-up enabled)
+
+ // Assuming pull-up already enabled
+ //if (pull_up)
+ // io_set_pin(sda);
+
+ bool result = true;
+
+ // SDA should be HIGH after ACK has been clocked away
+// bool skip_drive = false;
+ uint8_t retries = 0;
+ while (io_test_pin(sda) == false)
+ {
+ if (retries == I2C_DEFAULT_MAX_ACK_RETRIES)
+ {
+ debug_log_ex("I2C:STP ", false);
+ debug_log_hex(sda);
+ debug_blink_rev(4);
+
+// skip_drive = true;
+ result = false;
+ break; // SDA is being held low?!
+ }
+
+ ++retries;
+ I2C_DELAY(I2C_DEFAULT_RETRY_DELAY);
+ }
+
+ // STOP condition
+// if ((pull_up == false) || (skip_drive))
+ io_clear_pin(sda); // Don't tri-state if internal pull-up is used
+// //else
+// // Pin will now be driven, but having checked SDA is HIGH above means slave's SDA should be Open Collector (i.e. it won't blow up)
+ io_output_pin(sda); // Drive LOW
+// if (pull_up)
+// io_clear_pin(sda);
+
+ ///////////////////////////////////
+
+// if (pull_up)
+// io_set_pin(scl); // Don't tri-state if internal pull-up is used. Line will be driven, but assuming this is the only master on the clock line (i.e. no one else will pull it low).
+ io_input_pin(scl);
+ if (pull_up)
+ io_set_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_STOP_TIME);
+
+ ///////////////////////////////////
+
+// if ((pull_up) && (skip_drive == false))
+// io_set_pin(sda); // Don't tri-state if internal pull-up is used
+ io_input_pin(sda);
+// if ((pull_up) && (skip_drive))
+ io_set_pin(sda);
+ I2C_DELAY(I2C_DEFAULT_BUS_FREE_TIME);
+
+ return result;
+}
+/*
+static void _i2c_stop(io_pin_t sda, io_pin_t scl)
+{
+ _i2c_stop_ex(sda, scl, false);
+}
+*//*
+static void _i2c_abort_safe_ex(io_pin_t pin, bool pull_up)
+{
+ if (io_is_output(pin))
+ {
+ if (io_is_pin_set(pin)) // This is bad - hope no slave is pulling down the line
+ {
+ io_input_pin(pin); // Pull-up already enabled
+
+ if (pull_up == false)
+ io_clear_pin(pin); // Doing this after changing direction ensures the line is not brought down
+ }
+ else // Currently pulling line down
+ {
+ io_input_pin(pin); // Hi-Z
+
+ if (pull_up) // There will be a moment where the line will float (better than driving the line though...)
+ {
+ io_set_pin(pin);
+ }
+ }
+ }
+ else // Already an input
+ {
+ if (pull_up)
+ {
+ io_set_pin(pin); // Enable pull-ups
+ }
+ else
+ {
+ io_clear_pin(pin); // Disable pull-ups
+ }
+ }
+
+ // Normally: pin will be Hi-Z input
+ // With internal pull-up: pin will be input with pull-up enabled
+}
+*/
+static void _i2c_abort_safe(io_pin_t pin, bool pull_up)
+{
+ if (pull_up == false)
+ io_clear_pin(pin); // Should never be output/HIGH, could be input/<was outputting HIGH> so disable pull-ups
+
+ io_input_pin(pin);
+
+ if (pull_up)
+ io_set_pin(pin); // Enable pull-up
+}
+
+static void _i2c_abort_ex(io_pin_t sda, io_pin_t scl, bool pull_up)
+{
+/* if (pull_up == false)
+ {
+ io_clear_pin(sda);
+ io_clear_pin(scl);
+ }
+
+ io_input_pin(scl);
+ io_input_pin(sda);
+
+ if (pull_up)
+ {
+ io_set_pin(sda);
+ io_set_pin(scl);
+ }
+*/
+ _i2c_abort_safe(scl, pull_up);
+ _i2c_abort_safe(sda, pull_up);
+
+ //_i2c_abort_safe_ex(scl, pull_up);
+ //_i2c_abort_safe_ex(sda, pull_up);
+}
+/*
+static void _i2c_abort(io_pin_t sda, io_pin_t scl)
+{
+ _i2c_abort_ex(sda, scl, false);
+}
+*/
+static bool _i2c_write_byte_ex(io_pin_t sda, io_pin_t scl, uint8_t value, bool pull_up)
+{
+ // Assumes:
+ // SDA output is LOW
+ // SCL output is LOW
+
+ for (uint8_t i = 0; i < 8; ++i)
+ {
+ bool b = ((value & (0x01 << (7 - i))) != 0x00); // MSB first
+
+ if (b)
+ {
+ if (pull_up)
+ {
+// io_set_pin(sda); // This is bad (will drive line for a moment), but more stable than letting line float
+ io_input_pin(sda);
+ io_set_pin(sda);
+ }
+ else
+ io_input_pin(sda); // Release HIGH
+
+ if (io_test_pin(sda) == false)
+ {
+ debug_log("I2C:WR ");
+ debug_log_hex(sda);
+ debug_blink_rev(1);
+ return false;
+ }
+ }
+ else
+ {
+ if (pull_up)
+ {
+// if (io_is_output(sda))
+ io_clear_pin(sda);
+// else
+// {
+ io_output_pin(sda); // [This is bad (will drive line for a moment), but more stable than letting line float]
+// io_clear_pin(sda);
+// }
+ }
+ else
+ {
+ io_enable_pin(sda, false);
+ io_output_pin(sda); // Drive LOW
+ }
+ }
+
+ ///////////////////////////////
+
+ io_input_pin(scl); // Release HIGH
+ if (pull_up)
+ io_set_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD);
+#ifdef I2C_ALLOW_CLOCK_STRETCH
+ uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES;
+ while (io_test_pin(scl) == false) // Clock stretch requested?
+ {
+ I2C_DELAY(I2C_DEFAULT_BUS_WAIT);
+ if (--retries == 0)
+ {
+ io_input_pin(sda); // Release HIGH
+ if (pull_up)
+ io_set_pin(sda);
+
+ debug_log_ex("I2C:STRTCH ", false);
+ debug_log_hex(scl);
+ debug_blink_rev(2);
+ return false;
+ }
+ }
+#endif // I2C_ALLOW_CLOCK_STRETCH
+ if (pull_up)
+ io_clear_pin(scl);
+ io_output_pin(scl); // Drive LOW
+ I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD);
+ }
+
+ io_input_pin(sda); // Release HIGH
+ if (pull_up)
+ io_set_pin(sda); // Assuming letting line float won't confuse slave when pulling line LOW for ACK
+ I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD);
+
+ uint8_t retries = 0;
+ while ((_i2c_disable_ack_check == false) && (io_test_pin(sda)))
+ {
+ if (retries == I2C_DEFAULT_MAX_ACK_RETRIES)
+ {
+ debug_log_ex("I2C:ACK ", false);
+ debug_log_hex_ex(sda, false);
+ debug_log_hex(value);
+ debug_blink_rev(3);
+ return false; // Will abort and not release bus - done by caller
+ }
+
+ ++retries;
+ I2C_DELAY(I2C_DEFAULT_RETRY_DELAY);
+ }
+
+ // Clock away acknowledge
+// if (pull_up)
+// io_set_pin(scl);
+ io_input_pin(scl); // Release HIGH
+ if (pull_up)
+ io_set_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD);
+
+ if (pull_up)
+ io_clear_pin(scl);
+ io_output_pin(scl); // Drive LOW
+// if (pull_up)
+// io_clear_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD);
+
+ return true;
+}
+
+static bool _i2c_read_byte_ex(io_pin_t sda, io_pin_t scl, uint8_t* value, bool pull_up)
+{
+ // Assumes:
+ // SDA output is LOW
+ // SCL output is LOW
+
+ io_input_pin(sda);
+ if (pull_up)
+ io_set_pin(sda); // OK to leave line floating for a moment (better not to drive as slave will be pulling it to ground)
+
+ (*value) = 0x00;
+
+ for (uint8_t i = 0; i < 8; ++i)
+ {
+// if (pull_up)
+// io_set_pin(scl); // [Not ideal with pull-up]
+ io_input_pin(scl); // Release HIGH
+ if (pull_up)
+ io_set_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_SCL_HIGH_PERIOD);
+#ifdef I2C_ALLOW_CLOCK_STRETCH
+ uint8_t retries = I2C_DEFAULT_MAX_BUS_RETRIES;
+ while (io_test_pin(scl) == false) // Clock stretch requested?
+ {
+ I2C_DELAY(I2C_DEFAULT_BUS_WAIT);
+ if (--retries == 0)
+ {
+ debug_log_ex("I2C:R ");
+ debug_log_hex(scl);
+ debug_blink_rev(5);
+ return false;
+ }
+ }
+#endif // I2C_ALLOW_CLOCK_STRETCH
+ (*value) |= ((io_test_pin(sda) ? 0x1 : 0x0) << (7 - i)); // MSB first
+
+ if (pull_up)
+ io_clear_pin(scl);
+ io_output_pin(scl); // Drive LOW (not ideal with pull-up)
+// if (pull_up)
+// io_clear_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_SCL_LOW_PERIOD);
+ }
+
+ // Not necessary to ACK since it's only this one byte
+
+ return true;
+}
+
+bool i2c_read2_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up)
+{
+ if (_i2c_start_ex(sda, scl, pull_up) == false)
+ return false;
+
+ if (_i2c_write_byte_ex(sda, scl, addr & ~0x01, pull_up) == false)
+ {
+#ifdef I2C_EXTRA_DEBUGGING
+ //debug_log_ex("R21:", false);
+ debug_log("R21");
+ //debug_log_hex(addr);
+#endif // I2C_EXTRA_DEBUGGING
+ goto i2c_read2_fail;
+ }
+
+ if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false)
+ {
+#ifdef I2C_EXTRA_DEBUGGING
+ //debug_log_ex("R22:", false);
+ debug_log("R22");
+ //debug_log_hex(subaddr);
+#endif // I2C_EXTRA_DEBUGGING
+ goto i2c_read2_fail;
+ }
+
+ io_input_pin(scl);
+ if (pull_up)
+ io_set_pin(scl);
+ I2C_DELAY(I2C_DEFAULT_BUS_WAIT);
+
+ if (_i2c_start_ex(sda, scl, pull_up) == false)
+ {
+ return false;
+ }
+
+ if (_i2c_write_byte_ex(sda, scl, addr | 0x01, pull_up) == false)
+ {
+#ifdef I2C_EXTRA_DEBUGGING
+ //debug_log_ex("R23:", false);
+ debug_log("R23");
+ //debug_log_hex(addr);
+#endif // I2C_EXTRA_DEBUGGING
+ goto i2c_read2_fail;
+ }
+
+ if (_i2c_read_byte_ex(sda, scl, value, pull_up) == false)
+ {
+#ifdef I2C_EXTRA_DEBUGGING
+ //debug_log_ex("R24:", false);
+ debug_log("R24");
+ //debug_log_hex(*value);
+#endif // I2C_EXTRA_DEBUGGING
+ goto i2c_read2_fail;
+ }
+
+ if (_i2c_stop_ex(sda, scl, pull_up) == false)
+ {
+#ifdef I2C_EXTRA_DEBUGGING
+ debug_log("R25");
+#endif // I2C_EXTRA_DEBUGGING
+ }
+
+ return true;
+i2c_read2_fail:
+ _i2c_abort_ex(sda, scl, pull_up);
+ return false;
+}
+
+bool i2c_write_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value, bool pull_up)
+{
+ if (_i2c_start_ex(sda, scl, pull_up) == false)
+ return false;
+
+ if (_i2c_write_byte_ex(sda, scl, addr, pull_up) == false)
+ goto i2c_write_fail;
+
+ if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false)
+ goto i2c_write_fail;
+
+ if (_i2c_write_byte_ex(sda, scl, value, pull_up) == false)
+ goto i2c_write_fail;
+
+ _i2c_stop_ex(sda, scl, pull_up);
+
+ return true;
+i2c_write_fail:
+ _i2c_abort_ex(sda, scl, pull_up);
+ return false;
+}
+
+bool i2c_write(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t value)
+{
+ return i2c_write_ex(sda, scl, addr, subaddr, value, false);
+}
+
+bool i2c_read_ex(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value, bool pull_up)
+{
+ if (_i2c_start_ex(sda, scl, pull_up) == false)
+ return false;
+
+ if (_i2c_write_byte_ex(sda, scl, addr, pull_up) == false)
+ goto i2c_read_fail;
+
+ if (_i2c_write_byte_ex(sda, scl, subaddr, pull_up) == false)
+ goto i2c_read_fail;
+
+ if (_i2c_read_byte_ex(sda, scl, value, pull_up) == false)
+ goto i2c_read_fail;
+
+ _i2c_stop_ex(sda, scl, pull_up);
+
+ return true;
+i2c_read_fail:
+ _i2c_abort_ex(sda, scl, pull_up);
+ return false;
+}
+
+bool i2c_read(io_pin_t sda, io_pin_t scl, uint8_t addr, uint8_t subaddr, uint8_t* value)
+{
+ return i2c_read_ex(sda, scl, addr, subaddr, value, false);
+}
+
+void i2c_init_ex(io_pin_t sda, io_pin_t scl, bool pull_up)
+{
+ _i2c_abort_ex(sda, scl, pull_up);
+}
+
+void i2c_init(io_pin_t sda, io_pin_t scl)
+{
+ i2c_init_ex(sda, scl, false);
+}