avr-fw-modules/core/src/i2c.c

201 lines
3.4 KiB
C

#include <hwo/i2c.h>
#include <hwo/utils.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <sys/mutex.h>
#include <sys/atomic.h>
#include <sys/errno.h>
struct i2c i2c;
#define TWCMD(cmd) TWCR = ((TWCR & 0x0F) | _BV(TWINT) | _BV(TWIE) | cmd)
static inline void _i2c_send_slave(uint8_t slave) { TWDR = slave; TWCMD( 0 ); };
static inline void _i2c_send_start(void) { TWCMD( _BV(TWSTA) ); };
static inline void _i2c_send_restart(void) { TWCMD( _BV(TWSTA) ); };
static void _i2c_send_stop(void) {
TWCMD( _BV(TWSTO) );
if (i2c.owner)
thread_wake(i2c.owner);
};
static inline void _i2c_send_byte(uint8_t by) {
TWDR = by;
TWCMD(0);
};
void i2c_lock(void) {
mutex_lock(&i2c.mutex);
};
void i2c_release(void) {
mutex_release(&i2c.mutex);
};
void i2c_init(uint32_t clk)
{
uint32_t twbr = ((__freq_cpu / clk) - 16) / 2;
uint8_t twps = 0;
while (twbr > 255)
{
twps++;
twbr /= 4;
};
TWBR = twbr & 0xff;
TWSR = twps;
TWCR = _BV(TWEN) | _BV(TWIE);
i2c.clock = clk;
};
int i2c_default_callback(void *p,int n,int ch){
if (ch == -1) {
if (n >= i2c.length){
return -1;
};
return i2c.buffer[ n ];
} else {
i2c.buffer[ n ] = ch;
if ((n+2) == i2c.length){
return I2C_CBR_NACK;
} else if ((n+1) >= i2c.length){
return I2C_CBR_STOP;
} else {
return I2C_CBR_CONT;
};
};
};
int i2c_transfer(uint8_t slave,uint8_t *buffer,uint8_t length)
{
MUTEXED(&i2c.mutex);
i2c.length = length;
i2c.buffer = buffer;
return i2c_transfer_ex(slave, i2c_default_callback, NULL);
};
int i2c_transfer_ex(uint8_t slave,i2c_callback callback,void *p)
{
MUTEXED(&i2c.mutex);
// Warten auf freie Hardware
while (TWCR & 0xF0){
//wait_ms(1);
yield();
};
i2c.slave = slave;
i2c.ptr = 0;
i2c.error = 0;
i2c.callback= callback;
i2c.p = p;
i2c.owner = current_thread();
{
ATOMIC
_i2c_send_start();
thread_sleep(NULL);
};
i2c.owner = NULL;
return i2c.error ? i2c.error : i2c.ptr;
};
void _i2c_cancel(void)
{
i2c.error = -EFAIL;
_i2c_send_stop();
};
void _i2c_send(void)
{
int n;
if (!i2c.callback){
i2c.error = -ENULLPTR;
_i2c_send_stop();
} else {
n = i2c.callback( i2c.p, i2c.ptr, -1 );
if (n<0){
i2c.error = ESUCCESS;
_i2c_send_stop();
} else {
i2c.ptr++;
_i2c_send_byte( n & 0xFF );
};
};
};
void _i2c_recv(void)
{
int ch = TWDR;
if (!i2c.callback){
i2c.error = -ENULLPTR;
_i2c_send_stop();
} else {
ch = i2c.callback( i2c.p, i2c.ptr, ch );
i2c.ptr++;
switch (ch){
case I2C_CBR_CONT:
TWCMD(_BV(TWEA));
break;
case I2C_CBR_NACK:
TWCMD(0);
break;
case I2C_CBR_STOP:
i2c.error = ESUCCESS;
_i2c_send_stop();
break;
default:
i2c.error = -EPARAM;
_i2c_send_stop();
break;
};
};
};
VECT(TWI_vect){
switch (TWSR & 0xF8)
{
case 0x08: // START sent 0b00001000
case 0x10: // Repeated START sent 0b00010000
_i2c_send_slave( i2c.slave );
break;
case 0x18: // SLA+W sent + ACK 0b00011000
case 0x28: // Data sent + ACK 0b00101000
_i2c_send();
break;
case 0x20: // SLA+W sent + N-ACK 0b00100000
case 0x30: // Data sent + N-ACK 0b00110000
case 0x38: // ARB LOST / SLA+R N-ACK 0b00111000
case 0x48: // SLA+R N-ACK 0b01001000
_i2c_cancel();
break;
case 0x40: // SLA+R ACK
TWCMD(_BV(TWEA));
break;
case 0x50: // DATA rcvd + ACK
_i2c_recv();
break;
case 0x58: // Data rcvd + NACK
_i2c_recv();
break;
default:
_i2c_cancel();
i2c.error = -EUNKNOWN;
break;
};
};