/* * Copyright (C) 2017 Hamburg University of Applied Sciences, Dimitri Nahm * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @addtogroup driver_periph * @{ * * @file * @brief Low-level I2C driver implementation fot atmega common * * @note This implementation only implements the 7-bit addressing mode. * * @author Dimitri Nahm * * @} */ #include #include "cpu.h" #include "mutex.h" #include "periph/i2c.h" #include "periph_conf.h" #define ENABLE_DEBUG (0) #include "debug.h" #define MT_START 0x08 #define MT_ADDRESS_ACK 0x18 #define MT_DATA_ACK 0x28 #define MR_ADDRESS_ACK 0x40 /* guard file in case no I2C device is defined */ #if I2C_NUMOF /* static function definitions */ static int _start(uint8_t address, uint8_t rw_flag); static int _write(const uint8_t *data, int length); static void _stop(void); /** * @brief Array holding one pre-initialized mutex for each I2C device */ static mutex_t locks[] = { #if I2C_0_EN [I2C_0] = MUTEX_INIT, #endif }; int i2c_init_master(i2c_t dev, i2c_speed_t speed) { /* TWI Bit Rate Register - division factor for the bit rate generator*/ int twibrr; /* check if the line is valid */ if (dev >= I2C_NUMOF) { return -1; } /* calculate speed configuration */ switch (speed) { case I2C_SPEED_NORMAL: twibrr = ((CLOCK_CORECLOCK/100000)-16)/2; break; case I2C_SPEED_FAST: twibrr = ((CLOCK_CORECLOCK/400000)-16)/2; break; default: return -2; } /* set pull-up on SCL and SDA */ #if defined (CPU_ATMEGA2560) || defined (CPU_ATMEGA1281) PORTD |= (1 << PORTD0) | (1 << PORTD1); #endif #ifdef CPU_ATMEGA328P PORTC |= (1 << PORTC4) | (1 << PORTC5); #endif /* enable I2C clock */ i2c_poweron(dev); /* disable device */ TWCR &= ~(1 << TWEN); /* configure I2C clock */ TWBR = twibrr; /* enable device */ TWCR |= (1 << TWEN); return 0; } int i2c_acquire(i2c_t dev) { if (dev >= I2C_NUMOF) { return -1; } mutex_lock(&locks[dev]); return 0; } int i2c_release(i2c_t dev) { if (dev >= I2C_NUMOF) { return -1; } mutex_unlock(&locks[dev]); return 0; } int i2c_read_byte(i2c_t dev, uint8_t address, void *data) { return i2c_read_bytes(dev, address, data, 1); } int i2c_read_bytes(i2c_t dev, uint8_t address, void *data, int length) { uint8_t *my_data = data; if ((unsigned int)dev >= I2C_NUMOF) { return -1; } /* send start condition and slave address */ if (_start(address, I2C_FLAG_READ) != 0) return 0; for (int i = 0; i < length; i++) { /* Send NACK for last received byte */ if ((length-i) == 1) TWCR = (1 << TWEN) | (1 << TWINT); else TWCR = (1 << TWEA) | (1 << TWEN) | (1 << TWINT); DEBUG("Wait for byte %i\n", i+1); /* Wait for TWINT Flag set. This indicates that DATA has been received.*/ while (!(TWCR & (1 << TWINT))) {} /* receive data byte */ my_data[i] = TWDR; DEBUG("Byte %i received\n", i+1); } /* end transmission */ _stop(); return length; } int i2c_read_reg(i2c_t dev, uint8_t address, uint8_t reg, void *data) { return i2c_read_regs(dev, address, reg, data, 1); } int i2c_read_regs(i2c_t dev, uint8_t address, uint8_t reg, void *data, int length) { if ((unsigned int)dev >= I2C_NUMOF) { return -1; } /* start transmission and send slave address */ if (_start(address, I2C_FLAG_WRITE) != 0) return 0; /* send register address and wait for complete transfer to be finished*/ if (_write(®, 1) != 1) { _stop(); return 0; } _stop(); /* now start a new start condition and receive data */ return i2c_read_bytes(dev, address, data, length); } int i2c_write_byte(i2c_t dev, uint8_t address, uint8_t data) { return i2c_write_bytes(dev, address, &data, 1); } int i2c_write_bytes(i2c_t dev, uint8_t address, const void *data, int length) { int bytes = 0; if ((unsigned int)dev >= I2C_NUMOF) { return -1; } /* start transmission and send slave address */ if (_start(address, I2C_FLAG_WRITE) != 0) return 0; /* send out data bytes */ bytes = _write(data, length); /* end transmission */ _stop(); return bytes; } int i2c_write_reg(i2c_t dev, uint8_t address, uint8_t reg, uint8_t data) { if ((unsigned int)dev >= I2C_NUMOF) { return -1; } return i2c_write_regs(dev, address, reg, &data, 1); } int i2c_write_regs(i2c_t dev, uint8_t address, uint8_t reg, const void *data, int length) { int bytes = 0; if ((unsigned int)dev >= I2C_NUMOF) { return -1; } /* start transmission and send slave address */ if (_start(address, I2C_FLAG_WRITE) != 0) return 0; /* send register address and wait for complete transfer to be finished*/ if (_write(®, 1)) /* write data to register */ bytes = _write(data, length); /* finish transfer */ _stop(); /* return number of bytes send */ return bytes; } void i2c_poweron(i2c_t dev) { if (dev < I2C_NUMOF) { #if defined (CPU_ATMEGA2560) || defined (CPU_ATMEGA1281) PRR0 &= ~(1 << PRTWI); #endif #ifdef CPU_ATMEGA328P PRR &= ~(1 << PRTWI); #endif } } void i2c_poweroff(i2c_t dev) { if (dev < I2C_NUMOF) { #if defined (CPU_ATMEGA2560) || defined (CPU_ATMEGA1281) PRR0 |= (1 << PRTWI); #endif #ifdef CPU_ATMEGA328P PRR |= (1 << PRTWI); #endif } } static int _start(uint8_t address, uint8_t rw_flag) { /* Reset I2C Interrupt Flag and transmit START condition */ TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); DEBUG("START condition transmitted\n"); /* Wait for TWINT Flag set. This indicates that the START has been transmitted, and ACK/NACK has been received.*/ while (!(TWCR & (1 << TWINT))) {} /* Check value of TWI Status Register. Mask prescaler bits. If status different from START go to ERROR */ if ((TWSR & 0xF8) != MT_START) { DEBUG("I2C Status Register is different from START\n"); _stop(); return -1; } else DEBUG("I2C Status Register is: START\n"); /* Load ADDRESS and R/W Flag into TWDR Register. Clear TWINT bit in TWCR to start transmission of ADDRESS */ TWDR = (address << 1) | rw_flag; TWCR = (1 << TWINT) | (1 << TWEN); DEBUG("ADDRESS and FLAG transmitted\n"); /* Wait for TWINT Flag set. This indicates that ADDRESS has been transmitted.*/ while (!(TWCR & (1 << TWINT))) {} /* Check value of TWI Status Register. Mask prescaler bits. If status different from ADDRESS ACK go to ERROR */ if ((TWSR & 0xF8) == MT_ADDRESS_ACK) DEBUG("ACK has been received for ADDRESS (write)\n"); else if ((TWSR & 0xF8) == MR_ADDRESS_ACK) DEBUG("ACK has been received for ADDRESS (read)\n"); else { DEBUG("NOT ACK has been received for ADDRESS\n"); _stop(); return -2; } return 0; } static int _write(const uint8_t *data, int length) { int i = 0; for (i = 0; i < length; i++) { /* Load DATA into TWDR Register. Clear TWINT bit in TWCR to start transmission of data */ TWDR = data[i]; TWCR = (1 << TWINT) | (1 << TWEN); DEBUG("Byte %i transmitted\n", i+1); /* Wait for TWINT Flag set. This indicates that DATA has been transmitted.*/ while (!(TWCR & (1 << TWINT))) {} /* Check value of TWI Status Register. Mask prescaler bits. If status different from MT_DATA_ACK, return number of transmitted bytes */ if ((TWSR & 0xF8) != MT_DATA_ACK) { DEBUG("NACK has been received for BYTE %i\n", i+1); return i; } else DEBUG("ACK has been received for BYTE %i\n", i+1); } return i; } static void _stop(void) { /* Reset I2C Interrupt Flag and transmit STOP condition */ TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); while (TWCR & (1 << TWSTO)) {} DEBUG("STOP condition transmitted\n"); TWCR = 0; } #endif /* I2C_NUMOF */