/* * TWI-master Medium Access Control * * Copyright (c) 2013 Michael Buesch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "twi_master.h" #include "twi_master_sync.h" #include "util.h" #include #include #include #include #ifndef TWI_SCL_HZ # warning "No TWI_SCL_HZ defined. Defaulting to 100 KHz." # define TWI_SCL_HZ 100000ul #endif #define TWI_SYNC enum twi_transfer_status_flags { TWI_XFER_READ = 0x80, TWI_XFER_STAT_MASK = 0x0F, TWI_XFER_STAT_SHIFT = 0, }; struct twi_context { struct twi_transfer *first_xfer; struct twi_transfer *last_xfer; }; static struct twi_context twi; static inline void TWCR_write(uint8_t additional_flags) { mb(); TWCR = (1 << TWEN) | (1 << TWINT) | additional_flags; } static void send_start_condition(void) { /* Send start condition. */ TWCR_write((1 << TWIE) | (1 << TWSTA)); } static void send_stop_condition(void) { /* Send stop condition. */ TWCR_write(1 << TWSTO); } static void transfer_set_status(struct twi_transfer *xfer, enum twi_status status) { xfer->status = (xfer->status & ~TWI_XFER_STAT_MASK) | (status << TWI_XFER_STAT_SHIFT); } static enum twi_status transfer_get_status(const struct twi_transfer *xfer) { return (xfer->status & TWI_XFER_STAT_MASK) >> TWI_XFER_STAT_SHIFT; } static void stop_transfer(struct twi_transfer *xfer, enum twi_status new_status) { send_stop_condition(); transfer_set_status(xfer, new_status); twi.first_xfer = xfer->next; if (twi.first_xfer) send_start_condition(); else twi.last_xfer = NULL; if (xfer->callback) xfer->callback(xfer, new_status); } static void handle_tw_status(struct twi_transfer *xfer, uint8_t twstat) { uint8_t *buffer = xfer->buffer; switch (twstat) { default: stop_transfer(xfer, TWI_STAT_BUSERROR); break; case TW_START: case TW_REP_START: if (xfer->status & TWI_XFER_READ) TWDR = (xfer->address << 1) | 1; else TWDR = (xfer->address << 1); TWCR_write(1 << TWIE); break; case TW_MT_DATA_ACK: case TW_MR_DATA_ACK: case TW_MR_DATA_NACK: case TW_MT_SLA_ACK: if (xfer->status & TWI_XFER_READ) { buffer[xfer->offset++] = TWDR; if (xfer->offset == xfer->read_size) { stop_transfer(xfer, TWI_STAT_FINISHED); } else { if (xfer->offset + 1 == xfer->read_size) TWCR_write(1 << TWIE); else TWCR_write((1 << TWIE) | (1 << TWEA)); } } else { if (xfer->offset == xfer->write_size) { if (xfer->read_size) { xfer->status |= TWI_XFER_READ; xfer->offset = 0; send_start_condition(); } else stop_transfer(xfer, TWI_STAT_FINISHED); } else { TWDR = buffer[xfer->offset++]; TWCR_write(1 << TWIE); } } break; case TW_MR_SLA_ACK: if (xfer->offset + 1 == xfer->read_size) TWCR_write(1 << TWIE); else TWCR_write((1 << TWIE) | (1 << TWEA)); break; } } static void twi_interrupt_handler(void) { struct twi_transfer *xfer; uint8_t twstat; mb(); twstat = TW_STATUS; xfer = twi.first_xfer; if (xfer) handle_tw_status(xfer, twstat); mb(); } ISR(TWI_vect) { twi_interrupt_handler(); } void twi_init(void) { #ifdef TWI_SYNC i2c_init(); #else memset(&twi, 0, sizeof(twi)); TWSR = 0; TWBR = ((F_CPU / TWI_SCL_HZ) - 16) / 2; TWAR = 0; #endif } void twi_transfer(struct twi_transfer *xfer) { #ifdef TWI_SYNC twi_size_t i; uint8_t *buffer = xfer->buffer; if (xfer->write_size) { i2c_start(xfer->address << 1); for (i = 0; i < xfer->write_size; i++) i2c_write(buffer[i]); if (!xfer->read_size) i2c_stop(); } if (xfer->read_size) { i2c_start((xfer->address << 1) | 1); for (i = 0; i < xfer->read_size; i++) { if (i + 1 == xfer->read_size) buffer[i] = i2c_readNak(); else buffer[i] = i2c_readAck(); } i2c_stop(); } if (xfer->callback) xfer->callback(xfer, TWI_STAT_FINISHED); #else /* TWI_SYNC */ uint8_t sreg; xfer->offset = 0; xfer->next = NULL; xfer->status = 0; if (!xfer->write_size) xfer->status |= TWI_XFER_READ; transfer_set_status(xfer, TWI_STAT_INPROGRESS); sreg = irq_disable_save(); if (twi.last_xfer) twi.last_xfer->next = xfer; twi.last_xfer = xfer; if (!twi.first_xfer) { twi.first_xfer = xfer; send_start_condition(); } irq_restore(sreg); #endif } enum twi_status twi_transfer_get_status(const struct twi_transfer *xfer) { enum twi_status status; uint8_t sreg; #ifdef TWI_SYNC return TWI_STAT_FINISHED; #endif sreg = irq_disable_save(); status = transfer_get_status(xfer); irq_restore(sreg); return status; } enum twi_status twi_transfer_wait(struct twi_transfer *xfer, uint16_t timeout_ms) { enum twi_status status; uint32_t timeout = (uint32_t)timeout_ms * 100; while (1) { status = twi_transfer_get_status(xfer); if (status != TWI_STAT_INPROGRESS) break; if (timeout-- == 0) { twi_transfer_cancel(xfer); status = TWI_STAT_TIMEOUT; break; } _delay_us(10); if (!irqs_enabled()) { if (TWCR & (1 << TWINT)) twi_interrupt_handler(); } } return status; } void twi_transfer_cancel(struct twi_transfer *xfer) { uint8_t sreg; #ifdef TWI_SYNC return; #endif sreg = irq_disable_save(); if (transfer_get_status(xfer) == TWI_STAT_INPROGRESS) stop_transfer(xfer, TWI_STAT_CANCELLED); irq_restore(sreg); }