diff --git a/cpu/stm32l0/include/periph_cpu.h b/cpu/stm32l0/include/periph_cpu.h index bb4832e282..9c5df6fc69 100644 --- a/cpu/stm32l0/include/periph_cpu.h +++ b/cpu/stm32l0/include/periph_cpu.h @@ -73,6 +73,18 @@ typedef struct { uint8_t chan; /**< DAC device used for this line */ } dac_conf_t; +/** + * @brief I2C configuration data structure + */ +typedef struct { + I2C_TypeDef *dev; /**< i2c device */ + gpio_t scl; /**< scl pin number */ + gpio_t sda; /**< sda pin number */ + gpio_mode_t pin_mode; /**< with or without pull resistor */ + gpio_af_t af; /**< I2C alternate function value */ + uint8_t ev_irqn; /**< event IRQ */ +} i2c_conf_t; + #ifdef __cplusplus } #endif diff --git a/cpu/stm32l0/periph/i2c.c b/cpu/stm32l0/periph/i2c.c new file mode 100644 index 0000000000..cc9ec4f6de --- /dev/null +++ b/cpu/stm32l0/periph/i2c.c @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2017 we-sens.com + * + * 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 + * + * @note This implementation only implements the 7-bit addressing polling mode (for now + * interrupt mode is not available) + * + * @author Aurélien Fillau + * + * @} + */ + +#include + +#include "cpu.h" +#include "mutex.h" +#include "periph/i2c.h" +#include "periph/gpio.h" +#include "periph_conf.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* guard file in case no I2C device is defined */ +#if I2C_NUMOF + +/* static function definitions */ +static void _i2c_init(I2C_TypeDef *i2c, uint32_t presc, uint32_t scll, + uint32_t sclh, uint32_t sdadel, uint32_t scldel, + uint32_t timing); +static void _start(I2C_TypeDef *dev, uint8_t address, uint8_t length, uint8_t rw_flag); +static inline void _read(I2C_TypeDef *dev, uint8_t *data, int length); +static inline void _write(I2C_TypeDef *i2c, const uint8_t *data, int length); +static inline void _stop(I2C_TypeDef *i2c); + +/** + * @brief Array holding one pre-initialized mutex for each I2C device + */ +static mutex_t locks[] = { +#if I2C_0_EN + [I2C_0] = MUTEX_INIT, +#endif +#if I2C_1_EN + [I2C_1] = MUTEX_INIT, +#endif +#if I2C_2_EN + [I2C_2] = MUTEX_INIT, +#endif +#if I2C_3_EN + [I2C_3] = MUTEX_INIT +#endif +}; + +int i2c_init_master(i2c_t dev, i2c_speed_t speed) +{ + I2C_TypeDef *i2c; + gpio_t scl; + gpio_t sda; + uint32_t presc, scll, sclh, sdadel, scldel, timing; + + /* + * Speed configuration: + * Example values can be found in the STM32L0xx Reference manual RM0367 + * with f_I2CCLK = 16MHz + * t_SCLL: SCL low level counter + * t_SCLH: SCL high level counter + * t_SDADEL: Delay before sending SDA output + * t_SCLDEL: SCL low level during setup-time + */ + switch (speed) { + case I2C_SPEED_NORMAL: + presc = 1; + scll = 0x56; /* t_SCLL = 5.0us */ + sclh = 0x3E; /* t_SCLH = 4.0us */ + sdadel = 0x1; /* t_SDADEL = 500ns */ + scldel = 0xA; /* t_SCLDEL = 1250ns */ + break; + + case I2C_SPEED_FAST: + presc = 0; + scll = 0x2E; /* t_SCLL = 1250ns */ + sclh = 0x11; /* t_SCLH = 500ns */ + sdadel = 0x1; /* t_SDADEL = 125ns */ + scldel = 0xB; /* t_SCLDEL = 500ns */ + break; + + case I2C_SPEED_FAST_PLUS: + presc = 0; + scll = 0x6; /* t_SCLL = 875ns */ + sclh = 0x3; /* t_SCLH = 500ns */ + sdadel = 0x0; /* t_SDADEL = 0ns */ + scldel = 0x1; /* t_SCLDEL = 250ns */ + break; + + default: + return -2; + } + + /* prepare the timing register value */ + timing = ((presc << 28) | (scldel << 20) | (sdadel << 16) | (sclh << 8) | scll); + + /* read static device configuration */ + switch (dev) { +#if I2C_0_EN + case I2C_0: + i2c = I2C_0_DEV; + scl = GPIO_PIN(I2C_0_SCL_PORT, I2C_0_SCL_PIN); /* scl pin number */ + sda = GPIO_PIN(I2C_0_SDA_PORT, I2C_0_SDA_PIN); /* sda pin number */ + I2C_0_CLKEN(); + I2C_0_SCL_CLKEN(); + I2C_0_SDA_CLKEN(); + break; +#endif +#if I2C_1_EN + case I2C_1: + i2c = I2C_1_DEV; + scl = GPIO_PIN(I2C_1_SCL_PORT, I2C_1_SCL_PIN); /* scl pin number */ + sda = GPIO_PIN(I2C_1_SDA_PORT, I2C_1_SDA_PIN); /* sda pin number */ + I2C_1_CLKEN(); + I2C_1_SCL_CLKEN(); + I2C_1_SDA_CLKEN(); + break; +#endif + + default: + return -1; + } + + /* configure pins */ + gpio_init(scl, GPIO_OD_PU); + gpio_init_af(scl, GPIO_AF4); + gpio_init(sda, GPIO_OD_PU); + gpio_init_af(sda, GPIO_AF4); + + /* configure device */ + _i2c_init(i2c, presc, scll, sclh, sdadel, scldel, timing); + + return 0; +} + +static void _i2c_init(I2C_TypeDef *i2c, uint32_t presc, uint32_t scll, + uint32_t sclh, uint32_t sdadel, uint32_t scldel, + uint32_t timing) +{ + /* disable device */ + i2c->CR1 &= ~(I2C_CR1_PE); + + /* configure analog noise filter */ + i2c->CR1 |= I2C_CR1_ANFOFF; + + /* configure digital noise filter */ + i2c->CR1 |= I2C_CR1_DNF; + + /* set timing registers */ + i2c->TIMINGR = timing; + + /* configure clock stretching */ + i2c->CR1 &= ~(I2C_CR1_NOSTRETCH); + + /* enable device */ + i2c->CR1 |= I2C_CR1_PE; +} + +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) +{ + I2C_TypeDef *i2c; + + switch (dev) { +#if I2C_0_EN + case I2C_0: + i2c = I2C_0_DEV; + break; +#endif +#if I2C_1_EN + case I2C_1: + i2c = I2C_1_DEV; + break; +#endif + + default: + return -1; + } + + /* start reception and send slave address */ + _start(i2c, address, length, I2C_FLAG_READ); + + /* read the data bytes */ + _read(i2c, data, length); + + /* end transmission */ + _stop(i2c); + + 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) +{ + I2C_TypeDef *i2c; + + switch (dev) { +#if I2C_0_EN + case I2C_0: + i2c = I2C_0_DEV; + break; +#endif +#if I2C_1_EN + case I2C_1: + i2c = I2C_1_DEV; + break; +#endif + + default: + return -1; + } + + /* Check to see if the bus is busy */ + while((i2c->ISR & I2C_ISR_BUSY) == I2C_ISR_BUSY) {} + + /* send start sequence and slave address */ + _start(i2c, address, 1, I2C_FLAG_WRITE); + + /* wait for ack */ + DEBUG("Waiting for ACK\n"); + while (!(i2c->ISR & I2C_ISR_TXIS)) {} + + DEBUG("Write register to read\n"); + i2c->TXDR = reg; + + /* send repeated start sequence, read registers and end transmission */ + DEBUG("ACK received, send repeated start sequence\n"); + 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) +{ + I2C_TypeDef *i2c; + + switch (dev) { +#if I2C_0_EN + case I2C_0: + i2c = I2C_0_DEV; + break; +#endif +#if I2C_1_EN + case I2C_1: + i2c = I2C_1_DEV; + break; +#endif + + default: + return -1; + } + + /* start transmission and send slave address */ + _start(i2c, address, length, I2C_FLAG_WRITE); + + /* send out data bytes */ + _write(i2c, data, length); + + /* end transmission */ + _stop(i2c); + + return length; +} + +int i2c_write_reg(i2c_t dev, uint8_t address, uint8_t reg, uint8_t data) +{ + 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) +{ + I2C_TypeDef *i2c; + + switch (dev) { +#if I2C_0_EN + case I2C_0: + i2c = I2C_0_DEV; + break; +#endif +#if I2C_1_EN + case I2C_1: + i2c = I2C_1_DEV; + break; +#endif + + default: + return -1; + } + + /* Check to see if the bus is busy */ + while((i2c->ISR & I2C_ISR_BUSY) == I2C_ISR_BUSY); + + /* start transmission and send slave address */ + /* increase length because our data is register+data */ + _start(i2c, address, length+1, I2C_FLAG_WRITE); + + /* send register number */ + DEBUG("ACK received, write reg into DR\n"); + i2c->TXDR = reg; + + /* write out data bytes */ + _write(i2c, data, length); + + /* end transmission */ + _stop(i2c); + + return length; +} + +void i2c_poweron(i2c_t dev) +{ + switch (dev) { +#if I2C_0_EN + case I2C_0: + I2C_0_CLKEN(); + break; +#endif +#if I2C_1_EN + case I2C_1: + I2C_1_CLKEN(); + break; +#endif + } +} + +void i2c_poweroff(i2c_t dev) +{ + switch (dev) { +#if I2C_0_EN + case I2C_0: + while (I2C_0_DEV->ISR & I2C_ISR_BUSY) {} + + I2C_0_CLKDIS(); + break; +#endif +#if I2C_1_EN + case I2C_1: + while (I2C_1_DEV->ISR & I2C_ISR_BUSY) {} + + I2C_0_CLKDIS(); + break; +#endif + } +} + +static void _start(I2C_TypeDef *dev, uint8_t address, uint8_t length, uint8_t rw_flag) +{ + dev->CR2 = 0; + /* set address mode to 7-bit */ + dev->CR2 &= ~(I2C_CR2_ADD10); + + /* set slave address */ + dev->CR2 &= ~(I2C_CR2_SADD); + dev->CR2 |= (address << 1); + + /* set transfer direction */ + dev->CR2 &= ~(I2C_CR2_RD_WRN); + dev->CR2 |= (rw_flag << I2C_CR2_RD_WRN_Pos); + + /* set number of bytes */ + dev->CR2 &= ~(I2C_CR2_NBYTES); + dev->CR2 |= (length << I2C_CR2_NBYTES_Pos); + + /* configure autoend configuration */ + dev->CR2 &= ~(I2C_CR2_AUTOEND); + + /* generate start condition */ + DEBUG("Generate start condition\n"); + dev->CR2 |= I2C_CR2_START; + + /* Wait for the start followed by the address to be sent */ + while (!(dev->CR2 & I2C_CR2_START)) {} +} + +static inline void _read(I2C_TypeDef *dev, uint8_t *data, int length) +{ + for (int i = 0; i < length; i++) { + /* wait for transfer to finish */ + DEBUG("Waiting for DR to be full\n"); + while (!(dev->ISR & I2C_ISR_RXNE)) {} + DEBUG("DR is now full\n"); + + /* read data from data register */ + data[i] = dev->RXDR; + DEBUG("Read byte %i from DR\n", i); + } +} + +static inline void _write(I2C_TypeDef *dev, const uint8_t *data, int length) +{ + for (int i = 0; i < length; i++) { + /* wait for ack */ + DEBUG("Waiting for ACK\n"); + while (!(dev->ISR & I2C_ISR_TXIS)) {} + + /* write data to data register */ + DEBUG("Write byte %i to DR\n", i); + dev->TXDR = data[i]; + DEBUG("Sending data\n"); + } +} + +static inline void _stop(I2C_TypeDef *dev) +{ + /* make sure transfer is complete */ + DEBUG("Wait for transfer to be complete\n"); + while (!(dev->ISR & I2C_ISR_TC)) {} + + /* send STOP condition */ + DEBUG("Generate stop condition\n"); + dev->CR2 |= I2C_CR2_STOP; +} + +#endif /* I2C_NUMOF */