aboutsummaryrefslogtreecommitdiff
path: root/code/src/TinyI2CMaster.c
diff options
context:
space:
mode:
Diffstat (limited to 'code/src/TinyI2CMaster.c')
-rw-r--r--code/src/TinyI2CMaster.c133
1 files changed, 133 insertions, 0 deletions
diff --git a/code/src/TinyI2CMaster.c b/code/src/TinyI2CMaster.c
new file mode 100644
index 0000000..3ec6b64
--- /dev/null
+++ b/code/src/TinyI2CMaster.c
@@ -0,0 +1,133 @@
+/* Tiny I2C
+
+ David Johnson-Davies - www.technoblogy.com - 14th April 2018
+
+ CC BY 4.0
+ Licensed under a Creative Commons Attribution 4.0 International license:
+ http://creativecommons.org/licenses/by/4.0/
+*/
+
+#include "TinyI2CMaster.h"
+
+// Constants
+// Prepare register value to: Clear flags, and set USI to shift 8 bits i.e. count 16 clock edges.
+static const uint8_t USISR_8bit = 1<<USISIF | 1<<USIOIF | 1<<USIPF | 1<<USIDC | 0x0<<USICNT0;
+// Prepare register value to: Clear flags, and set USI to shift 1 bit i.e. count 2 clock edges.
+static const uint8_t USISR_1bit = 1<<USISIF | 1<<USIOIF | 1<<USIPF | 1<<USIDC | 0xE<<USICNT0;
+
+
+// Minimal Tiny I2C Routines **********************************************
+
+uint8_t i2c_transfer (uint8_t data) {
+ USISR = data; // Set USISR according to data.
+ // Prepare clocking.
+ data = 0<<USISIE | 0<<USIOIE | // Interrupts disabled
+ 1<<USIWM1 | 0<<USIWM0 | // Set USI in Two-wire mode.
+ 1<<USICS1 | 0<<USICS0 | 1<<USICLK | // Software clock strobe as source.
+ 1<<USITC; // Toggle Clock Port.
+ do {
+ DELAY_T2TWI;
+ USICR = data; // Generate positive SCL edge.
+ while (!(PIN_USI_CL & 1<<PIN_USI_SCL)); // Wait for SCL to go high.
+ DELAY_T4TWI;
+ USICR = data; // Generate negative SCL edge.
+ } while (!(USISR & 1<<USIOIF)); // Check for transfer complete.
+
+ DELAY_T2TWI;
+ data = USIDR; // Read out data.
+ USIDR = 0xFF; // Release SDA.
+ DDR_USI |= (1<<PIN_USI_SDA); // Enable SDA as output.
+
+ return data; // Return the data from the USIDR
+}
+
+void i2c_init () {
+ PORT_USI |= 1<<PIN_USI_SDA; // Enable pullup on SDA.
+ PORT_USI_CL |= 1<<PIN_USI_SCL; // Enable pullup on SCL.
+
+ DDR_USI_CL |= 1<<PIN_USI_SCL; // Enable SCL as output.
+ DDR_USI |= 1<<PIN_USI_SDA; // Enable SDA as output.
+
+ USIDR = 0xFF; // Preload data register with "released level" data.
+ USICR = 0<<USISIE | 0<<USIOIE | // Disable Interrupts.
+ 1<<USIWM1 | 0<<USIWM0 | // Set USI in Two-wire mode.
+ 1<<USICS1 | 0<<USICS0 | 1<<USICLK | // Software stobe as counter clock source
+ 0<<USITC;
+ USISR = 1<<USISIF | 1<<USIOIF | 1<<USIPF | 1<<USIDC | // Clear flags,
+ 0x0<<USICNT0; // and reset counter.
+}
+
+uint8_t i2c_read (void) {
+ if ((I2Ccount != 0) && (I2Ccount != -1)) I2Ccount--;
+
+ /* Read a byte */
+ DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.
+ uint8_t data = i2c_transfer(USISR_8bit);
+
+ /* Prepare to generate ACK (or NACK in case of End Of Transmission) */
+ if (I2Ccount == 0) USIDR = 0xFF; else USIDR = 0x00;
+ i2c_transfer(USISR_1bit); // Generate ACK/NACK.
+
+ return data; // Read successfully completed
+}
+
+uint8_t i2c_readLast (void) {
+ I2Ccount = 0;
+ return i2c_read();
+}
+
+uint8_t i2c_write (uint8_t data) {
+ /* Write a byte */
+ PORT_USI_CL &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.
+ USIDR = data; // Setup data.
+ i2c_transfer(USISR_8bit); // Send 8 bits on bus.
+
+ /* Clock and verify (N)ACK from slave */
+ DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.
+ if (i2c_transfer(USISR_1bit) & 1<<TWI_NACK_BIT) return 0;
+
+ return 1; // Write successfully completed
+}
+
+// Start transmission by sending address
+uint8_t i2c_start (uint8_t address, int8_t readcount) {
+ if (readcount != 0) { I2Ccount = readcount; readcount = 1; }
+ uint8_t addressRW = address<<1 | readcount;
+
+ /* Release SCL to ensure that (repeated) Start can be performed */
+ PORT_USI_CL |= 1<<PIN_USI_SCL; // Release SCL.
+ while (!(PIN_USI_CL & 1<<PIN_USI_SCL)); // Verify that SCL becomes high.
+#ifdef TWI_FAST_MODE
+ DELAY_T4TWI;
+#else
+ DELAY_T2TWI;
+#endif
+
+ /* Generate Start Condition */
+ PORT_USI &= ~(1<<PIN_USI_SDA); // Force SDA LOW.
+ DELAY_T4TWI;
+ PORT_USI_CL &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.
+ PORT_USI |= 1<<PIN_USI_SDA; // Release SDA.
+
+ if (!(USISR & 1<<USISIF)) return 0;
+
+ /*Write address */
+ PORT_USI_CL &= ~(1<<PIN_USI_SCL); // Pull SCL LOW.
+ USIDR = addressRW; // Setup data.
+ i2c_transfer(USISR_8bit); // Send 8 bits on bus.
+
+ /* Clock and verify (N)ACK from slave */
+ DDR_USI &= ~(1<<PIN_USI_SDA); // Enable SDA as input.
+ if (i2c_transfer(USISR_1bit) & 1<<TWI_NACK_BIT) return 0; // No ACK
+
+ return 1; // Start successfully completed
+}
+
+void i2c_stop (void) {
+ PORT_USI &= ~(1<<PIN_USI_SDA); // Pull SDA low.
+ PORT_USI_CL |= 1<<PIN_USI_SCL; // Release SCL.
+ while (!(PIN_USI_CL & 1<<PIN_USI_SCL)); // Wait for SCL to go high.
+ DELAY_T4TWI;
+ PORT_USI |= 1<<PIN_USI_SDA; // Release SDA.
+ DELAY_T2TWI;
+}