|
- /*
- ------------------------------------------------------------------------------------------------------
- i2c_t3 - I2C library for Teensy 3.x & LC
-
- - (v11.0) Modified 01Dec18 by Brian (nox771 at gmail.com)
-
- Full changelog at end of file
- ------------------------------------------------------------------------------------------------------
- Copyright (c) 2013-2018, Brian (nox771 at gmail.com)
-
- Permission is hereby granted, 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:
-
- 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.
- ------------------------------------------------------------------------------------------------------
- */
-
- #if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) || \
- defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.0/3.1-3.2/LC/3.5/3.6
-
- #include "i2c_t3.h"
-
-
- // ------------------------------------------------------------------------------------------------------
- // Static inits
- //
- #define I2C_STRUCT(a1,f,c1,s,d,c2,flt,ra,smb,a2,slth,sltl,scl,sda) \
- {a1, f, c1, s, d, c2, flt, ra, smb, a2, slth, sltl, {}, 0, 0, {}, 0, 0, I2C_OP_MODE_ISR, I2C_MASTER, scl, sda, I2C_PULLUP_EXT, 100000, \
- I2C_STOP, I2C_WAITING, 0, 0, 0, 0, I2C_DMA_OFF, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0, {}, 0, 0 }
-
- struct i2cStruct i2c_t3::i2cData[] =
- {
- I2C_STRUCT(&I2C0_A1, &I2C0_F, &I2C0_C1, &I2C0_S, &I2C0_D, &I2C0_C2, &I2C0_FLT, &I2C0_RA, &I2C0_SMB, &I2C0_A2, &I2C0_SLTH, &I2C0_SLTL, 19, 18)
- #if (I2C_BUS_NUM >= 2) && defined(__MK20DX256__) // 3.1/3.2
- ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, 29, 30)
- #elif (I2C_BUS_NUM >= 2) && defined(__MKL26Z64__) // LC
- ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, 22, 23)
- #elif (I2C_BUS_NUM >= 2) && (defined(__MK64FX512__) || defined(__MK66FX1M0__)) // 3.5/3.6
- ,I2C_STRUCT(&I2C1_A1, &I2C1_F, &I2C1_C1, &I2C1_S, &I2C1_D, &I2C1_C2, &I2C1_FLT, &I2C1_RA, &I2C1_SMB, &I2C1_A2, &I2C1_SLTH, &I2C1_SLTL, 37, 38)
- #endif
- #if (I2C_BUS_NUM >= 3) && (defined(__MK64FX512__) || defined(__MK66FX1M0__)) // 3.5/3.6
- ,I2C_STRUCT(&I2C2_A1, &I2C2_F, &I2C2_C1, &I2C2_S, &I2C2_D, &I2C2_C2, &I2C2_FLT, &I2C2_RA, &I2C2_SMB, &I2C2_A2, &I2C2_SLTH, &I2C2_SLTL, 3, 4)
- #endif
- #if (I2C_BUS_NUM >= 4) && defined(__MK66FX1M0__) // 3.6
- ,I2C_STRUCT(&I2C3_A1, &I2C3_F, &I2C3_C1, &I2C3_S, &I2C3_D, &I2C3_C2, &I2C3_FLT, &I2C3_RA, &I2C3_SMB, &I2C3_A2, &I2C3_SLTH, &I2C3_SLTL, 57, 56)
- #endif
- };
-
- volatile uint8_t i2c_t3::isrActive = 0;
-
-
- // ------------------------------------------------------------------------------------------------------
- // Constructor/Destructor
- //
- i2c_t3::i2c_t3(uint8_t i2c_bus)
- {
- bus = i2c_bus;
- i2c = &i2cData[bus];
- }
- i2c_t3::~i2c_t3()
- {
- // if DMA active, delete DMA object
- if(i2c->opMode == I2C_OP_MODE_DMA)
- delete i2c->DMA;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Initialize I2C - initializes I2C as Master or address range Slave
- // return: none
- // parameters (optional parameters marked '^'):
- // mode = I2C_MASTER, I2C_SLAVE
- // address1 = 7bit slave address when configured as Slave (ignored for Master mode)
- // ^ address2 = 2nd 7bit address for specifying Slave address range (ignored for Master mode)
- // ^ pins = pins to use, can be specified as 'i2c_pins' enum,
- // or as 'SCL,SDA' pair (using any valid SCL or SDA), options are:
- // Pin Name
- // Interface Devices (deprecated) SCL SDA
- // --------- ------- -------------- ----- ----- (note: in almost all cases SCL is the
- // Wire All I2C_PINS_16_17 16 17 lower pin #, except cases marked *)
- // Wire All I2C_PINS_18_19 19 18 *
- // Wire 3.5/3.6 I2C_PINS_7_8 7 8
- // Wire 3.5/3.6 I2C_PINS_33_34 33 34
- // Wire 3.5/3.6 I2C_PINS_47_48 47 48
- // Wire1 LC I2C_PINS_22_23 22 23
- // Wire1 3.1/3.2 I2C_PINS_26_31 26 31
- // Wire1 3.1/3.2 I2C_PINS_29_30 29 30
- // Wire1 3.5/3.6 I2C_PINS_37_38 37 38
- // Wire2 3.5/3.6 I2C_PINS_3_4 3 4
- // Wire3 3.6 I2C_PINS_56_57 57 56 *
- // ^ pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT
- // ^ rate = I2C frequency to use, can be specified directly in Hz, eg. 400000 for 400kHz, or using one of the
- // following enum values (deprecated):
- // I2C_RATE_100, I2C_RATE_200, I2C_RATE_300, I2C_RATE_400,
- // I2C_RATE_600, I2C_RATE_800, I2C_RATE_1000, I2C_RATE_1200,
- // I2C_RATE_1500, I2C_RATE_1800, I2C_RATE_2000, I2C_RATE_2400,
- // I2C_RATE_2800, I2C_RATE_3000
- // ^ opMode = I2C_OP_MODE_IMM, I2C_OP_MODE_ISR, I2C_OP_MODE_DMA (ignored for Slave mode, defaults to ISR)
- //
- void i2c_t3::begin_(struct i2cStruct* i2c, uint8_t bus, i2c_mode mode, uint8_t address1, uint8_t address2,
- uint8_t pinSCL, uint8_t pinSDA, i2c_pullup pullup, uint32_t rate, i2c_op_mode opMode)
- {
- // Enable I2C internal clock
- if(bus == 0)
- SIM_SCGC4 |= SIM_SCGC4_I2C0;
- #if I2C_BUS_NUM >= 2
- if(bus == 1)
- SIM_SCGC4 |= SIM_SCGC4_I2C1;
- #endif
- #if I2C_BUS_NUM >= 3
- if(bus == 2)
- SIM_SCGC1 |= SIM_SCGC1_I2C2;
- #endif
- #if I2C_BUS_NUM >= 4
- if(bus == 3)
- SIM_SCGC1 |= SIM_SCGC1_I2C3;
- #endif
-
- i2c->currentMode = mode; // Set mode
- i2c->currentStatus = I2C_WAITING; // reset status
-
- // Set Master/Slave address
- if(i2c->currentMode == I2C_MASTER)
- {
- *(i2c->C2) = I2C_C2_HDRS; // Set high drive select
- //*(i2c->A1) = 0;
- //*(i2c->RA) = 0;
- }
- else
- {
- *(i2c->C2) = (address2) ? (I2C_C2_HDRS|I2C_C2_RMEN) // Set high drive select and range-match enable
- : I2C_C2_HDRS; // Set high drive select
- // set Slave address, if two addresses are given, setup range and put lower address in A1, higher in RA
- *(i2c->A1) = (address2) ? ((address1 < address2) ? (address1<<1) : (address2<<1))
- : (address1<<1);
- *(i2c->RA) = (address2) ? ((address1 < address2) ? (address2<<1) : (address1<<1))
- : 0;
- }
-
- // Setup pins - As noted in original TwoWire.cpp, internal 3.0/3.1/3.2 pullup is strong (about 190 ohms),
- // but it can work if other devices on bus have strong enough pulldowns.
- //
- if(!pinSCL) pinSCL = i2c->currentSCL; // if either pin specified as 0, then use current settings
- if(!pinSDA) pinSDA = i2c->currentSDA;
- pinConfigure_(i2c, bus, pinSCL, pinSDA, pullup, i2c->configuredSCL, i2c->configuredSDA);
-
- // Set I2C rate
- #if defined(__MKL26Z64__) // LC
- if(bus == 1)
- setRate_(i2c, (uint32_t)F_CPU, rate); // LC Wire1 bus uses system clock (F_CPU) instead of bus clock (F_BUS)
- else
- setRate_(i2c, (uint32_t)F_BUS, rate);
- #else
- setRate_(i2c, (uint32_t)F_BUS, rate);
- #endif
-
- // Set config registers and operating mode
- setOpMode_(i2c, bus, opMode);
- if(i2c->currentMode == I2C_MASTER)
- *(i2c->C1) = I2C_C1_IICEN; // Master - enable I2C (hold in Rx mode, intr disabled)
- else
- *(i2c->C1) = I2C_C1_IICEN|I2C_C1_IICIE; // Slave - enable I2C and interrupts
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Valid pin checks - verify if SCL or SDA pin is valid on given bus, intended for internal use only
- // return: alt setting, 0=not valid
- // parameters:
- // bus = bus number
- // pin = pin number to check
- // offset = array offset
- //
- uint8_t i2c_t3::validPin_(uint8_t bus, uint8_t pin, uint8_t offset)
- {
- for(uint8_t idx=0; idx < I2C_PINS_COUNT-1; idx++)
- if(i2c_valid_pins[idx*4] == bus && i2c_valid_pins[idx*4+offset] == pin) return i2c_valid_pins[idx*4+3];
- return 0;
- }
-
-
- // Set Operating Mode - this configures operating mode of the I2C as either Immediate, ISR, or DMA.
- // By default Arduino-style begin() calls will initialize to ISR mode. This can
- // only be called when the bus is idle (no changing mode in the middle of Tx/Rx).
- // Note that Slave mode can only use ISR operation.
- // return: 1=success, 0=fail (bus busy)
- // parameters:
- // opMode = I2C_OP_MODE_ISR, I2C_OP_MODE_DMA, I2C_OP_MODE_IMM
- //
- uint8_t i2c_t3::setOpMode_(struct i2cStruct* i2c, uint8_t bus, i2c_op_mode opMode)
- {
- if(*(i2c->S) & I2C_S_BUSY) return 0; // return immediately if bus busy
-
- *(i2c->C1) = I2C_C1_IICEN; // reset I2C modes, stop intr, stop DMA
- *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear status flags just in case
-
- // Slaves can only use ISR
- if(i2c->currentMode == I2C_SLAVE) opMode = I2C_OP_MODE_ISR;
-
- if(opMode == I2C_OP_MODE_IMM)
- {
- i2c->opMode = I2C_OP_MODE_IMM;
- }
- if(opMode == I2C_OP_MODE_ISR || opMode == I2C_OP_MODE_DMA)
- {
- // Nested Vec Interrupt Ctrl - enable I2C interrupt
- if(bus == 0)
- {
- NVIC_ENABLE_IRQ(IRQ_I2C0);
- I2C0_INTR_FLAG_INIT; // init I2C0 interrupt flag if used
- }
- #if I2C_BUS_NUM >= 2
- if(bus == 1)
- {
- NVIC_ENABLE_IRQ(IRQ_I2C1);
- I2C1_INTR_FLAG_INIT; // init I2C1 interrupt flag if used
- }
- #endif
- #if I2C_BUS_NUM >= 3
- if(bus == 2)
- {
- NVIC_ENABLE_IRQ(IRQ_I2C2);
- I2C2_INTR_FLAG_INIT; // init I2C2 interrupt flag if used
- }
- #endif
- #if I2C_BUS_NUM >= 4
- if(bus == 3)
- {
- NVIC_ENABLE_IRQ(IRQ_I2C3);
- I2C3_INTR_FLAG_INIT; // init I2C3 interrupt flag if used
- }
- #endif
- if(opMode == I2C_OP_MODE_DMA)
- {
- // attempt to get a DMA Channel (if not already allocated)
- if(i2c->DMA == nullptr)
- i2c->DMA = new DMAChannel();
- // check if object created but no available channel
- if(i2c->DMA != nullptr && i2c->DMA->channel == DMA_NUM_CHANNELS)
- {
- // revert to ISR mode if no DMA channels avail
- delete i2c->DMA;
- i2c->DMA = nullptr;
- i2c->opMode = I2C_OP_MODE_ISR;
- }
- else
- {
- // DMA object has valid channel
- if(bus == 0)
- {
- // setup static DMA settings
- i2c->DMA->disableOnCompletion();
- i2c->DMA->attachInterrupt(i2c0_isr);
- i2c->DMA->interruptAtCompletion();
- i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C0);
- }
- #if I2C_BUS_NUM >= 2
- if(bus == 1)
- {
- // setup static DMA settings
- i2c->DMA->disableOnCompletion();
- i2c->DMA->attachInterrupt(i2c1_isr);
- i2c->DMA->interruptAtCompletion();
- i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C1);
- }
- #endif
- #if I2C_BUS_NUM >= 3
- // note: on T3.6 I2C2 shares DMAMUX with I2C1
- if(bus == 2)
- {
- // setup static DMA settings
- i2c->DMA->disableOnCompletion();
- i2c->DMA->attachInterrupt(i2c2_isr);
- i2c->DMA->interruptAtCompletion();
- i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C2);
- }
- #endif
- #if I2C_BUS_NUM >= 4
- // note: on T3.6 I2C3 shares DMAMUX with I2C0
- if(bus == 3)
- {
- // setup static DMA settings
- i2c->DMA->disableOnCompletion();
- i2c->DMA->attachInterrupt(i2c3_isr);
- i2c->DMA->interruptAtCompletion();
- i2c->DMA->triggerAtHardwareEvent(DMAMUX_SOURCE_I2C3);
- }
- #endif
- i2c->activeDMA = I2C_DMA_OFF;
- i2c->opMode = I2C_OP_MODE_DMA;
- }
- }
- else
- i2c->opMode = I2C_OP_MODE_ISR;
- }
- return 1;
- }
-
-
- // Set I2C rate - reconfigures I2C frequency divider based on supplied bus freq and desired I2C freq.
- // This will be done assuming an idealized I2C rate, even though at high I2C rates
- // the actual throughput is much lower than theoretical value.
- //
- // Since the division ratios are quantized with non-uniform spacing, the selected rate
- // will be the one using the nearest available divider.
- // return: none
- // parameters:
- // busFreq = bus frequency, typically F_BUS unless reconfigured
- // freq = desired I2C frequency (will be quantized to nearest rate), or can be I2C_RATE_XXX enum (deprecated),
- // such as I2C_RATE_100, I2C_RATE_400, etc...
- //
- // Max I2C rate is 1/20th F_BUS. Some examples:
- //
- // F_CPU F_BUS Max I2C
- // (MHz) (MHz) Rate
- // ------------- ----- ----------
- // 240/120 120 6.0M bus overclock
- // 216 108 5.4M bus overclock
- // 192/96 96 4.8M bus overclock
- // 180 90 4.5M bus overclock
- // 240 80 4.0M bus overclock
- // 216/144/72 72 3.6M bus overclock
- // 192 64 3.2M bus overclock
- // 240/180/120 60 3.0M
- // 168 56 2.8M
- // 216 54 2.7M
- // 192/144/96/48 48 2.4M
- // 72 36 1.8M
- // 24 24 1.2M
- // 16 16 800k
- // 8 8 400k
- // 4 4 200k
- // 2 2 100k
- //
- void i2c_t3::setRate_(struct i2cStruct* i2c, uint32_t busFreq, uint32_t i2cFreq)
- {
- int32_t target_div = ((busFreq/1000)<<8)/(i2cFreq/1000);
- size_t idx;
- // find closest divide ratio
- for(idx=0; idx < sizeof(i2c_div_num)/sizeof(i2c_div_num[0]) && (i2c_div_num[idx]<<8) <= target_div; idx++);
- if(idx && abs(target_div-(i2c_div_num[idx-1]<<8)) <= abs(target_div-(i2c_div_num[idx]<<8))) idx--;
- // Set divider to set rate
- *(i2c->F) = i2c_div_ratio[idx];
- // save current rate setting
- i2c->currentRate = busFreq/i2c_div_num[idx];
-
- // Set filter
- if(busFreq >= 48000000)
- *(i2c->FLT) = 4;
- else
- *(i2c->FLT) = busFreq/12000000;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Configure I2C pins - reconfigures active I2C pins on-the-fly (only works when bus is idle). If reconfig
- // set then inactive pins will switch to input mode using same pullup configuration.
- // return: 1=success, 0=fail (bus busy or incompatible pins)
- // parameters:
- // pins = pins to use, can be specified as 'i2c_pins' enum,
- // or as 'SCL,SDA' pair (using any valid SCL or SDA), options are:
- // Pin Name
- // Interface Devices (deprecated) SCL SDA
- // --------- ------- -------------- ----- ----- (note: in almost all cases SCL is the
- // Wire All I2C_PINS_16_17 16 17 lower pin #, except cases marked *)
- // Wire All I2C_PINS_18_19 19 18 *
- // Wire 3.5/3.6 I2C_PINS_7_8 7 8
- // Wire 3.5/3.6 I2C_PINS_33_34 33 34
- // Wire 3.5/3.6 I2C_PINS_47_48 47 48
- // Wire1 LC I2C_PINS_22_23 22 23
- // Wire1 3.1/3.2 I2C_PINS_26_31 26 31
- // Wire1 3.1/3.2 I2C_PINS_29_30 29 30
- // Wire1 3.5/3.6 I2C_PINS_37_38 37 38
- // Wire2 3.5/3.6 I2C_PINS_3_4 3 4
- // Wire3 3.6 I2C_PINS_56_57 57 56 *
- // pullup = I2C_PULLUP_EXT, I2C_PULLUP_INT
- // reconfig = 1=reconfigure old pins, 0=do not reconfigure old pins (base routine only)
- //
- #define PIN_CONFIG_ALT(name,alt) uint32_t name = (pullup == I2C_PULLUP_EXT) ? (PORT_PCR_MUX(alt)|PORT_PCR_ODE|PORT_PCR_SRE|PORT_PCR_DSE) \
- : (PORT_PCR_MUX(alt)|PORT_PCR_PE|PORT_PCR_PS)
-
- uint8_t i2c_t3::pinConfigure_(struct i2cStruct* i2c, uint8_t bus, uint8_t pinSCL, uint8_t pinSDA, i2c_pullup pullup,
- uint8_t configuredSCL, uint8_t configuredSDA)
- {
- uint8_t validAltSCL, validAltSDA;
- volatile uint32_t* pcr;
-
- if((configuredSCL && configuredSDA) && (*(i2c->S) & I2C_S_BUSY)) return 0; // if configured return immediately if bus busy
-
- // Verify new SCL pin is different or not configured, and valid
- //
- validAltSCL = validPin_(bus, pinSCL, 1);
- if((pinSCL != i2c->currentSCL || !configuredSCL) && validAltSCL)
- {
- // If configured, switch previous pin to non-I2C input
- if(configuredSCL) pinMode(i2c->currentSCL, (i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP);
- // Config new pin
- PIN_CONFIG_ALT(configSCL, validAltSCL);
- pcr = portConfigRegister(pinSCL);
- *pcr = configSCL;
- i2c->currentSCL = pinSCL;
- i2c->currentPullup = pullup;
- i2c->configuredSCL = 1;
- }
-
- // Verify new SDA pin is different or not configured, and valid
- //
- validAltSDA = validPin_(bus, pinSDA, 2);
- if((pinSDA != i2c->currentSDA || !configuredSDA) && validAltSDA)
- {
- // If reconfig set, switch previous pin to non-I2C input
- if(configuredSDA) pinMode(i2c->currentSDA, (i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP);
- // Config new pin
- PIN_CONFIG_ALT(configSDA, validAltSDA);
- pcr = portConfigRegister(pinSDA);
- *pcr = configSDA;
- i2c->currentSDA = pinSDA;
- i2c->currentPullup = pullup;
- i2c->configuredSDA = 1;
- }
-
- return (validAltSCL && validAltSDA);
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Acquire Bus - acquires bus in Master mode and escalates priority as needed, intended
- // for internal use only
- // return: 1=success, 0=fail (cannot acquire bus)
- // parameters:
- // timeout = timeout in microseconds
- // forceImm = flag to indicate if immediate mode is required
- //
- uint8_t i2c_t3::acquireBus_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout, uint8_t& forceImm)
- {
- elapsedMicros deltaT;
-
- // update timeout
- timeout = (timeout == 0) ? i2c->defTimeout : timeout;
-
- // TODO may need to check bus busy before issuing START if multi-master
-
- // start timer, then take control of the bus
- deltaT = 0;
- if(*(i2c->C1) & I2C_C1_MST)
- {
- // we are already the bus master, so send a repeated start
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_RSTA | I2C_C1_TX;
- }
- else
- {
- while(timeout == 0 || deltaT < timeout)
- {
- // we are not currently the bus master, so check if bus ready
- if(!(*(i2c->S) & I2C_S_BUSY))
- {
- // become the bus master in transmit mode (send start)
- i2c->currentMode = I2C_MASTER;
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
- break;
- }
- }
- #if defined(I2C_AUTO_RETRY)
- // if not master and auto-retry set, then reset bus and try one last time
- if(!(*(i2c->C1) & I2C_C1_MST))
- {
- resetBus_(i2c,bus);
- I2C_ERR_INC(I2C_ERRCNT_RESET_BUS);
- if(!(*(i2c->S) & I2C_S_BUSY))
- {
- // become the bus master in transmit mode (send start)
- i2c->currentMode = I2C_MASTER;
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
- }
- }
- #endif
- // check if not master
- if(!(*(i2c->C1) & I2C_C1_MST))
- {
- i2c->currentStatus = I2C_NOT_ACQ; // bus not acquired
- I2C_ERR_INC(I2C_ERRCNT_NOT_ACQ);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if cannot acquire bus
- return 0;
- }
- }
-
- #ifndef I2C_DISABLE_PRIORITY_CHECK
- // For ISR operation, check if current routine has higher priority than I2C IRQ, and if so
- // either escalate priority of I2C IRQ or send I2C using immediate mode.
- //
- // This check is disabled if the routine is called during an active I2C ISR (assumes it is
- // called from ISR callback). This is to prevent runaway escalation with nested Wire calls.
- //
- int irqPriority, currPriority;
- if(!i2c_t3::isrActive && (i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA))
- {
- currPriority = nvic_execution_priority();
- switch(bus)
- {
- case 0: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C0); break;
- #if defined(__MKL26Z64__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.1/3.2/3.5/3.6
- case 1: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C1); break;
- #endif
- #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6
- case 2: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C2); break;
- #endif
- #if defined(__MK66FX1M0__) // 3.6
- case 3: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C3); break;
- #endif
- default: irqPriority = NVIC_GET_PRIORITY(IRQ_I2C0); break;
- }
- if(currPriority <= irqPriority)
- {
- if(currPriority < 16)
- forceImm = 1; // current priority cannot be surpassed, force Immediate mode
- else
- {
- switch(bus)
- {
- case 0: NVIC_SET_PRIORITY(IRQ_I2C0, currPriority-16); break;
- #if defined(__MKL26Z64__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.1/3.2/3.5/3.6
- case 1: NVIC_SET_PRIORITY(IRQ_I2C1, currPriority-16); break;
- #endif
- #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // 3.5/3.6
- case 2: NVIC_SET_PRIORITY(IRQ_I2C2, currPriority-16); break;
- #endif
- #if defined(__MK66FX1M0__) // 3.6
- case 3: NVIC_SET_PRIORITY(IRQ_I2C3, currPriority-16); break;
- #endif
- default: NVIC_SET_PRIORITY(IRQ_I2C0, currPriority-16); break;
- }
- }
- }
- }
- #endif
-
- return 1;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Reset Bus - toggles SCL until SDA line is released (9 clocks max). This is used to correct
- // a hung bus in which a Slave device missed some clocks and remains stuck outputting
- // a low signal on SDA (thereby preventing START/STOP signaling).
- // return: none
- //
- void i2c_t3::resetBus_(struct i2cStruct* i2c, uint8_t bus)
- {
- uint8_t scl = i2c->currentSCL;
- uint8_t sda = i2c->currentSDA;
-
- // change pin mux to digital I/O
- pinMode(sda,((i2c->currentPullup == I2C_PULLUP_EXT) ? INPUT : INPUT_PULLUP));
- digitalWrite(scl,HIGH);
- pinMode(scl,OUTPUT);
-
- for(uint8_t count=0; digitalRead(sda) == 0 && count < 9; count++)
- {
- digitalWrite(scl,LOW);
- delayMicroseconds(5); // 10us period == 100kHz
- digitalWrite(scl,HIGH);
- delayMicroseconds(5);
- }
-
- // reconfigure pins for I2C
- pinConfigure_(i2c, bus, scl, sda, i2c->currentPullup, 0, 0);
-
- // reset config and status
- if(*(i2c->S) & 0x7F) // reset config if any residual status bits are set
- {
- *(i2c->C1) = 0x00; // disable I2C, intr disabled
- delayMicroseconds(5);
- *(i2c->C1) = I2C_C1_IICEN; // enable I2C, intr disabled, Rx mode
- delayMicroseconds(5);
- }
- i2c->currentStatus = I2C_WAITING;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Setup Master Transmit - initialize Tx buffer for transmit to slave at address
- // return: none
- // parameters:
- // address = target 7bit slave address
- //
- void i2c_t3::beginTransmission(uint8_t address)
- {
- i2c->txBuffer[0] = (address << 1); // store target addr
- i2c->txBufferLength = 1;
- clearWriteError(); // clear any previous write error
- i2c->currentStatus = I2C_WAITING; // reset status
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Master Transmit - blocking routine with timeout, transmits Tx buffer to slave. i2c_stop parameter can be used
- // to indicate if command should end with a STOP(I2C_STOP) or not (I2C_NOSTOP).
- // return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error
- // parameters:
- // i2c_stop = I2C_NOSTOP, I2C_STOP
- // timeout = timeout in microseconds
- //
- uint8_t i2c_t3::endTransmission(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout)
- {
- sendTransmission_(i2c, bus, sendStop, timeout);
-
- // wait for completion or timeout
- finish_(i2c, bus, timeout);
-
- return getError();
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Send Master Transmit - non-blocking routine, starts transmit of Tx buffer to slave. i2c_stop parameter can be
- // used to indicate if command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use
- // done() or finish() to determine completion and status() to determine success/fail.
- // return: none
- // parameters:
- // i2c_stop = I2C_NOSTOP, I2C_STOP
- // timeout = timeout in microseconds (only used for Immediate operation)
- //
- void i2c_t3::sendTransmission_(struct i2cStruct* i2c, uint8_t bus, i2c_stop sendStop, uint32_t timeout)
- {
- uint8_t status, forceImm=0;
- size_t idx;
-
- // exit immediately if sending 0 bytes
- if(i2c->txBufferLength == 0) return;
-
- // update timeout
- timeout = (timeout == 0) ? i2c->defTimeout : timeout;
-
- // clear the status flags
- #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
- *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr
- *(i2c->FLT) &= ~I2C_FLT_SSIE; // disable STOP/START intr (not used in Master mode)
- #endif
- *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear intr, arbl
-
- // try to take control of the bus
- if(!acquireBus_(i2c, bus, timeout, forceImm)) return;
-
- //
- // Immediate mode - blocking
- //
- if(i2c->opMode == I2C_OP_MODE_IMM || forceImm)
- {
- elapsedMicros deltaT;
- i2c->currentStatus = I2C_SENDING;
- i2c->currentStop = sendStop;
-
- for(idx=0; idx < i2c->txBufferLength && (timeout == 0 || deltaT < timeout); idx++)
- {
- // send data, wait for done
- *(i2c->D) = i2c->txBuffer[idx];
-
- // wait for byte
- while(!(*(i2c->S) & I2C_S_IICIF) && (timeout == 0 || deltaT < timeout));
- *(i2c->S) = I2C_S_IICIF;
- if(timeout && deltaT >= timeout) break;
-
- status = *(i2c->S);
-
- // check arbitration
- if(status & I2C_S_ARBL)
- {
- i2c->currentStatus = I2C_ARB_LOST;
- *(i2c->S) = I2C_S_ARBL; // clear arbl flag
- // TODO: this is clearly not right, after ARBL it should drop into IMM slave mode if IAAS=1
- // Right now Rx message would be ignored regardless of IAAS
- *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
- I2C_ERR_INC(I2C_ERRCNT_ARBL);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
- return;
- }
- // check if slave ACK'd
- else if(status & I2C_S_RXAK)
- {
- if(idx == 0)
- {
- i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
- I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
- }
- else
- {
- i2c->currentStatus = I2C_DATA_NAK; // NAK on Data
- I2C_ERR_INC(I2C_ERRCNT_DATA_NAK);
- }
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
- return;
- }
- }
-
- // send STOP if configured
- if(i2c->currentStop == I2C_STOP)
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- else
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled
-
- // Set final status
- if(idx < i2c->txBufferLength)
- {
- i2c->currentStatus = I2C_TIMEOUT; // Tx incomplete, mark as timeout
- I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
- }
- else
- {
- i2c->currentStatus = I2C_WAITING; // Tx complete, change to waiting state
- if(i2c->user_onTransmitDone != nullptr) i2c->user_onTransmitDone(); // Call Master Tx complete callback
- }
- }
- //
- // ISR/DMA mode - non-blocking
- //
- else if(i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA)
- {
- // send target addr and enable interrupts
- i2c->currentStatus = I2C_SENDING;
- i2c->currentStop = sendStop;
- i2c->txBufferIndex = 0;
- if(i2c->opMode == I2C_OP_MODE_DMA && i2c->txBufferLength >= 5) // limit transfers less than 5 bytes to ISR method
- {
- // init DMA, let the hack begin
- i2c->activeDMA = I2C_DMA_ADDR;
- i2c->DMA->sourceBuffer(&i2c->txBuffer[2],i2c->txBufferLength-3); // DMA sends all except first/second/last bytes
- i2c->DMA->destination(*(i2c->D));
- }
- // start ISR
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // enable intr
- *(i2c->D) = i2c->txBuffer[0]; // writing first data byte will start ISR
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Master Receive - blocking routine with timeout, requests length bytes from slave at address. Receive data will
- // be placed in the Rx buffer. i2c_stop parameter can be used to indicate if command should end
- // with a STOP (I2C_STOP) or not (I2C_NOSTOP).
- // return: #bytes received = success, 0=fail (0 length request, NAK, timeout, or bus error)
- // parameters:
- // address = target 7bit slave address
- // length = number of bytes requested
- // i2c_stop = I2C_NOSTOP, I2C_STOP
- // timeout = timeout in microseconds
- //
- size_t i2c_t3::requestFrom_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout)
- {
- // exit immediately if request for 0 bytes
- if(len == 0) return 0;
-
- sendRequest_(i2c, bus, addr, len, sendStop, timeout);
-
- // wait for completion or timeout
- if(finish_(i2c, bus, timeout))
- return i2c->rxBufferLength;
- else
- return 0; // NAK, timeout or bus error
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Start Master Receive - non-blocking routine, starts request for length bytes from slave at address. Receive
- // data will be placed in the Rx buffer. i2c_stop parameter can be used to indicate if
- // command should end with a STOP (I2C_STOP) or not (I2C_NOSTOP). Use done() or finish()
- // to determine completion and status() to determine success/fail.
- // return: none
- // parameters:
- // address = target 7bit slave address
- // length = number of bytes requested
- // i2c_stop = I2C_NOSTOP, I2C_STOP
- // timeout = timeout in microseconds (only used for Immediate operation)
- //
- void i2c_t3::sendRequest_(struct i2cStruct* i2c, uint8_t bus, uint8_t addr, size_t len, i2c_stop sendStop, uint32_t timeout)
- {
- uint8_t status, data, chkTimeout=0, forceImm=0;
-
- // exit immediately if request for 0 bytes or request too large
- if(len == 0) return;
- if(len > I2C_RX_BUFFER_LENGTH) { i2c->currentStatus=I2C_BUF_OVF; return; }
-
- i2c->reqCount = len; // store request length
- i2c->rxBufferIndex = 0; // reset buffer
- i2c->rxBufferLength = 0;
- timeout = (timeout == 0) ? i2c->defTimeout : timeout;
-
- // clear the status flags
- #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
- *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr
- *(i2c->FLT) &= ~I2C_FLT_SSIE; // disable STOP/START intr (not used in Master mode)
- #endif
- *(i2c->S) = I2C_S_IICIF | I2C_S_ARBL; // clear intr, arbl
-
- // try to take control of the bus
- if(!acquireBus_(i2c, bus, timeout, forceImm)) return;
-
- //
- // Immediate mode - blocking
- //
- if(i2c->opMode == I2C_OP_MODE_IMM || forceImm)
- {
- elapsedMicros deltaT;
- i2c->currentStatus = I2C_SEND_ADDR;
- i2c->currentStop = sendStop;
-
- // Send target address
- *(i2c->D) = (addr << 1) | 1; // address + READ
-
- // wait for byte
- while(!(*(i2c->S) & I2C_S_IICIF) && (timeout == 0 || deltaT < timeout));
- *(i2c->S) = I2C_S_IICIF;
- if(timeout && deltaT >= timeout)
- {
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- i2c->currentStatus = I2C_TIMEOUT; // Rx incomplete, mark as timeout
- I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
- return;
- }
-
- status = *(i2c->S);
-
- // check arbitration
- if(status & I2C_S_ARBL)
- {
- i2c->currentStatus = I2C_ARB_LOST;
- *(i2c->S) = I2C_S_ARBL; // clear arbl flag
- // TODO: this is clearly not right, after ARBL it should drop into IMM slave mode if IAAS=1
- // Right now Rx message would be ignored regardless of IAAS
- *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
- I2C_ERR_INC(I2C_ERRCNT_ARBL);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
- return;
- }
- // check if slave ACK'd
- else if(status & I2C_S_RXAK)
- {
- i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
- return;
- }
- else
- {
- // Slave addr ACK, change to Rx mode
- i2c->currentStatus = I2C_RECEIVING;
- if(i2c->reqCount == 1)
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
- else
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST; // no STOP, change to Rx
- data = *(i2c->D); // dummy read
-
- // Master receive loop
- while(i2c->rxBufferLength < i2c->reqCount && i2c->currentStatus == I2C_RECEIVING)
- {
- while(!(*(i2c->S) & I2C_S_IICIF) && (timeout == 0 || deltaT < timeout));
- *(i2c->S) = I2C_S_IICIF;
- chkTimeout = (timeout != 0 && deltaT >= timeout);
- // check if 2nd to last byte or timeout
- if((i2c->rxBufferLength+2) == i2c->reqCount || (chkTimeout && !i2c->timeoutRxNAK))
- {
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
- }
- // if last byte or timeout send STOP
- if((i2c->rxBufferLength+1) >= i2c->reqCount || (chkTimeout && i2c->timeoutRxNAK))
- {
- i2c->timeoutRxNAK = 0; // clear flag
- // change to Tx mode
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
- // grab last data
- data = *(i2c->D);
- i2c->rxBuffer[i2c->rxBufferLength++] = data;
- if(i2c->currentStop == I2C_STOP) // NAK then STOP
- {
- delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- }
- // else NAK no STOP
-
- // Set final status
- if(chkTimeout)
- {
- i2c->currentStatus = I2C_TIMEOUT; // Rx incomplete, mark as timeout
- I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
- }
- else
- {
- i2c->currentStatus = I2C_WAITING; // Rx complete, change to waiting state
- if(i2c->user_onReqFromDone != nullptr) i2c->user_onReqFromDone(); // Call Master Rx complete callback
- }
- }
- else
- {
- // grab next data, not last byte, will ACK
- i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
- }
- if(chkTimeout) i2c->timeoutRxNAK = 1; // set flag to indicate NAK sent
- }
- }
- }
- //
- // ISR/DMA mode - non-blocking
- //
- else if(i2c->opMode == I2C_OP_MODE_ISR || i2c->opMode == I2C_OP_MODE_DMA)
- {
- // send 1st data and enable interrupts
- i2c->currentStatus = I2C_SEND_ADDR;
- i2c->currentStop = sendStop;
- if(i2c->opMode == I2C_OP_MODE_DMA && i2c->reqCount >= 5) // limit transfers less than 5 bytes to ISR method
- {
- // init DMA, let the hack begin
- i2c->activeDMA = I2C_DMA_ADDR;
- i2c->DMA->source(*(i2c->D));
- i2c->DMA->destinationBuffer(&i2c->rxBuffer[0],i2c->reqCount-1); // DMA gets all except last byte
- }
- // start ISR
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // enable intr
- *(i2c->D) = (addr << 1) | 1; // address + READ
- }
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Get Wire Error - returns "Wire" error code from a failed Tx/Rx command
- // return: 0=success, 1=data too long, 2=recv addr NACK, 3=recv data NACK, 4=other error (timeout, arb lost)
- //
- uint8_t i2c_t3::getError(void)
- {
- // convert status to Arduino return values (give these a higher priority than buf overflow error)
- switch(i2c->currentStatus)
- {
- case I2C_BUF_OVF: return 1;
- case I2C_ADDR_NAK: return 2;
- case I2C_DATA_NAK: return 3;
- case I2C_ARB_LOST: return 4;
- case I2C_TIMEOUT: return 4;
- case I2C_NOT_ACQ: return 4;
- default: break;
- }
- if(getWriteError()) return 1; // if write_error was set then flag as buffer overflow
- return 0; // no errors
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Done Check - returns simple complete/not-complete value to indicate I2C status
- // return: 1=Tx/Rx complete (with or without errors), 0=still running
- //
- uint8_t i2c_t3::done_(struct i2cStruct* i2c)
- {
- return (i2c->currentStatus < I2C_SENDING);
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Finish - blocking routine with timeout, loops until Tx/Rx is complete or timeout occurs
- // return: 1=success (Tx or Rx completed, no error), 0=fail (NAK, timeout or Arb Lost)
- // parameters:
- // timeout = timeout in microseconds
- //
- uint8_t i2c_t3::finish_(struct i2cStruct* i2c, uint8_t bus, uint32_t timeout)
- {
- elapsedMicros deltaT;
-
- // update timeout
- timeout = (timeout == 0) ? i2c->defTimeout : timeout;
-
- // wait for completion or timeout
- deltaT = 0;
- while(!done_(i2c) && (timeout == 0 || deltaT < timeout));
-
- // DMA mode and timeout
- if(timeout != 0 && deltaT >= timeout && i2c->opMode == I2C_OP_MODE_DMA && i2c->activeDMA != I2C_DMA_OFF)
- {
- // If DMA mode times out, then wait for transfer to end then mark it as timeout.
- // This is done this way because abruptly ending the DMA seems to cause
- // the I2C_S_BUSY flag to get stuck, and I cannot find a reliable way to clear it.
- while(!done_(i2c));
- i2c->currentStatus = I2C_TIMEOUT;
- }
-
- // check exit status, if not done then timeout occurred
- if(!done_(i2c)) i2c->currentStatus = I2C_TIMEOUT; // set to timeout state
-
- // delay to allow bus to settle - allow Timeout or STOP to complete and be recognized. Timeouts must
- // propagate through ISR, and STOP must be recognized on both
- // Master and Slave sides
- delayMicroseconds(4);
-
- // note that onTransmitDone, onReqFromDone, onError callbacks are handled in ISR, this is done
- // because use of this function is optional on background transfers
- if(i2c->currentStatus == I2C_WAITING) return 1;
- return 0;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Write - write data to Tx buffer
- // return: #bytes written = success, 0=fail
- // parameters:
- // data = data byte
- //
- size_t i2c_t3::write(uint8_t data)
- {
- if(i2c->txBufferLength < I2C_TX_BUFFER_LENGTH)
- {
- i2c->txBuffer[i2c->txBufferLength++] = data;
- return 1;
- }
- setWriteError();
- return 0;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Write Array - write count number of bytes from data array to Tx buffer
- // return: #bytes written = success, 0=fail
- // parameters:
- // data = pointer to uint8_t array of data
- // count = number of bytes to write
- //
- size_t i2c_t3::write(const uint8_t* data, size_t count)
- {
- if(i2c->txBufferLength < I2C_TX_BUFFER_LENGTH)
- {
- size_t avail = I2C_TX_BUFFER_LENGTH - i2c->txBufferLength;
- uint8_t* dest = i2c->txBuffer + i2c->txBufferLength;
-
- if(count > avail)
- {
- count = avail; // truncate to space avail if needed
- setWriteError();
- }
- memcpy(dest, data, count);
- i2c->txBufferLength += count;
- return count;
- }
- setWriteError();
- return 0;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Read - returns next data byte (signed int) from Rx buffer
- // return: data, -1 if buffer empty
- //
- int i2c_t3::read_(struct i2cStruct* i2c)
- {
- if(i2c->rxBufferIndex >= i2c->rxBufferLength) return -1;
- return i2c->rxBuffer[i2c->rxBufferIndex++];
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Read Array - read count number of bytes from Rx buffer to data array
- // return: #bytes read
- // parameters:
- // data = pointer to uint8_t array of data
- // count = number of bytes to write
- //
- size_t i2c_t3::read_(struct i2cStruct* i2c, uint8_t* data, size_t count)
- {
- if(i2c->rxBufferLength > i2c->rxBufferIndex)
- {
- size_t avail = i2c->rxBufferLength - i2c->rxBufferIndex;
- uint8_t* src = i2c->rxBuffer + i2c->rxBufferIndex;
-
- if(count > avail) count = avail; // truncate to data avail if needed
- memcpy(data, src, count);
- i2c->rxBufferIndex += count;
- return count;
- }
- return 0;
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Peek - returns next data byte (signed int) from Rx buffer without removing it from Rx buffer
- // return: data, -1 if buffer empty
- //
- int i2c_t3::peek_(struct i2cStruct* i2c)
- {
- if(i2c->rxBufferIndex >= i2c->rxBufferLength) return -1;
- return i2c->rxBuffer[i2c->rxBufferIndex];
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Read Byte - returns next data byte (uint8_t) from Rx buffer
- // return: data, 0 if buffer empty
- //
- uint8_t i2c_t3::readByte_(struct i2cStruct* i2c)
- {
- if(i2c->rxBufferIndex >= i2c->rxBufferLength) return 0;
- return i2c->rxBuffer[i2c->rxBufferIndex++];
- }
-
-
- // ------------------------------------------------------------------------------------------------------
- // Peek Byte - returns next data byte (uint8_t) from Rx buffer without removing it from Rx buffer
- // return: data, 0 if buffer empty
- //
- uint8_t i2c_t3::peekByte_(struct i2cStruct* i2c)
- {
- if(i2c->rxBufferIndex >= i2c->rxBufferLength) return 0;
- return i2c->rxBuffer[i2c->rxBufferIndex];
- }
-
-
- // ======================================================================================================
- // ------------------------------------------------------------------------------------------------------
- // I2C Interrupt Service Routine
- // ------------------------------------------------------------------------------------------------------
- // ======================================================================================================
-
-
- void i2c0_isr(void) // I2C0 ISR
- {
- I2C0_INTR_FLAG_ON;
- i2c_isr_handler(&(i2c_t3::i2cData[0]),0);
- I2C0_INTR_FLAG_OFF;
- }
- #if I2C_BUS_NUM >= 2
- void i2c1_isr(void) // I2C1 ISR
- {
- I2C1_INTR_FLAG_ON;
- i2c_isr_handler(&(i2c_t3::i2cData[1]),1);
- I2C1_INTR_FLAG_OFF;
- }
- #endif
- #if I2C_BUS_NUM >= 3
- void i2c2_isr(void) // I2C2 ISR
- {
- I2C2_INTR_FLAG_ON;
- i2c_isr_handler(&(i2c_t3::i2cData[2]),2);
- I2C2_INTR_FLAG_OFF;
- }
- #endif
- #if I2C_BUS_NUM >= 4
- void i2c3_isr(void) // I2C3 ISR
- {
- I2C3_INTR_FLAG_ON;
- i2c_isr_handler(&(i2c_t3::i2cData[3]),3);
- I2C3_INTR_FLAG_OFF;
- }
- #endif
-
- //
- // I2C ISR base handler
- //
- void i2c_isr_handler(struct i2cStruct* i2c, uint8_t bus)
- {
- uint8_t status, c1, data;
- i2c_t3::isrActive++;
-
- status = *(i2c->S);
- c1 = *(i2c->C1);
- #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
- uint8_t flt = *(i2c->FLT); // store flags
- #endif
-
- if(c1 & I2C_C1_MST)
- {
- //
- // Master Mode
- //
- if(c1 & I2C_C1_TX)
- {
- if(i2c->activeDMA == I2C_DMA_BULK || i2c->activeDMA == I2C_DMA_LAST)
- {
- if(i2c->DMA->complete() && i2c->activeDMA == I2C_DMA_BULK)
- {
- // clear DMA interrupt, final byte should trigger another ISR
- i2c->DMA->clearInterrupt();
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX; // intr en, Tx mode, DMA disabled
- // DMA says complete at the beginning of its last byte, need to
- // wait until end of its last byte to re-engage ISR
- i2c->activeDMA = I2C_DMA_LAST;
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- else if(i2c->activeDMA == I2C_DMA_LAST)
- {
- // wait for TCF
- while(!(*(i2c->S) & I2C_S_TCF));
- // clear DMA, only do this after TCF
- i2c->DMA->clearComplete();
- // re-engage ISR for last byte
- i2c->activeDMA = I2C_DMA_OFF;
- i2c->txBufferIndex = i2c->txBufferLength-1;
- *(i2c->D) = i2c->txBuffer[i2c->txBufferIndex];
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- else if(i2c->DMA->error())
- {
- i2c->DMA->clearError();
- i2c->DMA->clearInterrupt();
- i2c->activeDMA = I2C_DMA_OFF;
- i2c->currentStatus = I2C_DMA_ERR;
- I2C_ERR_INC(I2C_ERRCNT_DMA_ERR);
- // check arbitration
- if(status & I2C_S_ARBL)
- {
- // Arbitration Lost
- i2c->currentStatus = I2C_ARB_LOST;
- I2C_ERR_INC(I2C_ERRCNT_ARBL);
- *(i2c->S) = I2C_S_ARBL; // clear arbl flag
- i2c->txBufferIndex = 0; // reset Tx buffer index to prepare for resend
- // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read. not sure if this would work for DMA
- }
- *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled, DMA disabled
- *(i2c->S) = I2C_S_IICIF; // clear intr
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if DMA error or ARBL
- }
- i2c_t3::isrActive--;
- return;
- } // end DMA Tx
- else
- {
- // Continue Master Transmit
- // check if Master Tx or Rx
- if(i2c->currentStatus == I2C_SENDING)
- {
- // check arbitration
- if(status & I2C_S_ARBL)
- {
- // Arbitration Lost
- i2c->activeDMA = I2C_DMA_OFF; // clear pending DMA (if happens on address byte)
- i2c->currentStatus = I2C_ARB_LOST;
- *(i2c->S) = I2C_S_ARBL; // clear arbl flag
- *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
- i2c->txBufferIndex = 0; // reset Tx buffer index to prepare for resend
- // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read.
- *(i2c->S) = I2C_S_IICIF; // clear intr
- I2C_ERR_INC(I2C_ERRCNT_ARBL);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
- }
- // check if slave ACK'd
- else if(status & I2C_S_RXAK)
- {
- i2c->activeDMA = I2C_DMA_OFF; // clear pending DMA (if happens on address byte)
- if(i2c->txBufferIndex == 0)
- {
- i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
- I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
- }
- else
- {
- i2c->currentStatus = I2C_DATA_NAK; // NAK on Data
- I2C_ERR_INC(I2C_ERRCNT_DATA_NAK);
- }
- // send STOP, change to Rx mode, intr disabled
- // note: Slave NAK is an error, so send STOP regardless of setting
- *(i2c->C1) = I2C_C1_IICEN;
- *(i2c->S) = I2C_S_IICIF; // clear intr
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
- }
- else
- {
- // check if last byte transmitted
- if(++i2c->txBufferIndex >= i2c->txBufferLength)
- {
- // Tx complete, change to waiting state
- i2c->currentStatus = I2C_WAITING;
- // send STOP if configured
- if(i2c->currentStop == I2C_STOP)
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- else
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled
- // run TransmitDone callback when done
- *(i2c->S) = I2C_S_IICIF; // clear intr
- if(i2c->user_onTransmitDone != nullptr) i2c->user_onTransmitDone();
- }
- else if(i2c->activeDMA == I2C_DMA_ADDR)
- {
- // Start DMA
- i2c->activeDMA = I2C_DMA_BULK;
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TX | I2C_C1_DMAEN; // intr en, Tx mode, DMA en
- i2c->DMA->enable();
- *(i2c->D) = i2c->txBuffer[1]; // DMA will start on next request
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- else
- {
- // ISR transmit next byte
- *(i2c->D) = i2c->txBuffer[i2c->txBufferIndex];
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- }
- i2c_t3::isrActive--;
- return;
- }
- else if(i2c->currentStatus == I2C_SEND_ADDR)
- {
- // Master Receive, addr sent
- if(status & I2C_S_ARBL)
- {
- // Arbitration Lost
- i2c->currentStatus = I2C_ARB_LOST;
- *(i2c->S) = I2C_S_ARBL; // clear arbl flag
- *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled (does this send STOP if ARBL flagged?)
- // TODO does this need to check IAAS and drop to Slave Rx? if so set Rx + dummy read. not sure if this would work for DMA
- *(i2c->S) = I2C_S_IICIF; // clear intr
- I2C_ERR_INC(I2C_ERRCNT_ARBL);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if ARBL
- }
- else if(status & I2C_S_RXAK)
- {
- // Slave addr NAK
- i2c->currentStatus = I2C_ADDR_NAK; // NAK on Addr
- // send STOP, change to Rx mode, intr disabled
- // note: Slave NAK is an error, so send STOP regardless of setting
- *(i2c->C1) = I2C_C1_IICEN;
- *(i2c->S) = I2C_S_IICIF; // clear intr
- I2C_ERR_INC(I2C_ERRCNT_ADDR_NAK);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if NAK
- }
- else if(i2c->activeDMA == I2C_DMA_ADDR)
- {
- // Start DMA
- i2c->activeDMA = I2C_DMA_BULK;
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_DMAEN; // intr en, no STOP, change to Rx, DMA en
- i2c->DMA->enable();
- data = *(i2c->D); // dummy read
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- else
- {
- // Slave addr ACK, change to Rx mode
- i2c->currentStatus = I2C_RECEIVING;
- if(i2c->reqCount == 1)
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
- else
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST; // no STOP, change to Rx
- data = *(i2c->D); // dummy read
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- i2c_t3::isrActive--;
- return;
- }
- else if(i2c->currentStatus == I2C_TIMEOUT)
- {
- // send STOP if configured
- if(i2c->currentStop == I2C_STOP)
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- else
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX; // no STOP, stay in Tx mode, intr disabled
- *(i2c->S) = I2C_S_IICIF; // clear intr
- I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
- i2c_t3::isrActive--;
- return;
- }
- else
- {
- // Should not be in Tx mode if not sending
- // send STOP, change to Rx mode, intr disabled
- *(i2c->C1) = I2C_C1_IICEN;
- *(i2c->S) = I2C_S_IICIF; // clear intr
- i2c_t3::isrActive--;
- return;
- }
- } // end ISR Tx
- }
- else
- {
- // Continue Master Receive
- //
- if(i2c->activeDMA == I2C_DMA_BULK || i2c->activeDMA == I2C_DMA_LAST)
- {
- if(i2c->DMA->complete() && i2c->activeDMA == I2C_DMA_BULK) // 2nd to last byte
- {
- // clear DMA interrupt, final byte should trigger another ISR
- i2c->DMA->clearInterrupt();
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // intr en, Rx mode, DMA disabled, NAK on recv
- i2c->activeDMA = I2C_DMA_LAST;
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- else if(i2c->activeDMA == I2C_DMA_LAST) // last byte
- {
- // clear DMA
- i2c->DMA->clearComplete();
- i2c->activeDMA = I2C_DMA_OFF;
- // change to Tx mode
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
- // grab last data
- i2c->rxBufferLength = i2c->reqCount-1;
- i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
- if(i2c->currentStop == I2C_STOP) // NAK then STOP
- {
- delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- }
- // else NAK no STOP
- *(i2c->S) = I2C_S_IICIF; // clear intr
- i2c->currentStatus = I2C_WAITING; // Rx complete, change to waiting state
- if(i2c->user_onReqFromDone != nullptr) i2c->user_onReqFromDone(); // Call Master Rx complete callback
- }
- else if(i2c->DMA->error()) // not sure what would cause this...
- {
- i2c->DMA->clearError();
- i2c->DMA->clearInterrupt();
- i2c->activeDMA = I2C_DMA_OFF;
- i2c->currentStatus = I2C_DMA_ERR;
- *(i2c->C1) = I2C_C1_IICEN; // change to Rx mode, intr disabled, DMA disabled
- *(i2c->S) = I2C_S_IICIF; // clear intr
- I2C_ERR_INC(I2C_ERRCNT_DMA_ERR);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if DMA error
- }
- i2c_t3::isrActive--;
- return;
- }
- else
- {
- // check if 2nd to last byte or timeout
- if((i2c->rxBufferLength+2) == i2c->reqCount || (i2c->currentStatus == I2C_TIMEOUT && !i2c->timeoutRxNAK))
- {
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_MST | I2C_C1_TXAK; // no STOP, Rx, NAK on recv
- }
- // if last byte or timeout send STOP
- if((i2c->rxBufferLength+1) >= i2c->reqCount || (i2c->currentStatus == I2C_TIMEOUT && i2c->timeoutRxNAK))
- {
- i2c->timeoutRxNAK = 0; // clear flag
- // change to Tx mode
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_MST | I2C_C1_TX;
- // grab last data
- i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
- if(i2c->currentStop == I2C_STOP) // NAK then STOP
- {
- delayMicroseconds(1); // empirical patch, lets things settle before issuing STOP
- *(i2c->C1) = I2C_C1_IICEN; // send STOP, change to Rx mode, intr disabled
- }
- // else NAK no STOP
- *(i2c->S) = I2C_S_IICIF; // clear intr
- // Rx complete
- if(i2c->currentStatus == I2C_TIMEOUT)
- {
- I2C_ERR_INC(I2C_ERRCNT_TIMEOUT);
- if(i2c->user_onError != nullptr) i2c->user_onError(); // run Error callback if timeout
- }
- else
- {
- i2c->currentStatus = I2C_WAITING;
- if(i2c->user_onReqFromDone != nullptr) i2c->user_onReqFromDone(); // Call Master Rx complete callback
- }
- }
- else
- {
- // grab next data, not last byte, will ACK
- i2c->rxBuffer[i2c->rxBufferLength++] = *(i2c->D);
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- if(i2c->currentStatus == I2C_TIMEOUT && !i2c->timeoutRxNAK)
- i2c->timeoutRxNAK = 1; // set flag to indicate NAK sent
-
- i2c_t3::isrActive--;
- return;
- }
- }
- }
- else
- {
- //
- // Slave Mode
- //
-
- // ARBL makes no sense on Slave, but this might get set if there is a pullup problem and
- // SCL/SDA get stuck. This is primarily to guard against ARBL flag getting stuck.
- if(status & I2C_S_ARBL)
- {
- // Arbitration Lost
- *(i2c->S) = I2C_S_ARBL; // clear arbl flag
- if(!(status & I2C_S_IAAS))
- {
- #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
- *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr
- #endif
- *(i2c->S) = I2C_S_IICIF; // clear intr
- i2c_t3::isrActive--;
- return;
- }
- }
- if(status & I2C_S_IAAS)
- {
- // If in Slave Rx already, then RepSTART occured, run callback
- if(i2c->currentStatus == I2C_SLAVE_RX && i2c->user_onReceive != nullptr)
- {
- i2c->rxBufferIndex = 0;
- i2c->user_onReceive(i2c->rxBufferLength);
- }
-
- // Is Addressed As Slave
- if(status & I2C_S_SRW)
- {
- // Addressed Slave Transmit
- //
- i2c->currentStatus = I2C_SLAVE_TX;
- i2c->txBufferLength = 0;
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX;
- i2c->rxAddr = (*(i2c->D) >> 1); // read to get target addr
- if(i2c->user_onRequest != nullptr) i2c->user_onRequest(); // load Slave Tx buffer with data
- if(i2c->txBufferLength == 0) i2c->txBuffer[0] = 0; // send 0's if buffer empty
- *(i2c->D) = i2c->txBuffer[0]; // send first data
- i2c->txBufferIndex = 1;
- }
- else
- {
- // Addressed Slave Receive
- //
- // setup SDA-rising ISR - required for STOP detection in Slave Rx mode for 3.0/3.1/3.2
- #if defined(__MK20DX256__) && I2C_BUS_NUM == 2 // 3.1/3.2 (dual-bus)
- i2c->irqCount = 0;
- attachInterrupt(i2c->currentSDA, (bus == 0) ? i2c_t3::sda0_rising_isr : i2c_t3::sda1_rising_isr, RISING);
- #elif defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2 (single-bus)
- i2c->irqCount = 0;
- attachInterrupt(i2c->currentSDA, i2c_t3::sda0_rising_isr, RISING);
- #elif defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
- *(i2c->FLT) |= I2C_FLT_SSIE; // enable START/STOP intr for LC/3.5/3.6
- #endif
- i2c->currentStatus = I2C_SLAVE_RX;
- i2c->rxBufferLength = 0;
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE;
- i2c->rxAddr = (*(i2c->D) >> 1); // read to get target addr
- }
- #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
- *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr
- #endif
- *(i2c->S) = I2C_S_IICIF; // clear intr
- i2c_t3::isrActive--;
- return;
- }
- if(c1 & I2C_C1_TX)
- {
- // Continue Slave Transmit
- if((status & I2C_S_RXAK) == 0)
- {
- // Master ACK'd previous byte
- if(i2c->txBufferIndex < i2c->txBufferLength)
- data = i2c->txBuffer[i2c->txBufferIndex++];
- else
- data = 0; // send 0's if buffer empty
- *(i2c->D) = data;
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE | I2C_C1_TX;
- }
- else
- {
- // Master did not ACK previous byte
- *(i2c->C1) = I2C_C1_IICEN | I2C_C1_IICIE; // switch to Rx mode
- data = *(i2c->D); // dummy read
- i2c->currentStatus = I2C_WAITING;
- }
- }
- else if(i2c->currentStatus == I2C_SLAVE_RX)
- {
- #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
- if(flt & (I2C_FLT_STOPF|I2C_FLT_STARTF)) // STOP/START detected, run callback
- {
- // LC (MKL26) appears to have the same I2C_FLT reg definition as 3.6 (K66)
- // There is both STOPF and STARTF and they are both enabled via SSIE, and they must both
- // be cleared in order to work
- *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr
- *(i2c->FLT) &= ~I2C_FLT_SSIE; // disable STOP/START intr (will re-enable on next IAAS)
- *(i2c->S) = I2C_S_IICIF; // clear intr
- i2c->currentStatus = I2C_WAITING;
- // Slave Rx complete, run callback
- if(i2c->user_onReceive != nullptr)
- {
- i2c->rxBufferIndex = 0;
- i2c->user_onReceive(i2c->rxBufferLength);
- }
- i2c_t3::isrActive--;
- return;
- }
- #endif
- // Continue Slave Receive
- //
- // setup SDA-rising ISR - required for STOP detection in Slave Rx mode for 3.0/3.1/3.2
- #if defined(__MK20DX256__) && I2C_BUS_NUM == 2 // 3.1/3.2 (dual-bus)
- i2c->irqCount = 0;
- attachInterrupt(i2c->currentSDA, (bus == 0) ? i2c_t3::sda0_rising_isr : i2c_t3::sda1_rising_isr, RISING);
- #elif defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2 (single-bus)
- i2c->irqCount = 0;
- attachInterrupt(i2c->currentSDA, i2c_t3::sda0_rising_isr, RISING);
- #endif
- data = *(i2c->D);
- if(i2c->rxBufferLength < I2C_RX_BUFFER_LENGTH)
- i2c->rxBuffer[i2c->rxBufferLength++] = data;
- }
- #if defined(__MKL26Z64__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) // LC/3.5/3.6
- *(i2c->FLT) |= I2C_FLT_STOPF | I2C_FLT_STARTF; // clear STOP/START intr
- #endif
- *(i2c->S) = I2C_S_IICIF; // clear intr
- }
- }
-
- #if defined(__MK20DX128__) || defined(__MK20DX256__) // 3.0/3.1/3.2
- // ------------------------------------------------------------------------------------------------------
- // SDA-Rising Interrupt Service Routine - 3.0/3.1/3.2 only
- //
- // Detects the stop condition that terminates a slave receive transfer.
- //
-
- // I2C0 SDA ISR
- void i2c_t3::sda0_rising_isr(void)
- {
- i2c_t3::sda_rising_isr_handler(&(i2c_t3::i2cData[0]),0);
- }
-
- #if I2C_BUS_NUM >= 2
- // I2C1 SDA ISR
- void i2c_t3::sda1_rising_isr(void)
- {
- i2c_t3::sda_rising_isr_handler(&(i2c_t3::i2cData[1]),1);
- }
- #endif
-
- //
- // SDA ISR base handler
- //
- void i2c_t3::sda_rising_isr_handler(struct i2cStruct* i2c, uint8_t bus)
- {
- uint8_t status = *(i2c->S); // capture status first, can change if ISR is too slow
- if(!(status & I2C_S_BUSY))
- {
- i2c->currentStatus = I2C_WAITING;
- detachInterrupt(i2c->currentSDA);
- if(i2c->user_onReceive != nullptr)
- {
- i2c->rxBufferIndex = 0;
- i2c->user_onReceive(i2c->rxBufferLength);
- }
- }
- else
- {
- if(++(i2c->irqCount) >= 2 || !(i2c->currentMode == I2C_SLAVE))
- detachInterrupt(i2c->currentSDA);
- }
- }
- #endif // sda_rising_isr
-
- // ------------------------------------------------------------------------------------------------------
- // Instantiate
- //
- i2c_t3 Wire = i2c_t3(0); // I2C0
- #if I2C_BUS_NUM >= 2
- i2c_t3 Wire1 = i2c_t3(1); // I2C1
- #endif
- #if I2C_BUS_NUM >= 3
- i2c_t3 Wire2 = i2c_t3(2); // I2C2
- #endif
- #if I2C_BUS_NUM >= 4
- i2c_t3 Wire3 = i2c_t3(3); // I2C3
- #endif
-
- #endif // i2c_t3
-
- /*
- ------------------------------------------------------------------------------------------------------
- Changelog
- ------------------------------------------------------------------------------------------------------
-
- - (v11.0) Modified 01Dec18 by Brian (nox771 at gmail.com)
- - Added state variables and modified pinConfigure_() to recognize unconfigured SCL/SDA pins,
- allowing for setSCL()/setSDA() prior to begin(), which was previously blocked by bus busy
- check on unconfigured pins.
- - Header change to MIT permissive license
-
- - (v10.1) Modified 02Jan18 by Brian (nox771 at gmail.com)
- - Added User #define to disable priority checks entirely
- - Added i2c_t3::isrActive flag to dynamically disable priority checks during ISR & callbacks.
- This is to prevent runaway priority escalation in cases of callbacks with nested Wire calls.
-
- - (v10.0) Modified 21Oct17 by Brian (nox771 at gmail.com)
- - Default assignments have been added to many functions for pins/pullup/rate/op_mode, so
- all those parameters are now optional in many function calls (marked ^ below)
- - Unbound SCL/SDA pin assignment. Pins can be specified with either i2c_pins enum or by direct
- SCL,SDA pin definition (using any valid SCL and SDA pin). New function summary is:
- - begin(mode, address1, ^i2c_pins, ^i2c_pullup, ^rate, ^i2c_op_mode)
- - begin(mode, address1, ^pinSCL, ^pinSDA, ^i2c_pullup, ^rate, ^i2c_op_mode)
- - pinConfigure(i2c_pins, ^pullup)
- - pinConfigure(pinSCL, pinSDA, ^pullup)
- - setSCL(pin)
- - setSDA(pin)
- - getSCL()
- - getSDA()
- Note: internal to i2c structure, currentPins has been replaced by currentSCL and currentSDA
- - Added Master callback functions for completion of transfers. Primarily for
- sendTransmission/sendRequest, but these will also work on foreground commands
- endTransmission/requestFrom. Also added an Error callback for Master bus errors.
- - onTransmitDone(function) - where function() is called when Master Transmit is complete
- - onReqFromDone(function) - where function() is called when Master Receive is complete
- - onError(function) - where function() is called upon any I2C error which terminates the
- Master bus operation (eg. NAK, timeout, acquire fail, etc)
- - Fixed blocking conditions that could occur in immediate mode
- - Added error counters which may be optionally enabled via I2C_ERROR_COUNTERS define. When
- enabled it will track (Master mode only): Reset Bus (auto-retry only), Timeout, Addr NAK,
- Data NAK, Arb Lost, Bus Not Acquired, DMA Errors.
- - i2c_err_count enum, getErrorCount(), and zeroErrorCount() functions added
-
- - (v9.4) Modified 01Oct17 by Brian (nox771 at gmail.com)
- - Fixed Slave ISR for LC/3.5/3.6 not properly recognizing RepSTART
- - Fixed nested Wire calls during Slave ISR receive (calling Wire inside Wire1 Slave ISR)
- - Added uint8_t and char array read functions - Wire.read(databuf, count);
-
- - (v9.3) Modified 20Sep17 by Brian (nox771 at gmail.com)
- - Fixed Slave ISR for LC/3.5/3.6
-
- - (v9.2) Modified 29Dec16 by Brian (nox771 at gmail.com)
- - improved resetBus() function to reset C1 state (thanks hw999)
-
- - (v9.1) Modified 16Oct16 by Brian (nox771 at gmail.com)
- - applied two fixes due to bug reports:
- - removed I2C_F_DIV120 setting (120 divide-ratio) for I2C clock
- - disabled I2C_AUTO_RETRY by default (setting remains but must be manually enabled)
-
- - (v9) Modified 01Jul16 by Brian (nox771 at gmail.com)
- - Added support for Teensy 3.5/3.6:
- - fully supported (Master/Slave modes, IMM/ISR/DMA operation)
- - supports all available pin/bus options on Wire/Wire1/Wire2/Wire3
- - Fixed LC slave bug, whereby it was incorrectly detecting STOPs directed to other slaves
- - I2C rate is now set using a much more flexible method than previously used (this is partially
- motivated by increasing device count and frequencies). As a result, the fixed set of rate
- enums are no longer needed (however they are currently still supported), and desired I2C
- frequency can be directly specified, eg. for 400kHz, I2C_RATE_400 can be replaced by 400000.
- Some setRate() functions are deprecated due to these changes.
-
- - (v8) Modified 02Apr15 by Brian (nox771 at gmail.com)
- - added support for Teensy LC:
- - fully supported (Master/Slave modes, IMM/ISR/DMA operation)
- - Wire: pins 16/17 or 18/19, rate limited to I2C_RATE_1200
- - Wire1: pins 22/23, rate limited to I2C_RATE_2400
- - added timeout on acquiring bus (prevents lockup when bus cannot be acquired)
- - added setDefaultTimeout() function for setting the default timeout to apply to all commands
- - added resetBus() function for toggling SCL to release stuck Slave devices
- - added setRate(rate) function, similar to setClock(freq), but using rate specifiers (does not
- require specifying busFreq)
- - added I2C_AUTO_RETRY user define
-
- - (v7) Modified 09Jan15 by Brian (nox771 at gmail.com)
- - added support for F_BUS frequencies: 60MHz, 56MHz, 48MHz, 36MHz, 24MHz, 16MHz, 8MHz, 4MHz, 2MHz
- - added new rates: I2C_RATE_1800, I2C_RATE_2800, I2C_RATE_3000
- - added new priority escalation - in cases where I2C ISR is blocked by having a lower priority than
- calling function, the I2C will either adjust I2C ISR to a higher
- priority, or switch to Immediate mode as needed.
- - added new operating mode control - I2C can be set to operate in ISR mode, DMA mode (Master only),
- or Immediate Mode (Master only)
- - added new begin() functions to allow setting the initial operating mode:
- - begin(i2c_mode mode, uint8_t address, i2c_pins pins, i2c_pullup pullup, i2c_rate rate,
- i2c_op_mode opMode)
- - begin(i2c_mode mode, uint8_t address1, uint8_t address2, i2c_pins pins, i2c_pullup pullup,
- i2c_rate rate, i2c_op_mode opMode)
- - added new functions:
- - uint8_t setOpMode(i2c_op_mode opMode) - used to change operating mode on the fly
- (only when bus is idle)
- - void sendTransmission() - non-blocking Tx with implicit I2C_STOP, added for symmetry
- with endTransmission()
- - uint8_t setRate(uint32_t busFreq, i2c_rate rate) - used to set I2C clock dividers to get
- desired rate, i2c_rate argument
- - uint8_t setRate(uint32_t busFreq, uint32_t i2cFreq) - used to set I2C clock dividers to get
- desired SCL freq, uint32_t argument
- (quantized to nearest i2c_rate)
- - added new Wire compatibility functions:
- - void setClock(uint32_t i2cFreq) - (note: degenerate form of setRate() with busFreq == F_BUS)
- - uint8_t endTransmission(uint8_t sendStop)
- - uint8_t requestFrom(uint8_t addr, uint8_t len)
- - uint8_t requestFrom(uint8_t addr, uint8_t len, uint8_t sendStop)
- - fixed bug in Slave Range code whereby onRequest() callback occurred prior to updating rxAddr
- instead of after
- - fixed bug in arbitration, was missing from Master Tx mode
- - removed I2C1 defines (now included in kinetis.h)
- - removed all debug code (eliminates rbuf dependency)
-
- - (v6) Modified 16Jan14 by Brian (nox771 at gmail.com)
- - all new structure using dereferenced pointers instead of hardcoding. This allows functions
- (including ISRs) to be reused across multiple I2C buses. Most functions moved to static,
- which in turn are called by inline user functions. Added new struct (i2cData) for holding all
- bus information.
- - added support for Teensy 3.1 and I2C1 interface on pins 29/30 and 26/31.
- - added header define (I2C_BUS_ENABLE n) to control number of enabled buses (eg. both I2C0 & I2C1
- or just I2C0). When using only I2C0 the code and ram usage will be lower.
- - added interrupt flag (toggles pin high during ISR) with independent defines for I2C0 and
- I2C1 (refer to header file), useful for logic analyzer trigger
-
- - (v5) Modified 09Jun13 by Brian (nox771 at gmail.com)
- - fixed bug in ISR timeout code in which timeout condition could fail to reset in certain cases
- - fixed bug in Slave mode in sda_rising_isr attach, whereby it was not getting attached on the addr byte
- - moved debug routines so they are entirely defined internal to the library (no end user code req'd)
- - debug routines now use IntervalTimer library
- - added support for range of Slave addresses
- - added getRxAddr() for Slave using addr range to determine its called address
- - removed virtual keyword from all functions (is not a base class)
-
- - (v1-v4) Modified 26Feb13 by Brian (nox771 at gmail.com)
- - Reworked begin function:
- - added option for pins to use (SCL:SDA on 19:18 or 16:17 - note pin order difference)
- - added option for internal pullup - as mentioned in previous code pullup is very strong,
- approx 190 ohms, but is possibly useful for high speed I2C
- - added option for rates - 100kHz, 200kHz, 300kHz, 400kHz, 600kHz, 800kHz, 1MHz, 1.2MHz, <-- 24/48MHz bus
- 1.5MHz, 2.0MHz, 2.4MHz <-- 48MHz bus only
- - Removed string.h dependency (memcpy)
- - Changed Master modes to interrupt driven
- - Added non-blocking Tx/Rx routines, and status/done/finish routines:
- - sendTransmission() - non-blocking transmit
- - sendRequest() - non-blocking receive
- - status() - reports current status
- - done() - indicates Tx/Rx complete (for main loop polling if I2C is running in background)
- - finish() - loops until Tx/Rx complete or bus error
- - Added readByte()/peekByte() for uint8_t return values (note: returns 0 instead of -1 if buf empty)
- - Added fixes for Slave Rx mode - in short Slave Rx on this part is fubar
- (as proof, notice the difference in the I2Cx_FLT register in the KL25 Sub-Family parts)
- - the SDA-rising ISR hack can work but only detects STOP conditons.
- A slave Rx followed by RepSTART won't be detected since bus remains busy.
- To fix this if IAAS occurs while already in Slave Rx mode then it will
- assume RepSTART occurred and trigger onReceive callback.
- - Separated Tx/Rx buffer sizes for asymmetric devices (adjustable in i2c_t3.h)
- - Changed Tx/Rx buffer indicies to size_t to allow for large (>256 byte) buffers
- - Left debug routines in place (controlled via header defines - default is OFF). If debug is
- enabled, note that it can easily overrun the Debug queue on large I2C transfers, yielding
- garbage output. Adjust ringbuf size (in rbuf.h) and possibly PIT interrupt rate to adjust
- data flow to Serial (note also the buffer in Serial can overflow if written too quickly).
- - Added getError() function to return Wire error code
- - Added pinConfigure() function for changing pins on the fly (only when bus not busy)
- - Added timeouts to endTransmission(), requestFrom(), and finish()
-
- ------------------------------------------------------------------------------------------------------
- */
|