From ab61e9c03a9bba1a8d02c818a6859b9fdb4aa555 Mon Sep 17 00:00:00 2001 From: Natalia Date: Fri, 5 Apr 2019 07:56:11 +0300 Subject: [PATCH] I2C sw driver with support of multiple buses, Slow, Fast, FastPlus, and user-defined speed selection (#2465) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * I2C driver speed-up, i2c.SLOW, i2c.FAST and user-defined speed selection * - Multiple buses (up to 10) with different speeds on each bus - Standard(Slow, 100kHz), Fast(400kHz) and FastPlus(1MHz) modes or an arbitrary clock speed - Sharing SDA line over multiple I²C buses to save available pins - GPIO16 pin can be used as SCL pin, but it does not support clock stretching and selected bus will be limited to FAST speed. * Dynamic memory allocation, error checks, simplification, timing tweaks. * Separated the code of old driver for better compatibility and simplicity * Change of driver interface * Add bus status check in setup(); simplify getDC(); remove unnesessary lines in ACK read/write * Fix for moved doc file and trailing whitespaces --- app/driver/i2c_master.c | 601 ++++++++++++++++++++++++-------- app/include/driver/i2c_master.h | 78 +---- app/include/user_config.h | 16 + app/modules/i2c.c | 19 +- app/platform/cpu_esp8266.h | 6 + app/platform/platform.c | 28 +- app/platform/platform.h | 4 +- docs/modules/i2c.md | 135 +++++-- 8 files changed, 630 insertions(+), 257 deletions(-) diff --git a/app/driver/i2c_master.c b/app/driver/i2c_master.c index f8de4bc4..b3ec4869 100644 --- a/app/driver/i2c_master.c +++ b/app/driver/i2c_master.c @@ -1,20 +1,421 @@ -/****************************************************************************** - * Copyright 2013-2014 Espressif Systems (Wuxi) +/* + * ESPRESSIF MIT License * - * FileName: i2c_master.c + * Copyright (c) 2016 * - * Description: i2c master API + * Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: * - * Modification history: - * 2014/3/12, v1.0 create this file. -*******************************************************************************/ + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * Rework of original driver: Natalia Sorokina , 2018 + */ + +#include "../libc/c_stdlib.h" #include "ets_sys.h" #include "osapi.h" #include "gpio.h" +#include "user_interface.h" + +#include "cpu_esp8266.h" +#include "pin_map.h" + +#include "user_config.h" #include "driver/i2c_master.h" -#include "pin_map.h" + +#ifndef I2C_MASTER_OLD_VERSION +/****************************************************************************** +* NEW driver +* Enabled if I2C_MASTER_OLD_VERSION is not defined in user_config.h +*******************************************************************************/ +// Supports multiple i2c buses +// I2C speed in range 25kHz - 550kHz (25kHz - 1MHz if CPU at 160MHz) +// If GPIO16 is used as SCL then speed is limited to 25kHz - 400kHz +// Speed is defined for every bus separately + +// enable use GPIO16 (D0) pin as SCL line +#ifdef I2C_MASTER_GPIO16_ENABLE +#define IS_PIN16(n) ((n)==16) +// CPU_CYCLES_BETWEEN_DELAYS describes how much cpu cycles code runs +// between i2c_master_setDC() calls if delay is zero and i2c_master_set_DC_delay() +// is not being called. This is not exact value, but proportional with length of code. +// Increasing the value results in less delay and faster i2c clock speed. +#define CPU_CYCLES_BETWEEN_DELAYS 80 +// CPU_CYCLES_GPIO16 is added to CPU_CYCLES_BETWEEN_DELAYS, +// as RTC-related IO takes much more time than standard GPIOs. +// Increasing the value results in less delay and faster i2c clock speed for GPIO16. +#define CPU_CYCLES_GPIO16 90 + +#else +// If GPIO16 support is not enabled, remove GPIO16-related code during compile +// and change timing constants. +#define IS_PIN16(n) (0) +#define CPU_CYCLES_BETWEEN_DELAYS 74 +#endif //I2C_MASTER_GPIO16_ENABLE + +#define MIN_SPEED 25000 +#define MAX_NUMBER_OF_I2C NUM_I2C + +typedef struct { + uint8 last_SDA; + uint8 last_SCL; + uint8 pin_SDA; + uint8 pin_SCL; + uint32 pin_SDA_SCL_mask; + uint32 pin_SDA_mask; + uint32 pin_SCL_mask; + uint32 speed; + sint16 cycles_delay; +} i2c_master_state_t; +static i2c_master_state_t *i2c[MAX_NUMBER_OF_I2C]; + +/****************************************************************************** + * FunctionName : i2c_master_set_DC_delay + * Description : Internal used function - calculate delay for i2c_master_setDC + * Parameters : bus id + * Returns : NONE +*******************************************************************************/ +LOCAL void ICACHE_FLASH_ATTR +i2c_master_set_DC_delay(uint16 id) +{ + // [cpu cycles per half SCL clock period] - [cpu cycles that code takes to run] + i2c[id]->cycles_delay = system_get_cpu_freq() * 500000 / i2c[id]->speed - CPU_CYCLES_BETWEEN_DELAYS; + #ifdef I2C_MASTER_GPIO16_ENABLE + if(IS_PIN16(i2c[id]->pin_SCL)){ //if GPIO16 + i2c[id]->cycles_delay -= CPU_CYCLES_GPIO16; //decrease delay + } + #endif //I2C_MASTER_GPIO16_ENABLE + if(i2c[id]->cycles_delay < 0){ + i2c[id]->cycles_delay = 0; + } +} +/****************************************************************************** + * FunctionName : i2c_master_wait_cpu_cycles + * Description : Internal used function - wait for given count of cpu cycles + * Parameters : sint16 cycles_delay + * Returns : NONE +*******************************************************************************/ +static inline void i2c_master_wait_cpu_cycles(sint16 cycles_delay) +{ + uint32 cycles_start; + uint32 cycles_curr; + // uses special 'ccount' register which is increased every CPU cycle + // to make precise delay + asm volatile("rsr %0, ccount":"=a"(cycles_start)); + do{ + asm volatile("rsr %0, ccount":"=a"(cycles_curr)); + } while (cycles_curr - cycles_start < cycles_delay); +} + +/****************************************************************************** + * FunctionName : i2c_master_wait_gpio_SCL_high + * Description : Internal used function - wait until SCL line in a high state + (slave device may hold SCL line low until it is ready to proceed) + * Parameters : bus id + * Returns : NONE +*******************************************************************************/ +static inline void i2c_master_wait_gpio_SCL_high(uint16 id) +{ + // retrieves bitmask of all GPIOs from memory-mapped gpio register and exits if SCL bit is set + // equivalent, but slow variant: + // while(!(READ_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_IN_ADDRESS) & i2c[id]->pin_SCL_mask)) {}; + // even slower: while (!(gpio_input_get() & i2c[id]->pin_SCL_mask)) {}; + asm volatile("l_wait:" + "l16ui %0, %[gpio_in_addr], 0;" //read gpio state into register %0 + "memw;" //wait for read completion + "bnall %0, %[gpio_SCL_mask], l_wait;" // test if SCL bit not set + ::[gpio_SCL_mask] "r" (i2c[id]->pin_SCL_mask), + [gpio_in_addr] "r" (PERIPHS_GPIO_BASEADDR + GPIO_IN_ADDRESS) + ); +} + +/****************************************************************************** + * FunctionName : i2c_master_setDC + * Description : Internal used function - + * set i2c SDA and SCL bit value for half clock cycle + * Parameters : bus id, uint8 SDA, uint8 SCL + * Returns : NONE +*******************************************************************************/ +LOCAL void ICACHE_FLASH_ATTR +i2c_master_setDC(uint16 id, uint8 SDA, uint8 SCL) +{ + uint32 this_SDA_SCL_set_mask; + uint32 this_SDA_SCL_clear_mask; + i2c[id]->last_SDA = SDA; + i2c[id]->last_SCL = SCL; + + if(i2c[id]->cycles_delay > 0){ + i2c_master_wait_cpu_cycles(i2c[id]->cycles_delay); + } + if (IS_PIN16(i2c[id]->pin_SCL)){ //GPIO16 wired differently, it has it's own register address + WRITE_PERI_REG(RTC_GPIO_OUT, SCL); // write SCL value + if(1 == SDA){ + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, i2c[id]->pin_SDA_mask); //SDA = 1 + }else{ + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, i2c[id]->pin_SDA_mask); // SDA = 0 + } + if(1 == SCL){ //clock stretching, GPIO16 version + while(!(READ_PERI_REG(RTC_GPIO_IN_DATA) & 1)) {}; //read SCL value until SCL goes high + }else{ + // dummy read operation and empty CPU cycles to maintain equal times for low and high state + READ_PERI_REG(RTC_GPIO_IN_DATA) & 1; asm volatile("nop;nop;nop;nop;"); + } + } + else{ + this_SDA_SCL_set_mask = (SDA << i2c[id]->pin_SDA) | (SCL << i2c[id]->pin_SCL); + this_SDA_SCL_clear_mask = i2c[id]->pin_SDA_SCL_mask ^ this_SDA_SCL_set_mask; + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, this_SDA_SCL_clear_mask); + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, this_SDA_SCL_set_mask); + if(1 == SCL) { //clock stretching + i2c_master_wait_gpio_SCL_high(id); + }else{ + asm volatile("nop;nop;nop;"); // empty CPU cycles to maintain equal times for low and high state + } + } +} + +/****************************************************************************** + * FunctionName : i2c_master_getDC + * Description : Internal used function - + * get i2c SDA bit value + * Parameters : bus id + * Returns : uint8 - SDA bit value +*******************************************************************************/ +static inline uint8 ICACHE_FLASH_ATTR +i2c_master_getDC(uint16 id) +{ + return (READ_PERI_REG(PERIPHS_GPIO_BASEADDR + GPIO_IN_ADDRESS) >> i2c[id]->pin_SDA) & 1; +} + +/****************************************************************************** + * FunctionName : i2c_master_configured + * Description : checks if i2c bus is configured + * Parameters : bus id + * Returns : boolean value, true if configured +*******************************************************************************/ +bool ICACHE_FLASH_ATTR +i2c_master_configured(uint16 id){ + return !(NULL == i2c[id]); +} + +/****************************************************************************** + * FunctionName : i2c_master_init + * Description : initialize I2C bus to enable i2c operations + (reset state of all slave devices) + * Parameters : bus id + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +i2c_master_init(uint16 id) +{ + uint8 i; + + i2c_master_setDC(id, 1, 0); + + // when SCL = 0, toggle SDA to clear up + i2c_master_setDC(id, 0, 0) ; + i2c_master_setDC(id, 1, 0) ; + + // set data_cnt to max value + for (i = 0; i < 28; i++) { + i2c_master_setDC(id, 1, 0); + i2c_master_setDC(id, 1, 1); + } + + // reset all + i2c_master_stop(id); + return; +} + +/****************************************************************************** + * FunctionName : i2c_master_setup + * Description : Initializes and configures the driver on given bus ID + * Parameters : bus id + * Returns : configured speed +*******************************************************************************/ +uint32 ICACHE_FLASH_ATTR +i2c_master_setup(uint16 id, uint8 sda, uint8 scl, uint32 speed) +{ + if(NULL == i2c[id]){ + i2c[id] = (i2c_master_state_t*) c_malloc(sizeof(i2c_master_state_t)); + } + if(NULL == i2c[id]){ // if malloc failed + return 0; + } + i2c[id]->last_SDA = 1; //default idle state + i2c[id]->last_SCL = 1; + i2c[id]->pin_SDA = pin_num[sda]; + i2c[id]->pin_SCL = pin_num[scl]; + i2c[id]->pin_SDA_mask = 1 << i2c[id]->pin_SDA; + i2c[id]->pin_SCL_mask = 1 << i2c[id]->pin_SCL; + i2c[id]->pin_SDA_SCL_mask = i2c[id]->pin_SDA_mask | i2c[id]->pin_SCL_mask; + i2c[id]->speed = speed; + i2c[id]->cycles_delay = 0; + + if(i2c[id]->speed < MIN_SPEED){ + i2c[id]->speed = MIN_SPEED; + } + i2c_master_set_DC_delay(id); // recalibrate clock + + ETS_GPIO_INTR_DISABLE(); //disable gpio interrupts + + if (IS_PIN16(i2c[id]->pin_SCL)){ //if GPIO16 + CLEAR_PERI_REG_MASK(PAD_XPD_DCDC_CONF, 0x43); //disable all functions for XPD_DCDC + SET_PERI_REG_MASK(PAD_XPD_DCDC_CONF, 0x1); // select function RTC_GPIO0 for pin XPD_DCDC + CLEAR_PERI_REG_MASK(RTC_GPIO_CONF, 0x1); //mux configuration for out enable + SET_PERI_REG_MASK(RTC_GPIO_ENABLE, 0x1); //out enable + SET_PERI_REG_MASK(RTC_GPIO_OUT, 0x1); // set SCL high + } + else{ + PIN_FUNC_SELECT(pin_mux[scl], pin_func[scl]); + SET_PERI_REG_MASK(PERIPHS_GPIO_BASEADDR + GPIO_PIN_ADDR(GPIO_ID_PIN(i2c[id]->pin_SCL)), + GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_ENABLE)); //open drain + gpio_output_set(i2c[id]->pin_SCL_mask, 0, i2c[id]->pin_SCL_mask, 0); //enable and set high + } + PIN_FUNC_SELECT(pin_mux[sda], pin_func[sda]); + SET_PERI_REG_MASK(PERIPHS_GPIO_BASEADDR + GPIO_PIN_ADDR(GPIO_ID_PIN(i2c[id]->pin_SDA)), + GPIO_PIN_PAD_DRIVER_SET(GPIO_PAD_DRIVER_ENABLE)); //open drain + gpio_output_set(i2c[id]->pin_SDA_mask, 0, i2c[id]->pin_SDA_mask, 0); //enable and set high + + ETS_GPIO_INTR_ENABLE(); //enable gpio interrupts + + if (! (gpio_input_get() ^ i2c[id]->pin_SCL_mask)){ //SCL is in low state, bus failure + return 0; + } + i2c_master_init(id); + if (! (gpio_input_get() ^ i2c[id]->pin_SDA_mask)){ //SDA is in low state, bus failure + return 0; + } + return i2c[id]->speed; +} + +/****************************************************************************** + * FunctionName : i2c_master_start + * Description : set i2c to send state + * Parameters : bus id + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +i2c_master_start(uint16 id) +{ + i2c_master_set_DC_delay(id); // recalibrate clock + i2c_master_setDC(id, 1, i2c[id]->last_SCL); + i2c_master_setDC(id, 1, 1); + i2c_master_setDC(id, 0, 1); +} + +/****************************************************************************** + * FunctionName : i2c_master_stop + * Description : set i2c to stop sending state + * Parameters : bus id + * Returns : NONE +*******************************************************************************/ +void ICACHE_FLASH_ATTR +i2c_master_stop(uint16 id) +{ + i2c_master_setDC(id, 0, i2c[id]->last_SCL); + i2c_master_setDC(id, 0, 1); + i2c_master_setDC(id, 1, 1); +} + +/****************************************************************************** + * FunctionName : i2c_master_readByte + * Description : read Byte from i2c bus + * Parameters : bus id + * Returns : uint8 - readed value +*******************************************************************************/ +uint8 ICACHE_FLASH_ATTR +i2c_master_readByte(uint16 id, sint16 ack) +{ + uint8 retVal = 0; + uint8 k; + sint8 i; + //invert and clamp ACK to 0/1, because ACK == 1 for i2c means SDA in low state + uint8 ackLevel = (ack ? 0 : 1); + + i2c_master_setDC(id, i2c[id]->last_SDA, 0); + i2c_master_setDC(id, 1, 0); + for (i = 7; i >= 0; i--) { + i2c_master_setDC(id, 1, 1); + k = i2c_master_getDC(id); + i2c_master_setDC(id, 1, 0); // unnecessary in last iteration + k <<= i; + retVal |= k; + } + // set ACK + i2c_master_setDC(id, ackLevel, 0); + i2c_master_setDC(id, ackLevel, 1); + i2c_master_setDC(id, 1, 0); + return retVal; +} + +/****************************************************************************** + * FunctionName : i2c_master_writeByte + * Description : write wrdata value(one byte) into i2c + * Parameters : bus id, uint8 wrdata - write value + * Returns : NONE +*******************************************************************************/ +uint8 ICACHE_FLASH_ATTR +i2c_master_writeByte(uint16 id, uint8 wrdata) +{ + uint8 dat; + sint8 i; + uint8 retVal; + + i2c_master_setDC(id, i2c[id]->last_SDA, 0); + for (i = 7; i >= 0; i--) { + dat = (wrdata >> i) & 1; + i2c_master_setDC(id, dat, 0); + i2c_master_setDC(id, dat, 1); + } + //get ACK + i2c_master_setDC(id, 1, 0); + i2c_master_setDC(id, 1, 1); + retVal = i2c_master_getDC(id); + i2c_master_setDC(id, 1, 0); + return ! retVal; +} + + + +#else // if defined I2C_MASTER_OLD_VERSION +/****************************************************************************** +* OLD driver +* Enabled when I2C_MASTER_OLD_VERSION is defined in user_config.h +*******************************************************************************/ + +#define I2C_MASTER_SDA_MUX (pin_mux[sda]) +#define I2C_MASTER_SCL_MUX (pin_mux[scl]) +#define I2C_MASTER_SDA_GPIO (pinSDA) +#define I2C_MASTER_SCL_GPIO (pinSCL) +#define I2C_MASTER_SDA_FUNC (pin_func[sda]) +#define I2C_MASTER_SCL_FUNC (pin_func[scl]) +#define I2C_MASTER_SDA_HIGH_SCL_HIGH() \ + gpio_output_set(1<= 0; i--) { dat = wrdata >> i; i2c_master_setDC(dat, 0); - i2c_master_wait(5); i2c_master_setDC(dat, 1); - i2c_master_wait(5); if (i == 0) { i2c_master_wait(3); //// } i2c_master_setDC(dat, 0); - i2c_master_wait(5); } + // get ACK + i2c_master_setDC(m_nLastSDA, 0); + i2c_master_setDC(1, 0); + i2c_master_setDC(1, 1); + retVal = i2c_master_getDC(); + i2c_master_wait(5); + i2c_master_setDC(1, 0); + return ! retVal; } +#endif diff --git a/app/include/driver/i2c_master.h b/app/include/driver/i2c_master.h index 18635d5f..5a9eed19 100644 --- a/app/include/driver/i2c_master.h +++ b/app/include/driver/i2c_master.h @@ -1,74 +1,12 @@ #ifndef __I2C_MASTER_H__ #define __I2C_MASTER_H__ -#define I2C_MASTER_SDA_MUX (pin_mux[sda]) -#define I2C_MASTER_SCL_MUX (pin_mux[scl]) -#define I2C_MASTER_SDA_GPIO (pinSDA) -#define I2C_MASTER_SCL_GPIO (pinSCL) -#define I2C_MASTER_SDA_FUNC (pin_func[sda]) -#define I2C_MASTER_SCL_FUNC (pin_func[scl]) +uint32 i2c_master_setup(uint16 id, uint8 sda, uint8 scl, uint32 speed); +void i2c_master_init(uint16 id); +bool i2c_master_configured(uint16 id); +void i2c_master_stop(uint16 id); +void i2c_master_start(uint16 id); +uint8 i2c_master_readByte(uint16 id, sint16 ack); +uint8 i2c_master_writeByte(uint16 id, uint8 wrdata); -// #define I2C_MASTER_SDA_MUX PERIPHS_IO_MUX_GPIO2_U -// #define I2C_MASTER_SCL_MUX PERIPHS_IO_MUX_MTDO_U -// #define I2C_MASTER_SDA_GPIO 2 -// #define I2C_MASTER_SCL_GPIO 15 -// #define I2C_MASTER_SDA_FUNC FUNC_GPIO2 -// #define I2C_MASTER_SCL_FUNC FUNC_GPIO15 - -// #define I2C_MASTER_SDA_MUX PERIPHS_IO_MUX_GPIO2_U -// #define I2C_MASTER_SCL_MUX PERIPHS_IO_MUX_MTMS_U -// #define I2C_MASTER_SDA_GPIO 2 -// #define I2C_MASTER_SCL_GPIO 14 -// #define I2C_MASTER_SDA_FUNC FUNC_GPIO2 -// #define I2C_MASTER_SCL_FUNC FUNC_GPIO14 - -//#define I2C_MASTER_SDA_MUX PERIPHS_IO_MUX_GPIO2_U -//#define I2C_MASTER_SCL_MUX PERIPHS_IO_MUX_GPIO0_U -//#define I2C_MASTER_SDA_GPIO 2 -//#define I2C_MASTER_SCL_GPIO 0 -//#define I2C_MASTER_SDA_FUNC FUNC_GPIO2 -//#define I2C_MASTER_SCL_FUNC FUNC_GPIO0 - -#if 0 -#define I2C_MASTER_GPIO_SET(pin) \ - gpio_output_set(1<