diff --git a/os/hal/platforms/AVR/i2c_lld.c b/os/hal/platforms/AVR/i2c_lld.c new file mode 100644 index 000000000..4795031ba --- /dev/null +++ b/os/hal/platforms/AVR/i2c_lld.c @@ -0,0 +1,293 @@ +/* + ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010, + 2011,2012 Giovanni Di Sirio. + + This file is part of ChibiOS/RT. + + ChibiOS/RT 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 3 of the License, or + (at your option) any later version. + + ChibiOS/RT 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, see . +*/ + +/** + * @file AVR/i2c_lld.c + * @brief AVR I2C subsystem low level driver source. + * + * @addtogroup I2C + * @{ + */ + +#include "ch.h" +#include "hal.h" + +#if HAL_USE_I2C || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** @brief I2C driver identifier.*/ +#if USE_AVR_I2C || defined(__DOXYGEN__) +I2CDriver I2CD; +#endif + +/*===========================================================================*/ +/* Driver local variables. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +/** + * @brief Wakes up the waiting thread. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * @param[in] msg wakeup message + * + * @notapi + */ +#define wakeup_isr(i2cp, msg) { \ + chSysLockFromIsr(); \ + if ((i2cp)->thread != NULL) { \ + Thread *tp = (i2cp)->thread; \ + (i2cp)->thread = NULL; \ + tp->p_u.rdymsg = (msg); \ + chSchReadyI(tp); \ + } \ + chSysUnlockFromIsr(); \ +} + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +#if USE_AVR_I2C || defined(__DOXYGEN__) +/** + * @brief I2C event interrupt handler. + * + * @notapi + */ +CH_IRQ_HANDLER(TWI_vect) { + CH_IRQ_PROLOGUE(); + + I2CDriver *i2cp = &I2CD; + + switch (TWSR & 0xF8) { + case TWI_START: + case TWI_REPEAT_START: + TWDR = (i2cp->addr << 1); + if ((i2cp->txbuf == NULL) || (i2cp->txbytes == 0) || (i2cp->txidx == i2cp->txbytes)) { + TWDR |= 0x01; + } + TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + break; + case TWI_MASTER_TX_ADDR_ACK: + case TWI_MASTER_TX_DATA_ACK: + if (i2cp->txidx < i2cp->txbytes) { + TWDR = i2cp->txbuf[i2cp->txidx++]; + TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + } else { + if (i2cp->rxbuf && i2cp->rxbytes) { + TWCR = ((1 << TWSTA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + } else { + TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN)); + wakeup_isr(i2cp, RDY_OK); + } + } + break; + case TWI_MASTER_RX_ADDR_ACK: + if (i2cp->rxidx == (i2cp->rxbytes - 1)) { + TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + } else { + TWCR = ((1 << TWEA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + } + break; + case TWI_MASTER_RX_DATA_ACK: + i2cp->rxbuf[i2cp->rxidx++] = TWDR; + if (i2cp->rxidx == (i2cp->rxbytes - 1)) { + TWCR = ((1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + } else { + TWCR = ((1 << TWEA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + } + break; + case TWI_MASTER_RX_DATA_NACK: + i2cp->rxbuf[i2cp->rxidx] = TWDR; + TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN)); + wakeup_isr(i2cp, RDY_OK); + case TWI_MASTER_TX_ADDR_NACK: + case TWI_MASTER_TX_DATA_NACK: + case TWI_MASTER_RX_ADDR_NACK: + i2cp->errors |= I2CD_ACK_FAILURE; + break; + case TWI_ARBITRATION_LOST: + i2cp->errors |= I2CD_ARBITRATION_LOST; + break; + case TWI_BUS_ERROR: + i2cp->errors |= I2CD_BUS_ERROR; + break; + default: + /* FIXME: only gets here if there are other MASTERs in the bus */ + TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN)); + wakeup_isr(i2cp, RDY_RESET); + } + + if (i2cp->errors != I2CD_NO_ERROR) { + TWCR = ((1 << TWSTO) | (1 << TWINT) | (1 << TWEN)); + wakeup_isr(i2cp, RDY_RESET); + } + + CH_IRQ_EPILOGUE(); +} +#endif /* USE_AVR_I2C */ + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level I2C driver initialization. + * + * @notapi + */ +void i2c_lld_init(void) { + i2cObjectInit(&I2CD); +} + +/** + * @brief Configures and activates the I2C peripheral. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +void i2c_lld_start(I2CDriver *i2cp) { + + /* TODO: Test TWI without external pull-ups (use internal) */ + + /* Configure prescaler to 1 */ + TWSR &= 0xF8; + + /* Configure baudrate */ + TWBR = ((F_CPU / i2cp->config->clock_speed) - 16) / 2; +} + +/** + * @brief Deactivates the I2C peripheral. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +void i2c_lld_stop(I2CDriver *i2cp) { + + if (i2cp->state != I2C_STOP) { + /* Disable TWI subsystem and stop all operations */ + TWCR &= ~(1 << TWEN); + } +} + +/** + * @brief Receives data via the I2C bus as master. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * @param[in] addr slave device address + * @param[out] rxbuf pointer to the receive buffer + * @param[in] rxbytes number of bytes to be received + * @param[in] timeout the number of ticks before the operation timeouts, + * the following special values are allowed: + * - @a TIME_INFINITE no timeout. + * . + * @return The operation status. + * @retval RDY_OK if the function succeeded. + * @retval RDY_RESET if one or more I2C errors occurred, the errors can + * be retrieved using @p i2cGetErrors(). + * @retval RDY_TIMEOUT if a timeout occurred before operation end. After a + * timeout the driver must be stopped and restarted + * because the bus is in an uncertain state. + * + * @notapi + */ +msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout) { + i2cp->addr = addr; + i2cp->txbuf = NULL; + i2cp->txbytes = 0; + i2cp->txidx = 0; + i2cp->rxbuf = rxbuf; + i2cp->rxbytes = rxbytes; + i2cp->rxidx = 0; + + /* Send START */ + TWCR = ((1 << TWSTA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + + chSysLock(); + i2cp->thread = chThdSelf(); + chSchGoSleepS(THD_STATE_SUSPENDED); + chSysUnlock(); + + return chThdSelf()->p_u.rdymsg; +} + +/** + * @brief Transmits data via the I2C bus as master. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * @param[in] addr slave device address + * @param[in] txbuf pointer to the transmit buffer + * @param[in] txbytes number of bytes to be transmitted + * @param[out] rxbuf pointer to the receive buffer + * @param[in] rxbytes number of bytes to be received + * @param[in] timeout the number of ticks before the operation timeouts, + * the following special values are allowed: + * - @a TIME_INFINITE no timeout. + * . + * @return The operation status. + * @retval RDY_OK if the function succeeded. + * @retval RDY_RESET if one or more I2C errors occurred, the errors can + * be retrieved using @p i2cGetErrors(). + * @retval RDY_TIMEOUT if a timeout occurred before operation end. After a + * timeout the driver must be stopped and restarted + * because the bus is in an uncertain state. + * + * @notapi + */ +msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, + const uint8_t *txbuf, size_t txbytes, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout) { + i2cp->addr = addr; + i2cp->txbuf = txbuf; + i2cp->txbytes = txbytes; + i2cp->txidx = 0; + i2cp->rxbuf = rxbuf; + i2cp->rxbytes = rxbytes; + i2cp->rxidx = 0; + + TWCR = ((1 << TWSTA) | (1 << TWINT) | (1 << TWEN) | (1 << TWIE)); + + chSysLock(); + i2cp->thread = chThdSelf(); + chSchGoSleepS(THD_STATE_SUSPENDED); + chSysUnlock(); + + return chThdSelf()->p_u.rdymsg; +} + +#endif /* HAL_USE_I2C */ + +/** @} */ diff --git a/os/hal/platforms/AVR/i2c_lld.h b/os/hal/platforms/AVR/i2c_lld.h new file mode 100644 index 000000000..6fae3b67f --- /dev/null +++ b/os/hal/platforms/AVR/i2c_lld.h @@ -0,0 +1,227 @@ +/* + ChibiOS/RT - Copyright (C) 2006,2007,2008,2009,2010, + 2011,2012 Giovanni Di Sirio. + + This file is part of ChibiOS/RT. + + ChibiOS/RT 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 3 of the License, or + (at your option) any later version. + + ChibiOS/RT 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, see . +*/ + +/** + * @file AVR/i2c_lld.h + * @brief AVR I2C subsystem low level driver header. + * + * @addtogroup I2C + * @{ + */ + +#ifndef _I2C_LLD_H_ +#define _I2C_LLD_H_ + +#if HAL_USE_I2C || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/** START transmitted */ +#define TWI_START 0x08 +/** Repeated START transmitted */ +#define TWI_REPEAT_START 0x10 +/** Arbitration Lost */ +#define TWI_ARBITRATION_LOST 0x38 +/** Bus errors */ +#define TWI_BUS_ERROR 0x00 + +/** SLA+W transmitted with ACK response */ +#define TWI_MASTER_TX_ADDR_ACK 0x18 +/** SLA+W transmitted with NACK response */ +#define TWI_MASTER_TX_ADDR_NACK 0x20 +/** DATA transmitted with ACK response */ +#define TWI_MASTER_TX_DATA_ACK 0x28 +/** DATA transmitted with NACK response */ +#define TWI_MASTER_TX_DATA_NACK 0x30 + +/** SLA+R transmitted with ACK response */ +#define TWI_MASTER_RX_ADDR_ACK 0x40 +/** SLA+R transmitted with NACK response */ +#define TWI_MASTER_RX_ADDR_NACK 0x48 +/** DATA received with ACK response */ +#define TWI_MASTER_RX_DATA_ACK 0x50 +/** DATA received with NACK response */ +#define TWI_MASTER_RX_DATA_NACK 0x58 + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name Configuration options + * @{ + */ +/** + * @brief I2C driver enable switch. + * @details If set to @p TRUE the support for I2C is included. + * @note The default is @p FALSE. + */ +#if !defined(USE_AVR_I2C) || defined(__DOXYGEN__) +#define USE_AVR_I2C FALSE +#endif + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Type representing I2C address. + */ +typedef uint8_t i2caddr_t; + +/** + * @brief I2C Driver condition flags type. + */ +typedef uint8_t i2cflags_t; + +/** + * @brief Driver configuration structure. + * @note Implementations may extend this structure to contain more, + * architecture dependent, fields. + */ +typedef struct { + + /** + * @brief Specifies the I2C clock frequency. + */ + uint32_t clock_speed; + +} I2CConfig; + +/** + * @brief Structure representing an I2C driver. + */ +struct I2CDriver { + /** + * @brief Driver state. + */ + i2cstate_t state; + /** + * @brief Current configuration data. + */ + const I2CConfig *config; + /** + * @brief Error flags. + */ + i2cflags_t errors; +#if I2C_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__) +#if CH_USE_MUTEXES || defined(__DOXYGEN__) + /** + * @brief Mutex protecting the bus. + */ + Mutex mutex; +#elif CH_USE_SEMAPHORES + Semaphore semaphore; +#endif +#endif /* I2C_USE_MUTUAL_EXCLUSION */ +#if defined(I2C_DRIVER_EXT_FIELDS) + I2C_DRIVER_EXT_FIELDS +#endif + /* End of the mandatory fields.*/ + /** + * @brief Thread waiting for I/O completion. + */ + Thread *thread; + /** + * @brief Address of slave device + */ + i2caddr_t addr; + /** + * @brief Pointer to the buffer with data to send. + */ + const uint8_t *txbuf; + /** + * @brief Number of bytes of data to send. + */ + size_t txbytes; + /** + * @brief Current index in buffer when sending data. + */ + size_t txidx; + /** + * @brief Pointer to the buffer to put received data. + */ + uint8_t *rxbuf; + /** + * @brief Number of bytes of data to receive. + */ + size_t rxbytes; + /** + * @brief Current index in buffer when receiving data. + */ + size_t rxidx; +}; + +/** + * @brief Type of a structure representing an I2C driver. + */ +typedef struct I2CDriver I2CDriver; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Get errors from I2C driver. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +#define i2c_lld_get_errors(i2cp) ((i2cp)->errors) + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if !defined(__DOXYGEN__) +#if USE_AVR_I2C +extern I2CDriver I2CD; +#endif +#endif /* !defined(__DOXYGEN__) */ + +#ifdef __cplusplus +extern "C" { +#endif + void i2c_lld_init(void); + void i2c_lld_start(I2CDriver *i2cp); + void i2c_lld_stop(I2CDriver *i2cp); + msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, + const uint8_t *txbuf, size_t txbytes, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout); + msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_I2C */ + +#endif /* _I2C_LLD_H_ */ + +/** @} */ diff --git a/os/hal/platforms/AVR/platform.dox b/os/hal/platforms/AVR/platform.dox index c80e61c2e..a9bb09c04 100644 --- a/os/hal/platforms/AVR/platform.dox +++ b/os/hal/platforms/AVR/platform.dox @@ -100,3 +100,15 @@ * . * @ingroup AVR_DRIVERS */ + +/** + * @defgroup AVR_I2C AVR I2C Support + * @details The AVR I2C driver uses the TWI peripheral in an interrupt + * driven, implementation. + * + * @section avr_i2c Supported HW resources + * The i2c driver can support the following hardware resource: + * - I2C. + * . + * @ingroup AVR_DRIVERS + */ diff --git a/os/hal/platforms/AVR/platform.mk b/os/hal/platforms/AVR/platform.mk index e31413c79..86a2c2eba 100644 --- a/os/hal/platforms/AVR/platform.mk +++ b/os/hal/platforms/AVR/platform.mk @@ -1,7 +1,8 @@ # List of all the AVR platform files. PLATFORMSRC = ${CHIBIOS}/os/hal/platforms/AVR/hal_lld.c \ ${CHIBIOS}/os/hal/platforms/AVR/pal_lld.c \ - ${CHIBIOS}/os/hal/platforms/AVR/serial_lld.c + ${CHIBIOS}/os/hal/platforms/AVR/serial_lld.c \ + ${CHIBIOS}/os/hal/platforms/AVR/i2c_lld.c # Required include directories PLATFORMINC = ${CHIBIOS}/os/hal/platforms/AVR diff --git a/readme.txt b/readme.txt index c9a2e8f17..25ab67dbe 100644 --- a/readme.txt +++ b/readme.txt @@ -163,6 +163,7 @@ to 2.4.1). - NEW: Added demo for Arduino Mega, contributed by Fabio Utzig. - NEW: Added support for ATmega1280, contributed by Fabio Utzig. +- NEW: Added I2C driver for AVR, contributed by Fabio Utzig. - NEW: Added FatFs demo for the Olimex STM32-P107 board. - NEW: Added support for the Olimex STM32-E407 board. Added an integrated demo including USB-CDC, lwIP with web server, FatFs and shell, all running