Stairs/module/src/lib/VeryTinyI2C.c

154 lines
5.2 KiB
C

/*
* Stairs lighting
* Copyright 2017 (c) Mark van Renswoude
*
* https://git.x2software.net/pub/Stairs
*
* Minimalistic I2C library for the ATTiny2313's limited space.
* Based on David Johnson-Davies' TinyI2C:
* https://github.com/technoblogy/tiny-i2c
*/
#include "VeryTinyI2C.h"
// Constants
// Prepare register value to: Clear flags, and set USI to shift 8 bits i.e. count 16 clock edges.
const unsigned char 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.
const unsigned char USISR_1bit = 1<<USISIF | 1<<USIOIF | 1<<USIPF | 1<<USIDC | 0xE<<USICNT0;
int32_t I2Ccount;
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(void)
{
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();
}
bool 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 false;
return true; // Write successfully completed
}
bool i2c_start(uint8_t address, int 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 false;
/*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 false; // No ACK
return true; // Start successfully completed
}
bool i2c_restart(uint8_t address, int readcount)
{
return i2c_start(address, readcount);
}
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;
}