diff --git a/boards/arduino-atmega-common/Makefile.features b/boards/arduino-atmega-common/Makefile.features index 4a18a5a3ce..92b1f77884 100644 --- a/boards/arduino-atmega-common/Makefile.features +++ b/boards/arduino-atmega-common/Makefile.features @@ -1,5 +1,6 @@ # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_gpio +FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/arduino-atmega-common/include/periph_conf.h b/boards/arduino-atmega-common/include/periph_conf.h index 7a6eed661e..12043bfa66 100644 --- a/boards/arduino-atmega-common/include/periph_conf.h +++ b/boards/arduino-atmega-common/include/periph_conf.h @@ -143,6 +143,14 @@ extern "C" { #endif /** @} */ +/** + * @name I2C configuration + * @{ + */ +#define I2C_NUMOF 1 +#define I2C_0_EN 1 +#define I2C_IRQ_PRIO 1 + #ifdef __cplusplus } #endif diff --git a/boards/waspmote-pro/Makefile.features b/boards/waspmote-pro/Makefile.features index 8131d940b2..5756565617 100644 --- a/boards/waspmote-pro/Makefile.features +++ b/boards/waspmote-pro/Makefile.features @@ -1,5 +1,6 @@ # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_gpio +FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/waspmote-pro/include/periph_conf.h b/boards/waspmote-pro/include/periph_conf.h index a6c46a9c7e..25f06d7ffa 100644 --- a/boards/waspmote-pro/include/periph_conf.h +++ b/boards/waspmote-pro/include/periph_conf.h @@ -104,6 +104,14 @@ extern "C" { #define MEGA_PRR PRR0 /* Power Reduction Register */ /** @} */ +/** + * @name I2C configuration + * @{ + */ +#define I2C_NUMOF 1 +#define I2C_0_EN 1 +#define I2C_IRQ_PRIO 1 + #ifdef __cplusplus } #endif diff --git a/cpu/atmega_common/periph/i2c.c b/cpu/atmega_common/periph/i2c.c new file mode 100644 index 0000000000..fc7509f63f --- /dev/null +++ b/cpu/atmega_common/periph/i2c.c @@ -0,0 +1,347 @@ +/* + * 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 */