From 2a4478a40c410a75462503fda9cca0c4cb83e050 Mon Sep 17 00:00:00 2001 From: MX682X Date: Sun, 12 Nov 2023 12:12:39 +0100 Subject: [PATCH 1/6] Wire rework --- megaavr/libraries/PTC/README.md | 2 +- megaavr/libraries/Wire/src/Wire.cpp | 786 +++++++++++++++++++++++----- megaavr/libraries/Wire/src/Wire.h | 179 ++++++- megaavr/libraries/Wire/src/twi.c | 692 ------------------------ megaavr/libraries/Wire/src/twi.h | 237 --------- 5 files changed, 817 insertions(+), 1079 deletions(-) delete mode 100644 megaavr/libraries/Wire/src/twi.c delete mode 100644 megaavr/libraries/Wire/src/twi.h diff --git a/megaavr/libraries/PTC/README.md b/megaavr/libraries/PTC/README.md index 5d815336..7a72c816 100644 --- a/megaavr/libraries/PTC/README.md +++ b/megaavr/libraries/PTC/README.md @@ -125,7 +125,7 @@ In order to ease the use of the PTC module, the ptc_add_* functions will initial If a node is not sensitive enough, you can increase the Analog Gain (if it becomes too sensitive, an increase of the thresholds might be needed). However it is better to have a bigger node to begin with because the bigger the area, the higher is the capacitance delta. -### Global settings of the State-maschine +### Global settings of the State-machine The state-machine, which changes the node's state between Calibration, touch, no touch, etc. uses some variables that are valid for all nodes, those are: - `uint16_t force_recal_delta`. Each node has a threshold value that is used to calculate the delta. This Threshold value is drifting over time to adjust for environmental changes. If the threshold value drifts 512 +/- this value, a recalibration of CC is performed. Default: 150 - `uint8_t touched_detect_nom`. Number of consecutive Measurements (Conversions) that are above the touch threshold until the node becomes "touched". Default: 3 diff --git a/megaavr/libraries/Wire/src/Wire.cpp b/megaavr/libraries/Wire/src/Wire.cpp index 82615bc4..198418d1 100644 --- a/megaavr/libraries/Wire/src/Wire.cpp +++ b/megaavr/libraries/Wire/src/Wire.cpp @@ -37,10 +37,18 @@ extern "C" { extern "C" { // compiler was complaining when I put twi.h into the upper C include part - #include "twi.h" #include "twi_pins.h" } +static uint8_t sleepStack = 0; + +void pushSleep(void); +void popSleep(void); + +TwoWire* twi0_wire; +TwoWire* twi1_wire; + + /** *@brief TwoWire creates a Wire object @@ -49,8 +57,20 @@ extern "C" { // compiler was complaining when I put twi.h into the upper C in * *@return constructor can't return anything */ + TwoWire::TwoWire(TWI_t *twi_module) { - vars._module = twi_module; + if (&TWI0 == twi_module) { + twi0_wire = this; + } + #if defined(TWI1) + else if (&TWI1 == twi_module) { + twi1_wire = this; + } + #endif + else { // ignore NULLs + return; + } + _module = twi_module; } /** @@ -103,9 +123,9 @@ bool TwoWire::pins(uint8_t sda_pin, uint8_t scl_pin) { * I don't see a way around that, though... but this will at least catch the majority of cases. -SK 10/5/22. */ #if defined(TWI1) - if (&TWI0 == vars._module) { + if (&TWI0 == _module) { return TWI0_Pins(sda_pin, scl_pin); - } else if (&TWI1 == vars._module) { + } else if (&TWI1 == _module) { return TWI1_Pins(sda_pin, scl_pin); } else { return false; @@ -177,9 +197,9 @@ bool TwoWire::swap(uint8_t state) { #endif } #if defined(TWI1) - if (&TWI0 == vars._module) { + if (&TWI0 == _module) { return TWI0_swap(state); - } else if (&TWI1 == vars._module) { + } else if (&TWI1 == _module) { return TWI1_swap(state); } else { return false; @@ -201,9 +221,9 @@ bool TwoWire::swap(uint8_t state) { */ void TwoWire::usePullups(void) { #if defined(TWI1) - if (&TWI0 == vars._module) { + if (&TWI0 == _module) { TWI0_usePullups(); - } else if (&TWI1 == vars._module) { + } else if (&TWI1 == _module) { TWI1_usePullups(); } #else @@ -224,19 +244,32 @@ void TwoWire::usePullups(void) { *@retval true if change was successful */ bool TwoWire::swapModule(TWI_t *twi_module) { + if (__builtin_constant_p(twi_module)) { + if (twi_module == NULL) { + badArg("Null Pointer passed"); + return false; + } + } #if defined(TWI1) #if defined(TWI_USING_WIRE1) badCall("swapModule() can only be used if Wire1 is not used"); #else - if (vars._module->MCTRLA == 0) { // client and host initialisations enable MCTRLA, so just check for that - vars._module = twi_module; - return true; // Success + if ((_bools._hostEnabled | _bools._clientEnabled) == 0) { // make sure nothing's enabled + if (&TWI0 == twi_module) { + twi0_wire = this; + _module = twi_module; + return true; + } + else if (&TWI1 == twi_module) { + twi1_wire = this; + _module = twi_module; + return true; + } } #endif #else badCall("Only one TWI module available, nothing to switch with"); #endif - (void)twi_module; // Remove warning unused variable return false; } @@ -249,7 +282,33 @@ bool TwoWire::swapModule(TWI_t *twi_module) { *@return void */ void TwoWire::begin(void) { - TWI_MasterInit(&vars); + #if defined(TWI_MANDS) // Check if the user wants to use Master AND Slave + if (_bools._hostEnabled == 1) { // Slave is allowed to be enabled, don't re-enable the host though + return; + } + #else // Master OR Slave + if ((_bools._hostEnabled | _bools._clientEnabled) == 1) { //If either are enabled + return; // return and do nothing + } + #endif + + + #if defined(TWI1) // More then one TWI used + if (&TWI0 == _module) { // check which one this function is working with + TWI0_ClearPins(); + } else if (&TWI1 == _module) { + TWI1_ClearPins(); + } + #else // Only one TWI is used + TWI0_ClearPins(); // Only one option is possible + #endif + + _bools._hostEnabled = 1; + TWI_t* module = _module; + module->MCTRLA = TWI_ENABLE_bm; // Master Interrupt flags stay disabled + module->MSTATUS = TWI_BUSSTATE_IDLE_gc; + + setClock(DEFAULT_FREQUENCY); } @@ -274,7 +333,34 @@ void TwoWire::begin(uint8_t address, bool receive_broadcast, uint8_t second_addr return; } } - TWI_SlaveInit(&vars, address, receive_broadcast, second_address); + + + #if defined(TWI_MANDS) // Check if the user wants to use Master AND Slave + if (_bools._clientEnabled == 1) { // Master is allowed to be enabled, don't re-enable the client though + return; + } + #else // Master or Slave + if ((_bools._hostEnabled | _bools._clientEnabled) == 1) { //If either are enabled + return; // return and do nothing + } + #endif + + #if defined(TWI1) + if (&TWI0 == _module) { + TWI0_ClearPins(); + } else if (&TWI1 == _module) { + TWI1_ClearPins(); + } + #else + TWI0_ClearPins(); + #endif + + _bools._clientEnabled = 1; + client_irq_mask = TWI_COLL_bm; + TWI_t* module = _module; + module->SADDR = (address << 1) | receive_broadcast; + module->SADDRMASK = second_address; + module->SCTRLA = TWI_DIEN_bm | TWI_APIEN_bm | TWI_PIEN_bm | TWI_ENABLE_bm; } @@ -290,7 +376,85 @@ void TwoWire::begin(uint8_t address, bool receive_broadcast, uint8_t second_addr *@retval 1 if a problem occurred */ uint8_t TwoWire::setClock(uint32_t clock) { - return TWI_MasterSetBaud(&vars, clock); + TWI_t* module = _module; + if (__builtin_constant_p(clock)) { + if ((clock < 1000) || (clock > 15000000)) { + badArg("Invalid frequency was passed for SCL clock!"); + return 1; + } + } else { + if (clock < 1000) { + return 1; + } + } + if (_bools._hostEnabled == 1) { // Do something only if the host is enabled. + uint8_t newBaud = MasterCalcBaud(clock); // get the new Baud value + uint8_t oldBaud = module->MBAUD; // load the old Baud value + if (newBaud != oldBaud) { // compare both, in case the code is issuing this before every transmission. + uint8_t restore = module->MCTRLA; // Save the old Master state + module->MCTRLA = 0; // Disable Master + module->MBAUD = newBaud; // update Baud register + if (clock > 400000) { + module->CTRLA |= TWI_FMPEN_bm; // Enable FastMode+ + } else { + module->CTRLA &= ~TWI_FMPEN_bm; // Disable FastMode+ + } + module->MCTRLA = restore; // restore the old register, thus enabling it again + if (restore & TWI_ENABLE_bm) { // If the TWI was enabled, + module->MSTATUS = TWI_BUSSTATE_IDLE_gc; // Force the state machine into IDLE according to the data sheet + } + } + return 0; + } + return 1; +} + + +/** + *@brief TWI_MasterCalcBaud calculates the baud for the desired frequency + * + *@param uint32_t frequency is the desired frequency + * + *@return uint8_t value for the MBAUD register + *@retval the desired baud value + */ +#define TWI_BAUD(freq, t_rise) ((F_CPU / freq) / 2) - (5 + (((F_CPU / 1000000) * t_rise) / 2000)) +uint8_t TwoWire::MasterCalcBaud(uint32_t frequency) { + int16_t baud; + + #if (F_CPU == 20000000) || (F_CPU == 10000000) + if (frequency >= 600000) { // assuming 1.5kOhm + baud = TWI_BAUD(frequency, 250); + } else if (frequency >= 400000) { // assuming 2.2kOhm + baud = TWI_BAUD(frequency, 350); + } else { // assuming 4.7kOhm + baud = TWI_BAUD(frequency, 600); // 300kHz will be off at 10MHz. Trade-off between size and accuracy + } + #else + if (frequency >= 600000) { // assuming 1.5kOhm + baud = TWI_BAUD(frequency, 250); + } else if (frequency >= 400000) { // assuming 2.2kOhm + baud = TWI_BAUD(frequency, 400); + } else { // assuming 4.7kOhm + baud = TWI_BAUD(frequency, 600); + } + #endif + + #if (F_CPU >= 20000000) + const uint8_t baudlimit = 2; + #elif (F_CPU == 16000000) || (F_CPU == 8000000) || (F_CPU == 4000000) + const uint8_t baudlimit = 1; + #else + const uint8_t baudlimit = 0; + #endif + + if (baud < baudlimit) { + return baudlimit; + } else if (baud > 255) { + return 255; + } + + return (uint8_t)baud; } @@ -302,7 +466,8 @@ uint8_t TwoWire::setClock(uint32_t clock) { *@return void */ void TwoWire::end(void) { - TWI_Disable(&vars); + endMaster(); + endSlave(); } @@ -313,11 +478,14 @@ void TwoWire::end(void) { * *@return void */ -#if defined(TWI_MANDS) void TwoWire::endMaster(void) { - TWI_DisableMaster(&vars); + if (true == _bools._hostEnabled) { + _module->MCTRLA = 0x00; + _module->MBAUD = 0x00; + _bools._hostEnabled = 0x00; + } } -#endif + /** @@ -327,17 +495,28 @@ void TwoWire::endMaster(void) { * *@return void */ -#if defined(TWI_MANDS) void TwoWire::endSlave(void) { - TWI_DisableSlave(&vars); + if (true == _bools._clientEnabled) { + _module->SADDR = 0x00; + _module->SCTRLA = 0x00; + _module->SADDRMASK = 0x00; + _bools._clientEnabled = 0x00; + #if defined(TWI_DUALCTRL) + _module->DUALCTRL = 0x00; // Disable pin splitting when available + #endif + } } -#endif + + + /** *@brief specialConfig allows configuring of wacky features. - * smbus evel: Vihmin and Vilmax are normally 0.7*Vdd (or VDDIO on MVIO pins) and 0.3*Vdd respectively. This option sets them to the SMBus 3.0 levels: - * In this mode, any voltage below 0.8V is guaranteed to be a LOW, and anything above 1.35 or 1.45 (see electrical characteristics in datasheet) - * This is of great utility for communication with lower voltage devices, especially where you don't have MVIO. Can also be set independently if dual mode used. - * loongsetup: The setup times are normally 4 system clocks, however this can be doubled for disagreeable devices and/or adverse bus conditions + * smbus evel: Vihmin and Vilmax are normally 0.7*Vdd (or VDDIO on MVIO pins) and 0.3*Vdd respectively. + * This option sets them to the SMBus 3.0 levels: In this mode, any voltage below 0.8V is guaranteed to be a LOW, + * and anything above 1.35 or 1.45 (see electrical characteristics in datasheet) + * This is of great utility for communication with lower voltage devices, especially where you don't have MVIO. + * Can also be set independently if dual mode used. + * longsetup: The setup times are normally 4 system clocks, however this can be doubled for disagreeable devices and/or adverse bus conditions * Four options are available for the SDA hold times. sda_hold_dual handles the dual pins. This is used for SMBus 2.0 compatibility. * WIRE_SDA_HOLD_OFF 0 - hold time off (default) * WIRE_SDA_HOLD_50 1 - short hold time @@ -418,9 +597,9 @@ uint8_t TwoWire::specialConfig( __attribute__ ((unused)) bool smbuslvl, __attrib // Now we actually call the function in twi_pins. // Notice how the non-dualctrl parts also don't have smbus levels! #if defined(TWI1) // TWI_DUALCTRL is also defined here - everything with TWI1 has dual mode - if (&TWI0 == vars._module) { + if (&TWI0 == _module) { ret |= TWI0_setConfig(smbuslvl, longsetup, sda_hold, smbuslvl_dual, sda_hold_dual); - } else if (&TWI1 == vars._module) { + } else if (&TWI1 == _module) { ret |= TWI1_setConfig(smbuslvl, longsetup, sda_hold, smbuslvl_dual, sda_hold_dual); } #else @@ -434,11 +613,114 @@ uint8_t TwoWire::specialConfig( __attribute__ ((unused)) bool smbuslvl, __attrib } + + + +/** + *@brief masterReceive sends a host READ with the specified client address + * + * This function will read an arbitrary number of bytes from the TWI module + * and store them into the specified buffer. This allows the user to use + * custom sized buffers and avoids extra copy operations through read. + * + *@param auto length - amount of bytes to be read (first arg so it is placed in r24:r25) + *@param uint8_t addr - the address of the client (7-bit) + *@param uint8_t* buffer - pointer to the memory area to be written upon. + *@param uint8_t/bool sendStop - if the transaction should be terminated with a STOP condition + * + *@return auto (uint8_t/uint16_t) - depends on the usage + *@retval amount of bytes that were actually read. If 0, no read took place due to a bus error. + */ + + +auto TwoWire::masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop) { + TWI_t *module = _module; + + TWIR_INIT_ERROR; // local variable for errors + auto dataRead = 0; + + uint8_t currentSM; + uint8_t currentStatus; + uint8_t state = 0; + #if defined (TWI_TIMEOUT_ENABLE) + uint16_t timeout = (F_CPU/1000); + #endif + + while (true) { + currentStatus = module->MSTATUS; + currentSM = currentStatus & TWI_BUSSTATE_gm; // get the current mode of the state machine + + if (currentSM == TWI_BUSSTATE_UNKNOWN_gc) { + TWIR_SET_ERROR(TWI_ERR_UNINITIALIZED); + return dataRead; + } + + #if defined(TWI_TIMEOUT_ENABLE) + if (--timeout == 0) { + if (currentSM == TWI_BUSSTATE_OWNER_gc) { + TWIR_SET_ERROR(TWI_ERR_TIMEOUT); + } else if (currentSM == TWI_BUSSTATE_IDLE_gc) { + TWIR_SET_ERROR(TWI_ERR_PULLUP); + } else { + TWIR_SET_ERROR(TWI_ERR_UNDEFINED); + } + break; + } + #endif + + if (currentStatus & TWI_ARBLOST_bm) { // Check for Bus error + TWIR_SET_ERROR(TWI_ERR_BUS_ARB); // set error flag + break; // leave RX loop + } + + + if (currentSM != TWI_BUSSTATE_BUSY_gc) { + if (state == 0x00) { + module->MADDR = ADD_READ_BIT(addr); // Send Address with read bit + state |= 0x01; + #if defined (TWI_TIMEOUT_ENABLE) + timeout = (F_CPU/1000); // reset timeout + #endif + } else { + if (currentStatus & TWI_WIF_bm) { + TWIR_SET_ERROR(TWI_ERR_RXACK); // set error flag + module->MCTRLB = TWI_MCMD_STOP_gc; // free the bus + break; + } else if (currentStatus & TWI_RIF_bm) { + *buffer = module->MDATA; + buffer++; + dataRead++; + #if defined (TWI_TIMEOUT_ENABLE) + timeout = (F_CPU/1000); // reset timeout + #endif + if (dataRead < length) { + module->MCTRLB = TWI_MCMD_RECVTRANS_gc; // send an ACK so the Slave so it can send the next byte + } else { + if (sendStop != 0) { + module->MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; // send STOP + NACK + } else { + module->MCTRLB = TWI_ACKACT_bm; // Send NACK, but no STOP + } + break; + } + } + } + } + } + #if defined(TWI_READ_ERROR_ENABLED) && defined(TWI_ERROR_ENABLED) + _errors = TWIR_GET_ERROR; // save error flags + #endif + return dataRead; +} + + + + /** - *@brief requestFrom sends a host READ with the specified client address + *@brief requestFrom is a wrapper for masterReceive providing the expected interface * - * When a greater quantity then the BUFFER_LENGTH is passed, the quantity gets - * limited to the BUFFER_LENGTH. + * When a greater quantity then the TWI_BUFFER_LENGTH is passed, the quantity gets + * limited to the TWI_BUFFER_LENGTH. * Received Bytes must be read with read(). * *@param int/uint8_t address - the address of the client @@ -448,15 +730,26 @@ uint8_t TwoWire::specialConfig( __attribute__ ((unused)) bool smbuslvl, __attrib *@return uint8_t/uint16_t *@retval amount of bytes that were actually read. If 0, no read took place due to a bus error. */ -twi_buffer_index_t TwoWire::requestFrom(uint8_t address, twi_buffer_index_t quantity, uint8_t sendStop) { - if (quantity >= BUFFER_LENGTH) { - quantity = BUFFER_LENGTH; +twi_buf_index_t TwoWire::requestFrom(uint8_t address, twi_buf_index_t quantity, uint8_t sendStop) { + if (__builtin_constant_p(quantity)) { + if (quantity > TWI_BUFFER_LENGTH) { + badArg("requestFrom requests more bytes then there is Buffer space"); + } + } + if (quantity >= TWI_BUFFER_LENGTH) { + quantity = TWI_BUFFER_LENGTH; } - vars._clientAddress = address << 1; - return TWI_MasterRead(&vars, quantity, sendStop); + + _clientAddress = address << 1; + + twi_buf_index_t count = masterReceive(quantity, _clientAddress, _hostBuffer, sendStop); + _bytesToReadWrite = count; // for available/read/peek + _bytesReadWritten = 0; + return count; } + /** *@brief beginTransmission prepares the Wire object for a host WRITE. * @@ -470,24 +763,21 @@ twi_buffer_index_t TwoWire::requestFrom(uint8_t address, twi_buffer_index_t qu *@return void */ void TwoWire::beginTransmission(uint8_t address) { - twi_buffer_index_t *txHead; - #if defined(TWI_MERGE_BUFFERS) // Same Buffers for tx/rx - txHead = &(vars._bytesToReadWrite); - #else // Separate tx/rx Buffers - txHead = &(vars._bytesToWrite); - #endif if (__builtin_constant_p(address) > 0x7F) { // Compile-time check if address is actually 7 bit long badArg("Supplied address seems to be 8 bit. Only 7-bit-addresses are supported"); return; } - // set address of targeted client - vars._clientAddress = address << 1; - (*txHead) = 0; // fill buffer from 0 + if (_bools._hostEnabled) { + // set address of targeted client + _clientAddress = address << 1; + _bytesToReadWrite = 0; // fill buffer from 0 + _bytesReadWritten = 0; + } } /** - *@brief endTransmission is the function that actually performs the (blocking) host WRITE + *@brief endTransmission is the wrapper function for masterTransmit and offers the API compatibility * * Originally, 'endTransmission' was an f(void) function. It has been modified to take * one parameter indicating whether or not a STOP should be performed on the bus. @@ -515,11 +805,111 @@ void TwoWire::beginTransmission(uint8_t address) { */ uint8_t TwoWire::endTransmission(bool sendStop) { // transmit (blocking) - return TWI_MasterWrite(&vars, sendStop); + + return masterTransmit(_bytesToReadWrite, _clientAddress, _hostBuffer, sendStop); } +/** + *@brief masterTransmit sends a host WRITE with the specified client address + * + * This function will write an arbitrary number of bytes to the TWI module + * and read the data from the specified buffer. This allows the user to use + * custom sized buffers and avoids extra copy operations through write. + * + *@param auto length - amount of bytes to be read (first arg so it is placed in r24:r25) + *@param uint8_t addr - the address of the client (7-bit) + *@param uint8_t* buffer - pointer to the memory area to be read from. + *@param uint8_t/bool sendStop - if the transaction should be terminated with a STOP condition + * + *@return uint8_t + *@retval errors (see endTransmission) + */ +uint8_t TwoWire::masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop) { + TWI_t* module = _module; + __asm__ __volatile__("\n\t" : "+z"(module)); + + TWI_INIT_ERROR; + uint8_t currentSM; + uint8_t currentStatus; + uint8_t stat = 0; + #if defined (TWI_TIMEOUT_ENABLE) + uint16_t timeout = (F_CPU/1000); + #endif + + if ((module->MCTRLA & TWI_ENABLE_bm) == 0x00) { // If the module is disabled, abort + return TWI_ERR_UNINIT; + } + + while (true) { + currentStatus = module->MSTATUS; + currentSM = currentStatus & TWI_BUSSTATE_gm; // get the current mode of the state machine + + if (currentSM == TWI_BUSSTATE_UNKNOWN_gc) { // If the bus was not initialized + return TWI_ERR_UNINIT; // abort + } + + #if defined(TWI_TIMEOUT_ENABLE) + if (--timeout == 0) { + if (currentSM == TWI_BUSSTATE_OWNER_gc) { + TWI_SET_ERROR(TWI_ERR_TIMEOUT); + } else if (currentSM == TWI_BUSSTATE_IDLE_gc) { + TWI_SET_ERROR(TWI_ERR_PULLUP); + } else { + TWI_SET_ERROR(TWI_ERR_UNDEFINED); + } + break; + } + #endif + + if (currentStatus & TWI_ARBLOST_bm) { // Check for Bus error + TWI_SET_ERROR(TWI_ERR_BUS_ARB); // set error flag + break; // leave TX loop + } + + if (currentSM != TWI_BUSSTATE_BUSY_gc) { // Undefined was excluded, so make sure it's IDLE or OWNER + if (stat == 0x00) { // At the start, we send the ADDR + module->MADDR = ADD_WRITE_BIT(addr); // clear bit 0 + stat |= 0x01; // skip this if we're done + #if defined (TWI_TIMEOUT_ENABLE) + timeout = (F_CPU/1000); // reset timeout + #endif + } else { + if (currentStatus & TWI_WIF_bm) { // ADDR was sent, check for completed write + if (currentStatus & TWI_RXACK_bm) { // got a NACK, see how much was written + if (stat & 0x02) { // bit 1 set, data was already sent + if (length != 0) // the client may send an ACK at the end. If we + TWI_SET_ERROR(TWI_ERR_ACK_DAT); // transferred everything, we can ignore the NACK + } else { // otherwise, no data sent, ADDR NACK + TWI_SET_ERROR(TWI_ERR_ACK_ADR); + } + break; + } else { // No NACK on write + if (length != 0) { // check if there is data to be written + module->MDATA = *buffer; // Writing to the register to send data + buffer++; + length--; + stat |= 0x02; // remember that we've sent data + #if defined (TWI_TIMEOUT_ENABLE) + timeout = (F_CPU/1000); // reset timeout + #endif + } else { // else there is no data to be written + break; // TX finished, leave loop, error is still TWI_NO_ERR + } + } + } + } /* dataWritten == 0 */ + } /* currentSM != TWI_BUSSTATE_BUSY_gc */ + } /* while */ + + + if ((sendStop != 0) || (TWI_ERR_SUCCESS != TWI_GET_ERROR)) { + module->MCTRLB = TWI_MCMD_STOP_gc; // Send STOP + } + return TWI_GET_ERROR; +} + /** *@brief write fills the transmit buffers, master or slave, depending on when it is called * @@ -535,26 +925,21 @@ uint8_t TwoWire::endTransmission(bool sendStop) { */ size_t TwoWire::write(uint8_t data) { uint8_t* txBuffer; - twi_buffer_index_t *txHead; + twi_buf_index_t *txHead; #if defined(TWI_MANDS) // Add following if host and client are split - if (vars._bools._toggleStreamFn == 0x01) { - txHead = &(vars._bytesToReadWriteS); - txBuffer = vars._trBufferS; + if (_bools._toggleStreamFn == 0x01) { + txHead = &(_bytesToReadWriteS); + txBuffer = _clientBuffer; } else #endif { - #if defined(TWI_MERGE_BUFFERS) // Same Buffers for tx/rx - txHead = &(vars._bytesToReadWrite); - txBuffer = vars._trBuffer; - #else // Separate tx/rx Buffers - txHead = &(vars._bytesToWrite); - txBuffer = vars._txBuffer; - #endif + txHead = &(_bytesToReadWrite); + txBuffer = _hostBuffer; } /* Put byte in txBuffer */ - if ((*txHead) < BUFFER_LENGTH) { // while buffer not full, write to it + if ((*txHead) < TWI_BUFFER_LENGTH) { // while buffer not full, write to it txBuffer[(*txHead)] = data; // Load data into the buffer (*txHead)++; // advancing the head return 1; @@ -577,7 +962,7 @@ size_t TwoWire::write(uint8_t data) { *@retval amount of bytes copied */ size_t TwoWire::write(const uint8_t *data, size_t quantity) { - twi_buffer_index_t i = 0; + twi_buf_index_t i = 0; for (; i < quantity; i++) { if (TwoWire::write(*(data++)) == 0) break; // break if buffer full @@ -601,20 +986,13 @@ size_t TwoWire::write(const uint8_t *data, size_t quantity) { *@retval amount of bytes available to read from the host buffer */ int TwoWire::available(void) { - int rxHead; #if defined(TWI_MANDS) // Add following if host and client are split - if (vars._bools._toggleStreamFn == 0x01) { - rxHead = vars._bytesToReadWriteS - vars._bytesReadWrittenS; - } else + if (_bools._toggleStreamFn == 0x01) { + return (_bytesToReadWriteS - _bytesReadWrittenS); + } #endif - { - #if defined(TWI_MERGE_BUFFERS) // Same Buffers for tx/rx - rxHead = vars._bytesToReadWrite - vars._bytesReadWritten; - #else // Separate tx/rx Buffers - rxHead = vars._bytesToRead - vars._bytesRead; - #endif - } - return rxHead; + + return (_bytesToReadWrite - _bytesReadWritten); } @@ -634,24 +1012,18 @@ int TwoWire::available(void) { */ int TwoWire::read(void) { uint8_t *rxBuffer; - twi_buffer_index_t *rxHead, *rxTail; + twi_buf_index_t *rxHead, *rxTail; #if defined(TWI_MANDS) // Add following if host and client are split - if (vars._bools._toggleStreamFn == 0x01) { - rxHead = &(vars._bytesToReadWriteS); - rxTail = &(vars._bytesReadWrittenS); - rxBuffer = vars._trBufferS; + if (_bools._toggleStreamFn == 0x01) { + rxHead = &(_bytesToReadWriteS); + rxTail = &(_bytesReadWrittenS); + rxBuffer = _clientBuffer; } else #endif { - #if defined(TWI_MERGE_BUFFERS) // Same Buffers for tx/rx - rxHead = &(vars._bytesToReadWrite); - rxTail = &(vars._bytesReadWritten); - rxBuffer = vars._trBuffer; - #else // Separate tx/rx Buffers - rxHead = &(vars._bytesToRead); - rxTail = &(vars._bytesRead); - rxBuffer = vars._rxBuffer; - #endif + rxHead = &(_bytesToReadWrite); + rxTail = &(_bytesReadWritten); + rxBuffer = _hostBuffer; } @@ -682,7 +1054,7 @@ int TwoWire::read(void) { *@retval actually read bytes. */ size_t TwoWire::readBytes(char* data, size_t quantity) { - twi_buffer_index_t i = 0; + twi_buf_index_t i = 0; for (; i < quantity; i++) { int16_t c = read(); if (c < 0) break; // break if buffer empty @@ -707,24 +1079,18 @@ size_t TwoWire::readBytes(char* data, size_t quantity) { */ int TwoWire::peek(void) { uint8_t *rxBuffer; - twi_buffer_index_t *rxHead, *rxTail; + twi_buf_index_t *rxHead, *rxTail; #if defined(TWI_MANDS) // Add following if host and client are split - if (vars._bools._toggleStreamFn == 0x01) { - rxHead = &(vars._bytesToReadWriteS); - rxTail = &(vars._bytesReadWrittenS); - rxBuffer = vars._trBufferS; + if (_bools._toggleStreamFn == 0x01) { + rxHead = &(_bytesToReadWriteS); + rxTail = &(_bytesReadWrittenS); + rxBuffer = _clientBuffer; } else #endif { - #if defined(TWI_MERGE_BUFFERS) // Same Buffers for tx/rx - rxHead = &(vars._bytesToReadWrite); - rxTail = &(vars._bytesReadWritten); - rxBuffer = vars._trBuffer; - #else // Separate tx/rx Buffers - rxHead = &(vars._bytesToRead); - rxTail = &(vars._bytesRead); - rxBuffer = vars._rxBuffer; - #endif + rxHead = &(_bytesToReadWrite); + rxTail = &(_bytesReadWritten); + rxBuffer = _hostBuffer; } if ((*rxTail) < (*rxHead)) { // if there are bytes to read @@ -744,7 +1110,20 @@ int TwoWire::peek(void) { */ void TwoWire::flush(void) { /* Turn off and on TWI module */ - TWI_Flush(&vars); + TWI_t* module = _module; + #if defined(ERRATA_TWI_FLUSH) + // badCall("The AVR DA-series parts are impacted by an errata that leaves the TWI peripheral in a non-functioning state when using flush."); + // restarting TWI hardware by hand. Extra size shouldn't matter on DA series + uint8_t temp_MCTRLA = module->MCTRLA; + uint8_t temp_SCTRLA = module->SCTRLA; + module->MCTRLA = 0x00; + module->SCTRLA = 0x00; + module->MCTRLA = temp_MCTRLA; + module->MSTATUS = 0x01; // force TWI state machine into idle state + module->SCTRLA = temp_SCTRLA; + #else + module->MCTRLB |= TWI_FLUSH_bm; + #endif } /** @@ -760,9 +1139,9 @@ void TwoWire::flush(void) { */ uint8_t TwoWire::getIncomingAddress(void) { #if defined(TWI_MANDS) // Alias handler - return vars._incomingAddress; + return _incomingAddress; #else - return vars._clientAddress; + return _clientAddress; #endif } @@ -780,9 +1159,9 @@ uint8_t TwoWire::getIncomingAddress(void) { * the last time this was called. */ -twi_buffer_index_t TwoWire::getBytesRead() { - twi_buffer_index_t num = vars._bytesTransmittedS; - vars._bytesTransmittedS = 0; +twi_buf_index_t TwoWire::getBytesRead() { + twi_buf_index_t num = _bytesTransmittedS; + _bytesTransmittedS = 0; return num; } @@ -803,7 +1182,7 @@ twi_buffer_index_t TwoWire::getBytesRead() { */ uint8_t TwoWire::slaveTransactionOpen() { - uint8_t status = vars._module->SSTATUS; + uint8_t status = _module->SSTATUS; if (!(status & TWI_AP_bm)) return 0; // If AP bit is cleared, last match was a stop condition -> not in transaction. if (status & TWI_DIR_bm) return 2; // DIR bit will be 1 if last address match was for read return 1; // Otherwise it was a write. @@ -824,7 +1203,7 @@ uint8_t TwoWire::slaveTransactionOpen() { */ void TwoWire::enableDualMode(bool fmp_enable) { #if defined(TWI_DUALCTRL) - vars._module->DUALCTRL = ((fmp_enable << TWI_FMPEN_bp) | TWI_ENABLE_bm); + _module->DUALCTRL = ((fmp_enable << TWI_FMPEN_bp) | TWI_ENABLE_bm); #else badCall("enableDualMode was called, but device does not support it"); (void) fmp_enable; // Disable unused variable warning @@ -846,9 +1225,9 @@ void TwoWire::enableDualMode(bool fmp_enable) { */ uint8_t TwoWire::checkPinLevels(void) { #if defined(TWI1) - if (&TWI0 == vars._module) { + if (&TWI0 == _module) { return TWI0_checkPinLevel(); - } else if (&TWI1 == vars._module) { + } else if (&TWI1 == _module) { return TWI1_checkPinLevel(); } else { return false; @@ -874,7 +1253,7 @@ uint8_t TwoWire::checkPinLevels(void) { */ void TwoWire::selectSlaveBuffer(void) { #if defined(TWI_MANDS) - vars._bools._toggleStreamFn = 0x01; + _bools._toggleStreamFn = 0x01; #else badCall("selectSlaveBuffer() was called, but simultaneous mode is not selected"); #endif @@ -896,7 +1275,7 @@ void TwoWire::selectSlaveBuffer(void) { */ void TwoWire::deselectSlaveBuffer(void) { #if defined(TWI_MANDS) - vars._bools._toggleStreamFn = 0x00; + _bools._toggleStreamFn = 0x00; #else badCall("deselectSlaveBuffer() was called, but simultaneous mode is not selected"); #endif @@ -905,32 +1284,105 @@ void TwoWire::deselectSlaveBuffer(void) { -/** - *@brief onSlaveIRQ is called by the interrupts and calls the interrupt handler - * - * Another little hack I had to do: This function is static, thus there is no extra copy - * when a new Wire object, like Wire1 is initialized. When I first wrote this function - * I was using Wire.vars.module and Wire1.vars.module to figure out which pointer to pass, - * but this made the compiler create a Wire1 object in some cases, where Wire1 was never used - * by the user. So I rewrote this function with the thought that if the module can be different, - * there is just one Wire object, so the code doesn't have to check if Wire is using TWI0 or TWI1 - * - * - *@param TWI_t *module - the pointer to the TWI module - * - *@return void - */ -void TwoWire::onSlaveIRQ(TWI_t *module) { // This function is static and is, thus, the only one for both - // Wire interfaces. Here is decoded which interrupt was fired. - #if defined(TWI1) && defined(TWI_USING_WIRE1) // Two TWIs available and TWI1 is used. Need to check the module - if (module == &TWI0) { - TWI_HandleSlaveIRQ(&(Wire.vars)); - } else if (module == &TWI1) { - TWI_HandleSlaveIRQ(&(Wire1.vars)); + +void TwoWire::HandleSlaveIRQ(TwoWire* wire_s) { + if (wire_s == NULL) { + return; + } + + + uint8_t *address, *buffer; + twi_buf_index_t *head, *tail; + #if defined(TWI_MANDS) + address = &(wire_s->_incomingAddress); + head = &(wire_s->_bytesToReadWriteS); + tail = &(wire_s->_bytesReadWrittenS); + buffer = wire_s->_clientBuffer; + #else + address = &(wire_s->_clientAddress); + head = &(wire_s->_bytesToReadWrite); + tail = &(wire_s->_bytesReadWritten); + buffer = wire_s->_hostBuffer; + #endif + + #if defined(TWI_MANDS) + wire_s->_bools._toggleStreamFn = 0x01; + #endif + + uint8_t action = 0; + uint8_t clientStatus = wire_s->_module->SSTATUS; + + + if (clientStatus & TWI_APIF_bm) { // Address/Stop Bit set + + if ((*head) > 0) { // At this point, we have either a START, REPSTART or a STOP + if (wire_s->user_onReceive != NULL) { // at START, head should be 0, as it was set so on the last STOP + wire_s->user_onReceive((*head)); // otherwise, we notify the use sketch, as we have an REPSTART/STOP + } + } + + if (clientStatus & TWI_AP_bm) { // Address bit set + if ((*head) == 0) { // only if there was no data (START) + pushSleep(); // push the sleep + } + (*address) = wire_s->_module->SDATA; // read address from data register + if (clientStatus & TWI_DIR_bm) { // Master is reading + (*head) = 0; // reset buffer positions for user sketch + (*tail) = 0; + + if (wire_s->user_onRequest != NULL) { + wire_s->user_onRequest(); + } + if ((*head) == 0) { // If no data to transmit, send NACK + action = TWI_ACKACT_bm | TWI_SCMD_COMPTRANS_gc; // NACK + "Wait for any Start (S/Sr) condition" + } else { + action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" + } + } else { // Master is writing + (*head) = 0; // reset buffer positions + (*tail) = 0; + action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" + } + } else { // Stop bit set + popSleep(); + + (*head) = 0; // clear whatever might be left due errors + (*tail) = 0; + action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" + } + } else if (clientStatus & TWI_DIF_bm) { // Data bit set + if (clientStatus & TWI_DIR_bm) { // Master is reading + if (clientStatus & wire_s->client_irq_mask) { // If a collision was detected, or RXACK bit is set (when it matters) + (*head) = 0; // Abort further data writes + wire_s->client_irq_mask = TWI_COLL_bm; // stop checking for NACK + action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" + } else { // RXACK bit not set, no COLL + wire_s->_bytesTransmittedS++; // increment bytes transmitted counter (for register model) + wire_s->client_irq_mask = TWI_COLL_bm | TWI_RXACK_bm; // start checking for NACK + if ((*tail) < (*head)) { // Data is available + wire_s->_module->SDATA = buffer[(*tail)]; // Writing to the register to send data + (*tail)++; // Increment counter for sent bytes + action = TWI_SCMD_RESPONSE_gc; // "Execute a byte read operation followed by Acknowledge Action" + } else { // No more data available + action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" + } + } + } else { // Master is writing + uint8_t payload = wire_s->_module->SDATA; // reading SDATA will clear the DATA IRQ flag + if ((*head) < TWI_BUFFER_LENGTH) { // make sure that we don't have a buffer overflow in case Master ignores NACK + buffer[(*head)] = payload; // save data + (*head)++; // Advance Head + if ((*head) == TWI_BUFFER_LENGTH) { // if buffer is not yet full + action = TWI_ACKACT_bm | TWI_SCMD_COMPTRANS_gc; // "Execute ACK Action succeeded by waiting for any Start (S/Sr) condition" + } else { // else buffer would overflow with next byte + action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" + } + } } - #else // Otherwise, only one Wire object is being used anyway, no need to check - (void)module; - TWI_HandleSlaveIRQ(&(Wire.vars)); + } + wire_s->_module->SCTRLB = action; // using local variable (register) reduces the amount of loading _module + #if defined(TWI_MANDS) + wire_s->_bools._toggleStreamFn = 0x00; #endif } @@ -946,7 +1398,7 @@ void TwoWire::onSlaveIRQ(TWI_t *module) { // This function is static an *@return void */ void TwoWire::onReceive(void (*function)(int)) { - vars.user_onReceive = function; + user_onReceive = function; } @@ -960,7 +1412,7 @@ void TwoWire::onReceive(void (*function)(int)) { *@return void */ void TwoWire::onRequest(void (*function)(void)) { - vars.user_onRequest = function; + user_onRequest = function; } @@ -975,7 +1427,7 @@ uint8_t TwoWire::returnError() { *@brief TWI0 Slave Interrupt vector */ ISR(TWI0_TWIS_vect) { - TwoWire::onSlaveIRQ(&TWI0); + TwoWire::HandleSlaveIRQ(twi0_wire); } @@ -984,11 +1436,61 @@ ISR(TWI0_TWIS_vect) { */ #if defined(TWI1) ISR(TWI1_TWIS_vect) { - TwoWire::onSlaveIRQ(&TWI1); + TwoWire::HandleSlaveIRQ(twi1_wire); } #endif +/** + *@brief pushSleep and popSleep handle the sleep guard + * + * When used only by one peripheral, just saving the sleep register is plenty, + * But when used by more then one, special care must be taken to restore the + * sleep settings only at the end. + * e.g. when TWI0 - START, TWI1 - START, TWI0 - STOP, TWI1 - STOP + * so, there is a counter that counts up to 15 when pushing and down to 0 when + * popping. Only at 0, the actual push and pop happen. An overflow will lead to + * unpredictable results. + * + *@param none + * + *@return void + */ +void pushSleep() { + #if defined(TWI_USING_WIRE1) + uint8_t sleepStackLoc = sleepStack; + if (sleepStackLoc > 0) { // Increment only if sleep was enabled + sleepStackLoc = (sleepStackLoc + 0x10); // use upper nibble to count - max 15 pushes + } else { + sleepStackLoc = SLPCTRL.CTRLA; // save sleep settings to sleepStack + SLPCTRL.CTRLA = sleepStackLoc & 0x01; // Set to IDLE if sleep was enabled + } + sleepStack = sleepStackLoc; + #else + sleepStack = SLPCTRL.CTRLA; // save old sleep State + SLPCTRL.CTRLA = sleepStack & 0x01; // only leave the SEN bit, if it was set + #endif +} + +void popSleep() { + #if defined(TWI_USING_WIRE1) + uint8_t sleepStackLoc = sleepStack; + if (sleepStackLoc > 0) { // only do something if sleep was enabled + if (sleepStackLoc > 0x10) { // only decrement if pushed once before + sleepStackLoc = (sleepStackLoc - 0x10); // upper nibble + } else { // at 0 we are about to put sleep back + SLPCTRL.CTRLA = sleepStackLoc; // restore sleep + sleepStackLoc = 0; // reset everything + } + sleepStack = sleepStackLoc; + } + #else + SLPCTRL.CTRLA = sleepStack; + #endif +} + + + /** * Wire object constructors with the default TWI modules. * If there is absolutely no way to swap the pins physically, diff --git a/megaavr/libraries/Wire/src/Wire.h b/megaavr/libraries/Wire/src/Wire.h index c19b6d29..d0485315 100644 --- a/megaavr/libraries/Wire/src/Wire.h +++ b/megaavr/libraries/Wire/src/Wire.h @@ -29,9 +29,6 @@ #include -extern "C" { -#include "twi.h" -} /* The Wire library unfortunately needs TWO buffers, one for TX, and one for RX. That means, multiply these * values by 2 to get the actual amount of RAM they take. You can see that on the smallest ram sizes, all but * the minuscule buffer we provide becomes prohibitive. 32b is a magic number because it's used on the stock @@ -46,6 +43,42 @@ extern "C" { * and while the enhanced wire library *will* fit on 2k parts, you have very little flash left for anything else. * and the practicality of using it there is limited. */ + + + #ifndef ADD_READ_BIT + #define ADD_READ_BIT(address) (address | 0x01) +#endif +#ifndef ADD_WRITE_BIT + #define ADD_WRITE_BIT(address) (address & ~0x01) +#endif + +#ifndef DEFAULT_FREQUENCY + #define DEFAULT_FREQUENCY 100000 +#endif + + +#if (!defined(TWI1) && defined(TWI_USING_WIRE1)) + // If pins for Wire1 are not defined, but TWI_USING_WIRE1 was defined in the boards.txt menu, throw an error. Used for 28-pin DA/DB parts + #error "This part only provides a single Wire interface." +#endif + +#if ((defined(TWI0_DUALCTRL) && !defined(TWI_USING_WIRE1)) || (defined(TWI1_DUALCTRL) && defined(TWI_USING_WIRE1))) + /* Instead of requiring changes to the library to switch between DxCore and megaTinyCore, we can check + * if the part supports dual mode. Goal is that the identical library can be used on both, so updates + * in one can be propagated to the other by just copying files. */ + #define TWI_DUALCTRL // This identifies if the device supports dual mode, where slave pins are different from the master pins +#endif + + +#if defined(__AVR_ATtiny202__) || defined(__AVR_ATtiny202__) + #if defined(TWI_MANDS) // 202 and 402 do not support independent master and slave. + // #undef TWI_MANDS + #error "Master + Slave mode is not supported on the 202 or 402." + // If a user enables master + slave mode on a part where we know it won't we should error + // so that they know what's wrong instead of silently disobeying + #endif +#endif + // WIRE_HAS_END means Wire has end(), which almost all implementations do. #ifndef WIRE_HAS_END @@ -69,9 +102,137 @@ extern "C" { #define WIRE_I2C_LEVELS 0 #define WIRE_SMBUS_LEVELS 1 + + +#if !defined(TWI_BUFFER_LENGTH) // we need a Buffer size + #if defined(BUFFER_LENGTH) && (BUFFER_LENGTH != 32) // Backwards Compatibility: someone needs a non-default size + #define TWI_BUFFER_LENGTH BUFFER_LENGTH // Apply it. + #warning "It seems like BUFFER_LENGTH was used to change the default Wire Buffer Size." + #warning "The define was renamed to TWI_BUFFER_LENGTH to reduce ambiguity." + #warning "TWI_BUFFER_LENGTH was set to BUFFER_LENGTH." // defining TWI_BUFFER_LENGTH= will remove the warnings + + #else // BUFFER_LENGTH was not messed with, go with our defaults + #if (RAMSIZE < 256) + #define TWI_BUFFER_LENGTH 16 + #elif (RAMSIZE < 4096) + #define TWI_BUFFER_LENGTH 32 + #else + #define TWI_BUFFER_LENGTH 130 + #endif + #endif +#endif + +// In the case someone wants to use custom 255+ buffer sizes, define TWI_16BIT_BUFFER +#if (TWI_BUFFER_LENGTH > 255) || defined(TWI_16BIT_BUFFER) + typedef uint16_t twi_buf_index_t; +#else + typedef uint8_t twi_buf_index_t; +#endif + + +#define TWI_TIMEOUT_ENABLE // Enabled by default, might be disabled for debugging or other reasons +#define TWI_ERROR_ENABLED // Enabled by default, TWI Master Write error functionality +//#define TWI_READ_ERROR_ENABLED // Enabled on Master Read too +//#define DISABLE_NEW_ERRORS // Disables the new error codes and returns TWI_ERR_UNDEFINED instead. + +// Errors from Arduino documentation: +#define TWI_ERR_SUCCESS 0x00 // Default +#define TWI_ERR_DATA_TOO_LONG 0x01 // Not used here; data too long to fit in TX buffer +#define TWI_ERR_ACK_ADR 0x02 // Address was NACKed on Master write +#define TWI_ERR_ACK_DAT 0x03 // Data was NACKed on Master write +#define TWI_ERR_UNDEFINED 0x04 // Software can't tell error source +#define TWI_ERR_TIMEOUT 0x05 // TWI Timed out on data rx/tx + +// Errors that are made to help finding errors on TWI lines. Only here to give a suggestion of where to look - these may not always be reported accurately. +#if !defined(DISABLE_NEW_ERRORS) + #define TWI_ERR_UNINIT 0x10 // TWI was in bad state when function was called. + #define TWI_ERR_PULLUP 0x11 // Likely problem with pull-ups + #define TWI_ERR_BUS_ARB 0x12 // Bus error and/or Arbitration lost + #define TWI_ERR_BUF_OVERFLOW 0x13 // Buffer overflow on master read + #define TWI_ERR_CLKHLD 0x14 // Something's holding the clock +#else + // DISABLE_NEW_ERRORS can be used to more completely emulate the old error reporting behavior; this should rarely be needed. + #define TWI_ERR_UNINIT TWI_ERR_UNDEFINED // TWI was in bad state when method was called. + #define TWI_ERR_PULLUP TWI_ERR_UNDEFINED // Likely problem with pull-ups + #define TWI_ERR_BUS_ARB TWI_ERR_UNDEFINED // Bus error and/or Arbitration lost + #define TWI_ERR_BUF_OVERFLOW TWI_ERR_UNDEFINED // Buffer overflow on master read + #define TWI_ERR_CLKHLD TWI_ERR_UNDEFINED // Something's holding the clock +#endif + +#if defined(TWI_ERROR_ENABLED) + #define TWI_ERROR_VAR twi_error + #define TWI_INIT_ERROR uint8_t TWI_ERROR_VAR = TWI_ERR_SUCCESS + #define TWI_GET_ERROR TWI_ERROR_VAR + #define TWI_CHK_ERROR(x) TWI_ERROR_VAR == x + #define TWI_SET_ERROR(x) TWI_ERROR_VAR = x +#else + #define TWI_ERROR_VAR {} + #define TWI_INIT_ERROR {} + #define TWI_GET_ERROR {0} + #define TWI_CHK_ERROR(x) (true) + #define TWI_SET_ERROR(x) {} +#endif + +#if defined(TWI_READ_ERROR_ENABLED) && defined(TWI_ERROR_ENABLED) + #define TWIR_ERROR_VAR twiR_error + #define TWIR_INIT_ERROR uint8_t TWIR_ERROR_VAR = TWI_ERR_SUCCESS + #define TWIR_GET_ERROR TWIR_ERROR_VAR + #define TWIR_CHK_ERROR(x) TWIR_ERROR_VAR == x + #define TWIR_SET_ERROR(x) TWIR_ERROR_VAR = x + + // #define TWI_SET_EXT_ERROR(x) TWI_ERROR_VAR = x +#else + #define TWIR_ERROR_VAR {} + #define TWIR_INIT_ERROR {} + #define TWIR_GET_ERROR {0} + #define TWIR_CHK_ERROR(x) (true) + #define TWIR_SET_ERROR(x) {} + + // #define TWI_SET_EXT_ERROR(x) {} +#endif + + +struct twiDataBools { // using a struct so the compiler can use skip if bit is set/cleared + bool _toggleStreamFn: 1; // used to toggle between Slave and Master elements when TWI_MANDS defined + bool _hostEnabled: 1; + bool _clientEnabled: 1; + uint8_t _reserved: 5; +}; + + + + + class TwoWire: public Stream { private: - twiData vars; // We're using a struct to reduce the amount of parameters that have to be passed. + TWI_t *_module; + uint8_t MasterCalcBaud(uint32_t frequency); + + uint8_t client_irq_mask; + struct twiDataBools _bools; // the structure to hold the bools for the class + #if defined(TWI_READ_ERROR_ENABLED) + uint8_t _errors; + #endif + + void (*user_onRequest)(void); + void (*user_onReceive)(int); + + uint8_t _clientAddress; + twi_buf_index_t _bytesToReadWrite; + twi_buf_index_t _bytesReadWritten; + twi_buf_index_t _bytesTransmittedS; + + #if defined(TWI_MANDS) + uint8_t _incomingAddress; + twi_buf_index_t _bytesToReadWriteS; + twi_buf_index_t _bytesReadWrittenS; + #endif + + uint8_t _hostBuffer[TWI_BUFFER_LENGTH]; + #if defined(TWI_MANDS) + uint8_t _clientBuffer[TWI_BUFFER_LENGTH]; + #endif + public: explicit TwoWire(TWI_t *twi_module); bool pins(uint8_t sda_pin, uint8_t scl_pin); @@ -96,7 +257,10 @@ class TwoWire: public Stream { return endTransmission(true); } - twi_buffer_index_t requestFrom(uint8_t address, twi_buffer_index_t quantity, uint8_t sendStop = 1); + twi_buf_index_t requestFrom(uint8_t address, twi_buf_index_t quantity, uint8_t sendStop = 1); + + uint8_t masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop); + auto masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop); virtual size_t write(uint8_t); virtual size_t write(const uint8_t *, size_t); @@ -106,7 +270,7 @@ class TwoWire: public Stream { void flush(void); uint8_t specialConfig(bool smbuslvl = 0, bool longsetup = 0, uint8_t sda_hold = 0, bool smbuslvl_dual = 0, uint8_t sda_hold_dual = 0); uint8_t getIncomingAddress(void); - twi_buffer_index_t getBytesRead(void); + twi_buf_index_t getBytesRead(void); uint8_t slaveTransactionOpen(void); uint8_t checkPinLevels(void); // Can be used to make sure after boot that SDA/SCL are high void enableDualMode(bool fmp_enable); // Moves the Slave to dedicated pins @@ -141,9 +305,10 @@ class TwoWire: public Stream { uint8_t returnError(); #endif - static void onSlaveIRQ(TWI_t *module); // is called by the TWI interrupt routines + static void HandleSlaveIRQ(TwoWire* wire_s); }; + #if defined(TWI0) extern TwoWire Wire; #endif diff --git a/megaavr/libraries/Wire/src/twi.c b/megaavr/libraries/Wire/src/twi.c deleted file mode 100644 index af00d6a7..00000000 --- a/megaavr/libraries/Wire/src/twi.c +++ /dev/null @@ -1,692 +0,0 @@ -/* - Copyright (c) 2022 MX682X - -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. -*/ -// *INDENT-OFF* astyle wants this file to be completely unreadable with no indentation for the many preprocessor conditionals! - -#include "Arduino.h" -#include "twi.h" -#include "twi_pins.h" - -#if !defined(_fastPtr_y) - //Supplied by dirty_tricks.h normally, which gets included by Arduino.h - but this library may want to be ported say, to MegaCoreX. - #define _fastPtr_y(__localVar__, __pointer__) __asm__ __volatile__("\n\t": "=&y" (__localVar__) : "0" (__pointer__)); // r28:r29 -#endif - - -static uint8_t sleepStack = 0; - -void pushSleep(void); -void popSleep(void); -// Function definitions -/** - *@brief TWI_MasterInit Initializes TWI host operation if not already initialized - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - * - *@return void - */ -void TWI_MasterInit(struct twiData *_data) { - #if defined(TWI_MANDS) // Check if the user wants to use Master AND Slave - if (_data->_bools._hostEnabled == 1) { // Slave is allowed to be enabled, don't re-enable the host though - return; - } - #else // Master OR Slave - if ((_data->_bools._hostEnabled | _data->_bools._clientEnabled) == 1) { //If either are enabled - return; // return and do nothing - } - #endif - - - #if defined(TWI1) // More then one TWI used - if (&TWI0 == _data->_module) { // check which one this function is working with - TWI0_ClearPins(); - } else if (&TWI1 == _data->_module) { - TWI1_ClearPins(); - } - #else // Only one TWI is used - TWI0_ClearPins(); // Only one option is possible - #endif - - _data->_bools._hostEnabled = 1; - _data->_module->MCTRLA = TWI_ENABLE_bm; // Master Interrupt flags stay disabled - _data->_module->MSTATUS = TWI_BUSSTATE_IDLE_gc; - - TWI_MasterSetBaud(_data, DEFAULT_FREQUENCY); -} - - -/** - *@brief TWI_SlaveInit Initializes TWI client operation if not already initialized - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - *@param uint8_t address holds the primary address that this TWI client should listen to - *@param uint8_t receive_broadcast if true, instructs the TWI client to react to the - * general TWI call 0x00 - *@param uint8_t second_address holds the data for the SADDRMASK register. If the LSB is '1' - * the TWI handles the 7 MSB as a second address for the client, otherwise the 7 MSB - * act as a bit mask, that disables the check on the corresponding SADDR bit. - * - *@return void - */ -void TWI_SlaveInit(struct twiData *_data, uint8_t address, uint8_t receive_broadcast, uint8_t second_address) { - #if defined(TWI_MANDS) // Check if the user wants to use Master AND Slave - if (_data->_bools._clientEnabled == 1) { // Master is allowed to be enabled, don't re-enable the client though - return; - } - #else // Master or Slave - if ((_data->_bools._hostEnabled | _data->_bools._clientEnabled) == 1) { //If either are enabled - return; // return and do nothing - } - #endif - - #if defined(TWI1) - if (&TWI0 == _data->_module) { - TWI0_ClearPins(); - } else if (&TWI1 == _data->_module) { - TWI1_ClearPins(); - } - #else - TWI0_ClearPins(); - #endif - - _data->_bools._clientEnabled = 1; - _data->_module->SADDR = (address << 1) | receive_broadcast; - _data->_module->SADDRMASK = second_address; - _data->_module->SCTRLA = TWI_DIEN_bm | TWI_APIEN_bm | TWI_PIEN_bm | TWI_ENABLE_bm; - - /* Bus Error Detection circuitry needs Master enabled to work */ - //_data->_module->MCTRLA = TWI_ENABLE_bm; -} - - -/** - *@brief TWI_Flush clears the internal state of the host and changes the bus state to idle - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - * - *@return void - */ -void TWI_Flush(struct twiData *_data) { - #if defined(ERRATA_TWI_FLUSH) - // badCall("The AVR DA-series parts are impacted by an errata that leaves the TWI peripheral in a non-functioning state when using flush."); - // restarting TWI hardware by hand. Extra size shouldn't matter on DA series - uint8_t temp_MCTRLA = _data->_module->MCTRLA; - uint8_t temp_SCTRLA = _data->_module->SCTRLA; - _data->_module->MCTRLA = 0x00; - _data->_module->SCTRLA = 0x00; - _data->_module->MCTRLA = temp_MCTRLA; - _data->_module->MSTATUS = 0x01; // force TWI state machine into idle state - _data->_module->SCTRLA = temp_SCTRLA; - #else - _data->_module->MCTRLB |= TWI_FLUSH_bm; - #endif -} - - -/** - *@brief TWI_Disable disables the TWI host and client - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - * - *@return void - */ -void TWI_Disable(struct twiData *_data) { - TWI_DisableMaster(_data); - TWI_DisableSlave(_data); -} - - -/** - *@brief TWI_DisableMaster disables the TWI host - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - * - *@return void - */ -void TWI_DisableMaster(struct twiData *_data) { - if (true == _data->_bools._hostEnabled) { - _data->_module->MCTRLA = 0x00; - _data->_module->MBAUD = 0x00; - _data->_bools._hostEnabled = 0x00; - } -} - - -/** - *@brief TWI_DisableSlave disables the TWI client - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - * - *@return void - */ -void TWI_DisableSlave(struct twiData *_data) { - if (true == _data->_bools._clientEnabled) { - _data->_module->SADDR = 0x00; - _data->_module->SCTRLA = 0x00; - _data->_module->SADDRMASK = 0x00; - _data->_bools._clientEnabled = 0x00; - #if defined(TWI_DUALCTRL) - _data->_module->DUALCTRL = 0x00; // Disable pin splitting when available - #endif - } -} - - - -/** - *@brief TWI_MasterSetBaud sets the baud register to get the desired frequency - * - * After checking if the master mode is actually enabled, the new baud is calculated. - * Then it is compared to the old baud. Only if they differ, the TWI is disabled, - * the baud register updated, and the TWI re-enabled - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - * - *@return uint8_t 1 - something went wrong; 0 - new frequency set. - */ -uint8_t TWI_MasterSetBaud(struct twiData *_data, uint32_t frequency) { - if (__builtin_constant_p(frequency)) { - if ((frequency < 1000) || (frequency > 15000000)) { - badArg("Invalid frequency was passed for SCL clock!"); - return 1; - } - } else { - if (frequency < 1000) { - return 1; - } - } - if (_data->_bools._hostEnabled == 1) { // Do something only if the host is enabled. - uint8_t newBaud = TWI_MasterCalcBaud(frequency); // get the new Baud value - uint8_t oldBaud = _data->_module->MBAUD; // load the old Baud value - if (newBaud != oldBaud) { // compare both, in case the code is issuing this before every transmission. - uint8_t restore = _data->_module->MCTRLA; // Save the old Master state - _data->_module->MCTRLA = 0; // Disable Master - _data->_module->MBAUD = newBaud; // update Baud register - if (frequency > 400000) { - _data->_module->CTRLA |= TWI_FMPEN_bm; // Enable FastMode+ - } else { - _data->_module->CTRLA &= ~TWI_FMPEN_bm; // Disable FastMode+ - } - _data->_module->MCTRLA = restore; // restore the old register, thus enabling it again - if (restore & TWI_ENABLE_bm) { // If the TWI was enabled, - _data->_module->MSTATUS = TWI_BUSSTATE_IDLE_gc; // Force the state machine into IDLE according to the data sheet - } - } - return 0; - } - return 1; -} - -/** - *@brief TWI_MasterCalcBaud calculates the baud for the desired frequency - * - *@param uint32_t frequency is the desired frequency - * - *@return uint8_t value for the MBAUD register - *@retval the desired baud value - */ -#define TWI_BAUD(freq, t_rise) ((F_CPU / freq) / 2) - (5 + (((F_CPU / 1000000) * t_rise) / 2000)) -uint8_t TWI_MasterCalcBaud(uint32_t frequency) { - int16_t baud; - - #if (F_CPU == 20000000) || (F_CPU == 10000000) - if (frequency >= 600000) { // assuming 1.5kOhm - baud = TWI_BAUD(frequency, 250); - } else if (frequency >= 400000) { // assuming 2.2kOhm - baud = TWI_BAUD(frequency, 350); - } else { // assuming 4.7kOhm - baud = TWI_BAUD(frequency, 600); // 300kHz will be off at 10MHz. Trade-off between size and accuracy - } - #else - if (frequency >= 600000) { // assuming 1.5kOhm - baud = TWI_BAUD(frequency, 250); - } else if (frequency >= 400000) { // assuming 2.2kOhm - baud = TWI_BAUD(frequency, 400); - } else { // assuming 4.7kOhm - baud = TWI_BAUD(frequency, 600); - } - #endif - - #if (F_CPU >= 20000000) - const uint8_t baudlimit = 2; - #elif (F_CPU == 16000000) || (F_CPU == 8000000) || (F_CPU == 4000000) - const uint8_t baudlimit = 1; - #else - const uint8_t baudlimit = 0; - #endif - - if (baud < baudlimit) { - return baudlimit; - } else if (baud > 255) { - return 255; - } - - return (uint8_t)baud; -} - -/** - *@brief TWI_MasterWrite performs a host write operation on the TWI bus - * - * As soon as the bus is in an idle state, a polled write operation is performed - * A STOP condition can be send at the end, or not if a REP START is wanted - * The user has to make sure to have a host write or read at the end with a STOP - * - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - *@param bool send_stop enables the STOP condition at the end of a write - * - *@return uint8_t - *@retval error code, if enabled. Not 0 means something went wrong - */ -uint8_t TWI_MasterWrite(struct twiData *_data, bool send_stop) { - uint8_t* txBuffer; - twi_buffer_index_t *txHead; - #if defined(TWI_MERGE_BUFFERS) // Same Buffers for tx/rx - txHead = &(_data->_bytesToReadWrite); - txBuffer = _data->_trBuffer; - #else // Separate tx/rx Buffers - txHead = &(_data->_bytesToWrite); - txBuffer = _data->_txBuffer; - #endif - - TWI_t *module = _data->_module; // Compiler treats the pointer to the TWI module as volatile and - // creates bloat-y code, this fixes it - TWI_INIT_ERROR; - uint8_t currentSM; - uint8_t currentStatus; - twi_buffer_index_t dataWritten = 0; - #if defined (TWI_TIMEOUT_ENABLE) - uint16_t timeout = 0; - #endif - - - if (((module->MSTATUS & TWI_BUSSTATE_gm) == TWI_BUSSTATE_UNKNOWN_gc) || // If the bus was not initialized - ((module->MCTRLA & TWI_ENABLE_bm) == false)) { // Or is disabled, - return TWI_ERR_UNINIT; // return - } - - while (true) { - currentStatus = module->MSTATUS; - currentSM = currentStatus & TWI_BUSSTATE_gm; // get the current mode of the state machine - - #if defined(TWI_TIMEOUT_ENABLE) - if (++timeout > (F_CPU/1000)) { - if (currentSM == TWI_BUSSTATE_OWNER_gc) { - TWI_SET_ERROR(TWI_ERR_TIMEOUT); - } else if (currentSM == TWI_BUSSTATE_IDLE_gc) { - TWI_SET_ERROR(TWI_ERR_PULLUP); - } else { - TWI_SET_ERROR(TWI_ERR_UNDEFINED); - } - break; - } - #endif - - if (currentStatus & TWI_ARBLOST_bm) { // Check for Bus error - module->MSTATUS = TWI_ARBLOST_bm; // reset error flags - TWI_SET_ERROR(TWI_ERR_BUS_ARB); // set error flag - break; // leave TX loop - } - - if (currentSM == TWI_BUSSTATE_IDLE_gc) { // Bus has not sent START yet and is not BUSY - module->MADDR = ADD_WRITE_BIT(_data->_clientAddress); - #if defined (TWI_TIMEOUT_ENABLE) - timeout = 0; // reset timeout - #endif - } else if (currentSM == TWI_BUSSTATE_OWNER_gc) { // Address was sent, host is owner - if (currentStatus & TWI_WIF_bm) { // data sent - if (currentStatus & TWI_RXACK_bm) { // AND the RXACK bit is set, last byte has failed - if (dataWritten == 0) TWI_SET_ERROR(TWI_ERR_ACK_ADR); // if dataWritten is 0, no payload was sent, so address was NACKed - else TWI_SET_ERROR(TWI_ERR_ACK_DAT); // else payload was NACKed - break; // leave loop - } else { // otherwise WRITE was ACKed - if (dataWritten < (*txHead)) { // check if there is data to be written - module->MDATA = txBuffer[dataWritten]; // Writing to the register to send data - dataWritten++; // data was Written - #if defined (TWI_TIMEOUT_ENABLE) - timeout = 0; // reset timeout - #endif - } else { // else there is no data to be written - break; // TX finished, leave loop, error is still TWI_NO_ERR - } - } - } - } - } - - - if ((send_stop != 0) || (TWI_ERR_SUCCESS != TWI_GET_ERROR)) { - module->MCTRLB = TWI_MCMD_STOP_gc; // Send STOP - } - return TWI_GET_ERROR; -} - - -/** - *@brief TWI_MasterRead performs a host read operation on the TWI bus - * - * As soon as the bus is in an idle state, a polled read operation is performed - * A STOP condition can be send at the end, or not if a REP START is wanted - * The user has to make sure to have a host write or read at the end with a STOP - * - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - *@param uint8_t/uint16_t bytesToRead is the desired amount of bytes to read. When finished, a - * NACK is issued. - *@param bool send_stop enables the STOP condition at the end of a write - * - *@return uint8_t/uint16_t - amount of actually read bytes - *@retval amount of bytes that were actually read. If 0, no read took place due to a bus error - */ -twi_buffer_index_t TWI_MasterRead(struct twiData *_data, twi_buffer_index_t bytesToRead, bool send_stop) { - uint8_t* rxBuffer; - twi_buffer_index_t *rxHead, *rxTail; - #if defined(TWI_MERGE_BUFFERS) // Same Buffers for tx/rx - rxHead = &(_data->_bytesToReadWrite); - rxTail = &(_data->_bytesReadWritten); - rxBuffer = _data->_trBuffer; - #else // Separate tx/rx Buffers - rxHead = &(_data->_bytesToRead); - rxTail = &(_data->_bytesRead); - rxBuffer = _data->_rxBuffer; - #endif - - (*rxTail) = 0; // Reset counter - - TWI_t *module = _data->_module; // Compiler treats the pointer to the TWI module as volatile and - // creates bloat-y code, using a local variable fixes that - - TWIR_INIT_ERROR; // local variable for errors - twi_buffer_index_t dataRead = 0; - - if ((module->MSTATUS & TWI_BUSSTATE_gm) != TWI_BUSSTATE_UNKNOWN_gc) { - uint8_t currentSM; - uint8_t currentStatus; - uint8_t command = 0; - #if defined (TWI_TIMEOUT_ENABLE) - uint16_t timeout = 0; - #endif - - module->MADDR = ADD_READ_BIT(_data->_clientAddress); // Send Address with read bit - - while (true) { - currentStatus = module->MSTATUS; - currentSM = currentStatus & TWI_BUSSTATE_gm; // get the current mode of the state machine - - #if defined(TWI_TIMEOUT_ENABLE) - if (++timeout > (F_CPU/1000)) { - if (currentSM == TWI_BUSSTATE_OWNER_gc) { - TWIR_SET_ERROR(TWI_ERR_TIMEOUT); - } else if (currentSM == TWI_BUSSTATE_IDLE_gc) { - TWIR_SET_ERROR(TWI_ERR_PULLUP); - } else { - TWIR_SET_ERROR(TWI_ERR_UNDEFINED); - } - break; - } - #endif - - if (currentStatus & TWI_ARBLOST_bm) { // Check for Bus error - module->MSTATUS = TWI_ARBLOST_bm; // reset error flags - TWIR_SET_ERROR(TWI_ERR_BUS_ARB); // set error flag - break; // leave TX loop - } - - if (command != 0) { - if (currentSM == TWI_BUSSTATE_OWNER_gc) { - module->MCTRLB = command; - } else { - break; - } - } - - if (currentSM == TWI_BUSSTATE_OWNER_gc) { // Address sent, check for WIF/RIF - if (currentStatus & TWI_RIF_bm) { // data received - if (dataRead < BUFFER_LENGTH) { // Buffer still free - rxBuffer[dataRead] = module->MDATA; // save byte in the Buffer. - dataRead++; // increment read counter - #if defined (TWI_TIMEOUT_ENABLE) - timeout = 0; // reset timeout - #endif - - if (dataRead < bytesToRead) { // expecting more bytes, so - module->MCTRLB = TWI_MCMD_RECVTRANS_gc; // send an ACK so the Slave so it can send the next byte - } else { // Otherwise, - if (send_stop != 0) { - command = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; // send STOP + NACK - } else { - break; - } - } - - } else { // Buffer overflow with the incoming byte - TWIR_SET_ERROR(TWI_ERR_BUF_OVERFLOW); // Set Error and - command = TWI_ACKACT_bm | TWI_MCMD_STOP_gc; // send STOP + NACK - } - } else if (currentStatus & TWI_WIF_bm) { // Address NACKed - TWIR_SET_ERROR(TWI_ERR_RXACK); // set error flag - command = TWI_MCMD_STOP_gc; // free the bus - } - } - } - (*rxHead) = dataRead; - } else { - TWIR_SET_ERROR(TWI_ERR_UNINIT); - } - #if defined(TWI_READ_ERROR_ENABLED) && defined(TWI_ERROR_ENABLED) - _data->_errors = TWIR_GET_ERROR; // save error flags - #endif - return dataRead; -} - - -/** - *@brief TWI_HandleSlaveIRQ checks the status register and decides the next action based on that - * - * This function handles the complete slave mode interaction, from (N)ACKing data and address - * over loading and storing transmitted data to calling the call-back functions if they were - * given. Previous versions of the Wire library had this split up, but using the Y-register - * and displacement requires it for everything to be in one place. - * - *@param struct twiData *_data is a pointer to the structure that holds the Wire variables - * - *@return void - */ - -void TWI_HandleSlaveIRQ(struct twiData *_data) { - // The following assembly helps the compiler to optimize variable access. It does not realize that accessing the - // struct members is faster and smaller by using a second displacement register (Y). Usually the Y register is - // used as a frame pointer and the compiler avoids using it. So, using it comes with a price of pushing/popping - // r28/r29, as well as ldi the address of the struct into those, so it only makes sense in a few cases like this. - // Since the Y register is not call-clobbered, it can be considered persistent in this function and has not to - // be restored after the icall to the user callbacks, unlike Z (which is used for _module and icall). - - _fastPtr_y(_data,_data); // force _data into Y and instruct to not change Y - - uint8_t *address, *txBuffer, *rxBuffer; - twi_buffer_index_t *txHead, *txTail, *rxHead, *rxTail; - #if defined(TWI_MANDS) - address = &(_data->_incomingAddress); - txHead = &(_data->_bytesToReadWriteS); - txTail = &(_data->_bytesReadWrittenS); - - rxHead = &(_data->_bytesToReadWriteS); - rxTail = &(_data->_bytesReadWrittenS); - - txBuffer = _data->_trBufferS; - rxBuffer = _data->_trBufferS; - #else - address = &(_data->_clientAddress); - #if defined(TWI_MERGE_BUFFERS) - txHead = &(_data->_bytesToReadWrite); - txTail = &(_data->_bytesReadWritten); - - rxHead = &(_data->_bytesToReadWrite); - rxTail = &(_data->_bytesReadWritten); - - txBuffer = _data->_trBuffer; - rxBuffer = _data->_trBuffer; - #else - txHead = &(_data->_bytesToWrite); - txTail = &(_data->_bytesWritten); - - rxHead = &(_data->_bytesToRead); - rxTail = &(_data->_bytesRead); - - txBuffer = _data->_txBuffer; - rxBuffer = _data->_rxBuffer; - #endif - #endif - - #if defined(TWI_MANDS) - _data->_bools._toggleStreamFn = 0x01; - #endif - - uint8_t action = 0; - uint8_t clientStatus = _data->_module->SSTATUS; - - if (clientStatus & TWI_APIF_bm) { // Address/Stop Bit set - if (clientStatus & TWI_AP_bm) { // Address bit set - uint8_t payload = _data->_module->SDATA; // read address from data register - if (clientStatus & TWI_DIR_bm) { // Master is reading - if ((*rxHead) > 0) { // There is no way to identify a REPSTART, - popSleep(); // (have to treat REPSTART as another pop for sleep) - if (_data->user_onReceive != NULL) { // so when a Master Read occurs after a Master write - _data->user_onReceive((*rxHead)); // issue a call to the user callback first - } - } - (*address) = payload; // saving address to expose to the user sketch - (*txHead) = 0; // reset buffer positions so the Master can start writing at zero. - (*txTail) = 0; - - if (_data->user_onRequest != NULL) { - _data->user_onRequest(); - } - if ((*txHead) == 0) { // If no data to transmit, send NACK - action = TWI_ACKACT_bm | TWI_SCMD_COMPTRANS_gc; // NACK + "Wait for any Start (S/Sr) condition" - } else { - action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" - } - } else { // Master is writing - action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" - (*address) = payload; // saving address to expose to the user sketch - (*rxHead) = 0; // reset buffer positions so the Master can start writing at zero. - (*rxTail) = 0; - } - pushSleep(); - } else { // Stop bit set - popSleep(); - if (_data->user_onReceive != NULL) { - if ((*rxHead) > 0) { - _data->user_onReceive((*rxHead)); - } - } - action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" - (*rxHead) = 0; - (*txHead) = 0; - (*rxTail) = 0; - (*txTail) = 0; - } - } else if (clientStatus & TWI_DIF_bm) { // Data bit set - if (clientStatus & TWI_DIR_bm) { // Master is reading - if ((clientStatus & (TWI_COLL_bm | TWI_RXACK_bm)) && // If a collision was detected, or RXACK bit is set AND - (true == _data->_bools._ackMatters)) { // And we have to check for it - (*txHead) = 0; // Abort further data writes - _data->_bools._ackMatters = false; // stop checking for NACK - action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" - } else { // RXACK bit not set, no COLL - _data->_bytesTransmittedS++; // increment bytes transmitted counter (for register model) - _data->_bools._ackMatters = true; // start checking for NACK - if ((*txTail) < (*txHead)) { // Data is available - _data->_module->SDATA = txBuffer[(*txTail)]; // Writing to the register to send data - (*txTail)++; // Increment counter for sent bytes - action = TWI_SCMD_RESPONSE_gc; // "Execute a byte read operation followed by Acknowledge Action" - } else { // No more data available - action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" - } - } - } else { // Master is writing - uint8_t payload = _data->_module->SDATA; // reading SDATA will clear the DATA IRQ flag - if ((*rxHead) < BUFFER_LENGTH) { // make sure that we don't have a buffer overflow in case Master ignores NACK - rxBuffer[(*rxHead)] = payload; // save data - (*rxHead)++; // Advance Head - if ((*rxHead) < BUFFER_LENGTH) { // if buffer is not yet full - action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" - } else { // else buffer would overflow with next byte - action = TWI_ACKACT_bm | TWI_SCMD_COMPTRANS_gc; // "Execute ACK Action succeeded by waiting for any Start (S/Sr) condition" - } - } - } - } - _data->_module->SCTRLB = action; // using local variable (register) reduces the amount of loading _module - #if defined(TWI_MANDS) - _data->_bools._toggleStreamFn = 0x00; - #endif -} - - -/** - *@brief pushSleep and popSleep handle the sleep guard - * - * When used only by one peripheral, just saving the sleep register is plenty, - * But when used by more then one, special care must be taken to restore the - * sleep settings only at the end. - * e.g. when TWI0 - START, TWI1 - START, TWI0 - STOP, TWI1 - STOP - * so, there is a counter that counts up to 15 when pushing and down to 0 when - * popping. Only at 0, the actual push and pop happen. An overflow will lead to - * unpredictable results. - * - *@param none - * - *@return void - */ -void pushSleep() { - #if defined(TWI_USING_WIRE1) - uint8_t sleepStackLoc = sleepStack; - if (sleepStackLoc > 0) { // Increment only if sleep was enabled - sleepStackLoc = (sleepStackLoc + 0x10); // use upper nibble to count - max 15 pushes - } else { - sleepStackLoc = SLPCTRL.CTRLA; // save sleep settings to sleepStack - SLPCTRL.CTRLA = sleepStackLoc & 0x01; // Set to IDLE if sleep was enabled - } - sleepStack = sleepStackLoc; - #else - sleepStack = SLPCTRL.CTRLA; // save old sleep State - SLPCTRL.CTRLA = sleepStack & 0x01; // only leave the SEN bit, if it was set - #endif -} - -void popSleep() { - #if defined(TWI_USING_WIRE1) - uint8_t sleepStackLoc = sleepStack; - if (sleepStackLoc > 0) { // only do something if sleep was enabled - if (sleepStackLoc > 0x10) { // only decrement if pushed once before - sleepStackLoc = (sleepStackLoc - 0x10); // upper nibble - } else { // at 0 we are about to put sleep back - SLPCTRL.CTRLA = sleepStackLoc; // restore sleep - sleepStackLoc = 0; // reset everything - } - sleepStack = sleepStackLoc; - } - #else - SLPCTRL.CTRLA = sleepStack; - #endif -} diff --git a/megaavr/libraries/Wire/src/twi.h b/megaavr/libraries/Wire/src/twi.h deleted file mode 100644 index 01266af8..00000000 --- a/megaavr/libraries/Wire/src/twi.h +++ /dev/null @@ -1,237 +0,0 @@ -/* - Copyright (c) 2022 MX682X - -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. -*/ - -// *INDENT-OFF* astyle wants this file to be completely unreadable with no indentation for the many preprocessor conditionals! - -#ifndef TWI_H_ -#define TWI_H_ - -#include "Arduino.h" - - - -/*! For adding R/_W bit to address */ -#ifndef ADD_READ_BIT - #define ADD_READ_BIT(address) (address | 0x01) -#endif -#ifndef ADD_WRITE_BIT - #define ADD_WRITE_BIT(address) (address & ~0x01) -#endif - -#ifndef DEFAULT_FREQUENCY - #define DEFAULT_FREQUENCY 100000 -#endif - -/* These options are or will be controlled by boards.txt menu options -#define TWI_USING_WIRE1 // On devices with two TWIs, this identifies if the user wants to use Wire1 -#define TWI_MANDS // This enables the simultaneous use of the Master and Slave functionality - where supported -#define TWI_MERGE_BUFFERS // Merges the master tx and rx buffers - this option will break the TWI when any rx occurs between beginTransmission and endTransmission! - // It is not advised to use this define. Only use this when you need the RAM **really** badly -*/ - -#if (!defined(TWI1) && defined(TWI_USING_WIRE1)) - // If pins for Wire1 are not defined, but TWI_USING_WIRE1 was defined in the boards.txt menu, throw an error. Used for 28-pin DA/DB parts - #error "This part only provides a single Wire interface." -#endif - -#if ((defined(TWI0_DUALCTRL) && !defined(TWI_USING_WIRE1)) || (defined(TWI1_DUALCTRL) && defined(TWI_USING_WIRE1))) - /* Instead of requiring changes to the library to switch between DxCore and megaTinyCore, we can check - * if the part supports dual mode. Goal is that the identical library can be used on both, so updates - * in one can be propagated to the other by just copying files. */ - #define TWI_DUALCTRL // This identifies if the device supports dual mode, where slave pins are different from the master pins -#endif - - -#if defined(__AVR_ATtiny202__) || defined(__AVR_ATtiny202__) - #if defined(TWI_MANDS) // 202 and 402 do not support independent master and slave. - // #undef TWI_MANDS - #error "Master + Slave mode is not supported on the 202 or 402." - // If a user enables master + slave mode on a part where we know it won't we should error - // so that they know what's wrong instead of silently disobeying - #endif -#endif -/* Reasoning behind buffer lengths of 16 bytes for stuff with hardly - * any RAM, 130 for stuff with tons and 32 for everything else. - * - * Parts with 128b of RAM wince at pair of 16b buffers (25%) - * - * In the past we configured this with more granularity but it - * was pointed out that because of the official cores all using - * 32 byte buffers, many libraries implicitly depend >= 32 byte - * buffers and will misbehave if the buffer used is smaller. - * So even where it's painfully large portion of ram, we use - * that. Similarly, most libraries will not make use of a larger one - * Thus, we don't enlarge the buffer until we finally get to - * the modern AVRs with >= 4k of RAM, where it doesn't really - * matter anymore. At that point, enlarging the buffer to 130b - * allows for a 128b page write to the popular 24-series EEPROMs - * Since the modern AVRs are lighter on EEPROM than classic ones - * it is more likely that external storage would be needed. - * SD cards are a poor choice for byte oriented data storage, in - * addition to being astonishingly unreliable, and byte oriented - * data storage is more useful for typical applications of AVRs - * than file-oriented storage. - * If the standard buffer size is not enough, the supported maximum - * is 32.767 (due to size_t), without further modifications - * to the library. Buffer lengths below 255 are highly recommended - * though. - */ - -#ifndef BUFFER_LENGTH - #if (RAMSIZE < 256) - #define BUFFER_LENGTH 16 - #elif (RAMSIZE < 4096) - #define BUFFER_LENGTH 32 - #else - #define BUFFER_LENGTH 130 - #endif -#endif - - -#if (BUFFER_LENGTH > 255) - typedef uint16_t twi_buffer_index_t; -#else - typedef uint8_t twi_buffer_index_t; -#endif - - -#define TWI_TIMEOUT_ENABLE // Enabled by default, might be disabled for debugging or other reasons -#define TWI_ERROR_ENABLED // Enabled by default, TWI Master Write error functionality -//#define TWI_READ_ERROR_ENABLED // Enabled on Master Read too -//#define DISABLE_NEW_ERRORS // Disables the new error codes and returns TWI_ERR_UNDEFINED instead. - -// Errors from Arduino documentation: -#define TWI_ERR_SUCCESS 0x00 // Default -#define TWI_ERR_DATA_TOO_LONG 0x01 // Not used here; data too long to fit in TX buffer -#define TWI_ERR_ACK_ADR 0x02 // Address was NACKed on Master write -#define TWI_ERR_ACK_DAT 0x03 // Data was NACKed on Master write -#define TWI_ERR_UNDEFINED 0x04 // Software can't tell error source -#define TWI_ERR_TIMEOUT 0x05 // TWI Timed out on data rx/tx - -// Errors that are made to help finding errors on TWI lines. Only here to give a suggestion of where to look - these may not always be reported accurately. -#if !defined(DISABLE_NEW_ERRORS) - #define TWI_ERR_UNINIT 0x10 // TWI was in bad state when function was called. - #define TWI_ERR_PULLUP 0x11 // Likely problem with pull-ups - #define TWI_ERR_BUS_ARB 0x12 // Bus error and/or Arbitration lost - #define TWI_ERR_BUF_OVERFLOW 0x13 // Buffer overflow on master read - #define TWI_ERR_CLKHLD 0x14 // Something's holding the clock -#else - // DISABLE_NEW_ERRORS can be used to more completely emulate the old error reporting behavior; this should rarely be needed. - #define TWI_ERR_UNINIT TWI_ERR_UNDEFINED // TWI was in bad state when method was called. - #define TWI_ERR_PULLUP TWI_ERR_UNDEFINED // Likely problem with pull-ups - #define TWI_ERR_BUS_ARB TWI_ERR_UNDEFINED // Bus error and/or Arbitration lost - #define TWI_ERR_BUF_OVERFLOW TWI_ERR_UNDEFINED // Buffer overflow on master read - #define TWI_ERR_CLKHLD TWI_ERR_UNDEFINED // Something's holding the clock -#endif - -#if defined(TWI_ERROR_ENABLED) - #define TWI_ERROR_VAR twi_error - #define TWI_INIT_ERROR uint8_t TWI_ERROR_VAR = TWI_ERR_SUCCESS - #define TWI_GET_ERROR TWI_ERROR_VAR - #define TWI_CHK_ERROR(x) TWI_ERROR_VAR == x - #define TWI_SET_ERROR(x) TWI_ERROR_VAR = x -#else - #define TWI_ERROR_VAR {} - #define TWI_INIT_ERROR {} - #define TWI_GET_ERROR {0} - #define TWI_CHK_ERROR(x) (true) - #define TWI_SET_ERROR(x) {} -#endif - -#if defined(TWI_READ_ERROR_ENABLED) && defined(TWI_ERROR_ENABLED) - #define TWIR_ERROR_VAR twiR_error - #define TWIR_INIT_ERROR uint8_t TWIR_ERROR_VAR = TWIR_ERR_SUCCESS - #define TWIR_GET_ERROR TWIR_ERROR_VAR - #define TWIR_CHK_ERROR(x) TWIR_ERROR_VAR == x - #define TWIR_SET_ERROR(x) TWIR_ERROR_VAR = x - - // #define TWI_SET_EXT_ERROR(x) TWI_ERROR_VAR = x -#else - #define TWIR_ERROR_VAR {} - #define TWIR_INIT_ERROR {} - #define TWIR_GET_ERROR {0} - #define TWIR_CHK_ERROR(x) (true) - #define TWIR_SET_ERROR(x) {} - - // #define TWI_SET_EXT_ERROR(x) {} -#endif - - -struct twiDataBools { // using a struct so the compiler can use skip if bit is set/cleared - bool _toggleStreamFn: 1; // used to toggle between Slave and Master elements when TWI_MANDS defined - bool _hostEnabled: 1; - bool _clientEnabled: 1; - bool _ackMatters: 1; - uint8_t _reserved: 4; -}; - - -struct twiData { - TWI_t *_module; - struct twiDataBools _bools; // the structure to hold the bools for the class - #if defined(TWI_READ_ERROR_ENABLED) - uint8_t _errors; - #endif - twi_buffer_index_t _bytesTransmittedS; - uint8_t _clientAddress; - #if defined(TWI_MERGE_BUFFERS) - twi_buffer_index_t _bytesToReadWrite; - twi_buffer_index_t _bytesReadWritten; - #else - twi_buffer_index_t _bytesToWrite; - twi_buffer_index_t _bytesToRead; - twi_buffer_index_t _bytesRead; // Used in slave mode exclusively - twi_buffer_index_t _bytesWritten; // Used in slave mode exclusively - #endif - #if defined(TWI_MANDS) - uint8_t _incomingAddress; - twi_buffer_index_t _bytesToReadWriteS; - twi_buffer_index_t _bytesReadWrittenS; - #endif - void (*user_onRequest)(void); - void (*user_onReceive)(int); - #if defined(TWI_MERGE_BUFFERS) - uint8_t _trBuffer[BUFFER_LENGTH]; - #else - uint8_t _txBuffer[BUFFER_LENGTH]; - uint8_t _rxBuffer[BUFFER_LENGTH]; - #endif - #if defined(TWI_MANDS) - uint8_t _trBufferS[BUFFER_LENGTH]; - #endif -}; - - -void TWI_MasterInit(struct twiData *_data); -void TWI_Flush(struct twiData *_data); -void TWI_Disable(struct twiData *_data); -void TWI_DisableMaster(struct twiData *_data); -void TWI_DisableSlave(struct twiData *_data); -void TWI_HandleSlaveIRQ(struct twiData *_data); -uint8_t TWI_MasterWrite(struct twiData *_data, bool send_stop); -uint8_t TWI_MasterSetBaud(struct twiData *_data, uint32_t frequency); -void TWI_SlaveInit(struct twiData *_data, uint8_t address, uint8_t receive_broadcast, uint8_t second_address); -uint8_t TWI_MasterCalcBaud(uint32_t frequency); - -twi_buffer_index_t TWI_MasterRead(struct twiData *_data, twi_buffer_index_t bytesToRead, bool send_stop); - -#endif From b5c8a6af1fa177d075437ccc8d7897ac4b7b517d Mon Sep 17 00:00:00 2001 From: MX682X Date: Tue, 16 Apr 2024 20:11:26 +0200 Subject: [PATCH 2/6] fix for PulseIn --- megaavr/cores/megatinycore/Arduino.h | 16 ++++++++-------- megaavr/cores/megatinycore/wiring.c | 7 ++++--- megaavr/cores/megatinycore/wiring_pulse.c | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/megaavr/cores/megatinycore/Arduino.h b/megaavr/cores/megatinycore/Arduino.h index 3fa1a724..5c3cd733 100644 --- a/megaavr/cores/megatinycore/Arduino.h +++ b/megaavr/cores/megatinycore/Arduino.h @@ -444,9 +444,9 @@ uint8_t PWMoutputTopin(uint8_t timer, uint8_t channel); * 3/19/23: These are supposed to be macros, not inline functions * Users have reported problems resulting from their being functions, even inline ones */ -#define clockCyclesPerMicrosecond() ((F_CPU / 1000000UL)) -#define clockCyclesToMicroseconds(__a__) (__a__ / clockCyclesPerMicrosecond()) -#define microsecondsToClockCycles(__a__) (__a__ * clockCyclesPerMicrosecond()) +#define clockCyclesPerMicrosecond() ((uint16_t)(F_CPU / 1000000UL)) +#define clockCyclesToMicroseconds(__a__) ((uint32_t)((__a__) / clockCyclesPerMicrosecond())) +#define microsecondsToClockCycles(__a__) ((uint32_t)((__a__) * clockCyclesPerMicrosecond())) // Currently DxCore has no cases where the millis timer isn't derived from system clock, but that will change /* This becomes important when we support other timers for timekeeping. The Type D timer can be faster, requiring: @@ -474,15 +474,15 @@ uint32_t microsecondsToMillisClockCycles(uint32_t microseconds); */ #ifdef MILLIS_USE_TIMERD0 #if (F_CPU == 20000000UL || F_CPU == 10000000UL ||F_CPU == 5000000UL) - #define millisClockCyclesPerMicrosecond() (20) // this always runs off the 20MHz oscillator + #define millisClockCyclesPerMicrosecond() ((uint16_t)(20)) // this always runs off the 20MHz oscillator #else - #define millisClockCyclesPerMicrosecond() (16) + #define millisClockCyclesPerMicrosecond() ((uint16_t)(16)) #endif #else - #define millisClockCyclesPerMicrosecond() (F_CPU / 1000000UL) + #define millisClockCyclesPerMicrosecond() ((uint16_t)((F_CPU / 1000000UL))) #endif -#define millisClockCyclesToMicroseconds(__a__) (__a__ / millisClockCyclesPerMicrosecond()) -#define microsecondsToMillisClockCycles(__a__) (__a__ * millisClockCyclesPerMicrosecond()) +#define millisClockCyclesToMicroseconds(__a__) ((uint32_t)(__a__ / millisClockCyclesPerMicrosecond())) +#define microsecondsToMillisClockCycles(__a__) ((uint32_t)(__a__ * millisClockCyclesPerMicrosecond())) /* Timers and Timer-like-things * These are used for two things: Identifying the timer on a pin in diff --git a/megaavr/cores/megatinycore/wiring.c b/megaavr/cores/megatinycore/wiring.c index 4fa43ae4..a17b2f0c 100644 --- a/megaavr/cores/megatinycore/wiring.c +++ b/megaavr/cores/megatinycore/wiring.c @@ -166,9 +166,10 @@ uint8_t __PeripheralControl = 0xFF; #endif /* defined(MILLIS_USE_TIMER__) */ #endif /* defined(MILLIS_USE_TIMERRTC) */ + #define ClockCyclesToMicroseconds(__a__) ((__a__) / (F_CPU / 1000000L)) #define FRACT_MAX (1000) - #define FRACT_INC (millisClockCyclesToMicroseconds(TIME_TRACKING_CYCLES_PER_OVF)%1000) - #define MILLIS_INC (millisClockCyclesToMicroseconds(TIME_TRACKING_CYCLES_PER_OVF)/1000) + #define FRACT_INC (ClockCyclesToMicroseconds(TIME_TRACKING_CYCLES_PER_OVF)%1000) + #define MILLIS_INC (ClockCyclesToMicroseconds(TIME_TRACKING_CYCLES_PER_OVF)/1000) struct sTimeMillis { #if (defined(MILLIS_USE_TIMERB0) || defined(MILLIS_USE_TIMERB1) || defined(MILLIS_USE_TIMERB2) || defined(MILLIS_USE_TIMERB3) || defined(MILLIS_USE_TIMERB4)) // Now TCB as millis source does not need fraction @@ -334,7 +335,7 @@ uint8_t __PeripheralControl = 0xFF; "subi r24,%[LFRMAX]" "\n\t" // subtract FRACT_MAX and see if it is lower "sbci r25,%[HFRMAX]" "\n\t" // - #if MILLIS_INC != 0 // (6 words / 4 - 5 clocks, branches were optimize to create minimal diversion) + #if MILLIS_INC > 0 // (6 words / 4 - 5 clocks, branches were optimize to create minimal diversion) "brlo higher" "\n\t" // if FRAC_MAX was not reached, "ldi r24, %[MIINC]" "\n\t" // load "normal" MILLIS_INC (0x00-MILLIS_INC) "rjmp sub4" "\n\t" // avoid overwriting r24 diff --git a/megaavr/cores/megatinycore/wiring_pulse.c b/megaavr/cores/megatinycore/wiring_pulse.c index 86832053..9a4b94c7 100644 --- a/megaavr/cores/megatinycore/wiring_pulse.c +++ b/megaavr/cores/megatinycore/wiring_pulse.c @@ -41,7 +41,7 @@ unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout) // convert the timeout from microseconds to a number of times through // the initial loop; it takes approximately 16 clock cycles per iteration - unsigned long maxloops = microsecondsToClockCycles(timeout) / 16; + unsigned long maxloops = (uint32_t)microsecondsToClockCycles(timeout) / 16; unsigned long width = countPulseASM(portInputRegister(port), bit, stateMask, maxloops); From 876f12ba285c57f7c09e3b5d55ad7b9b2e488103 Mon Sep 17 00:00:00 2001 From: MX682X Date: Tue, 16 Apr 2024 20:31:41 +0200 Subject: [PATCH 3/6] Lingering Wire synchronisation with DxCore --- megaavr/libraries/Wire/README.md | 16 ++- .../master_and_slave/master_and_slave.ino | 84 ------------- .../Wire/examples/master_read/master_read.ino | 55 +++++++++ .../examples/master_write/master_write.ino | 64 ++++++++++ .../master_write_read/master_write_read.ino | 113 ------------------ .../Wire/examples/slave_read/slave_read.ino | 40 +++++++ .../Wire/examples/slave_write/slave_write.ino | 37 ++++++ .../slave_write_read/slave_write_read.ino | 67 ----------- megaavr/libraries/Wire/keywords.txt | 6 + megaavr/libraries/Wire/library.properties | 2 +- megaavr/libraries/Wire/src/Wire.cpp | 93 +++++++------- megaavr/libraries/Wire/src/Wire.h | 45 ++++--- megaavr/libraries/Wire/src/twi_pins.c | 25 ++-- megaavr/libraries/Wire/src/twi_pins.h | 4 +- 14 files changed, 296 insertions(+), 355 deletions(-) delete mode 100644 megaavr/libraries/Wire/examples/master_and_slave/master_and_slave.ino create mode 100644 megaavr/libraries/Wire/examples/master_read/master_read.ino create mode 100644 megaavr/libraries/Wire/examples/master_write/master_write.ino delete mode 100644 megaavr/libraries/Wire/examples/master_write_read/master_write_read.ino create mode 100644 megaavr/libraries/Wire/examples/slave_read/slave_read.ino create mode 100644 megaavr/libraries/Wire/examples/slave_write/slave_write.ino delete mode 100644 megaavr/libraries/Wire/examples/slave_write_read/slave_write_read.ino diff --git a/megaavr/libraries/Wire/README.md b/megaavr/libraries/Wire/README.md index e1acf4f2..32e3856f 100644 --- a/megaavr/libraries/Wire/README.md +++ b/megaavr/libraries/Wire/README.md @@ -208,6 +208,18 @@ uint8_t checkPinLevels(); ``` This function returns the level of the master TWI pins, depending on the used TWI module and port multiplexer settings. Bit 0 represents SDA line and bit 1 represents SCL line. This is useful on initialisation, where you want to make sure that all devices have their pins ready in open-drain mode. A value of 0x03 indicates that both lines have a HIGH level and the bus is ready. +#### `uint8_t masterTransmit()` +```c++ +uint8_t masterTransmit(auto *length, uint8_t *buffer, uint8_t addr, uint8_t sendStop); +``` +This functions allows to transmit a buffer of data without having to copy the data to the internal buffer of the library. This allows transmission lengths as long as the RAM size without having to define the TWI_BUFFER_SIZE in the platform.txt. The return value is the same as endTransmission(). length will be overwritten with the actual amount of written bytes. + +#### `uint8_t masterReceive()` +```c++ +uint8_t masterReceive(auto *length, uint8_t *buffer, uint8_t addr, uint8_t sendStop); +``` +This functions allows to receive a buffer of data without having to copy the data from the internal buffer of the library. This allows transmission lengths as long as the RAM size without having to define the TWI_BUFFER_SIZE in the platform.txt. The return value is the same as endTransmission(). length will be overwritten with the actual amount of received bytes. + ### Additional New Methods not available on all parts These new methods are available exclusively for parts with certain specialized hardware; Most full-size parts support enableDualMode (but tinyAVR does not), while only the DA and DB-series parts have the second TWI interface that swapModule requires. #### `void swapModule()` @@ -323,6 +335,8 @@ When the second or third argument was used, `Wire.getIncomingAddress()` should b If (and only if) the Master and Slave option is selected in the Tools -> Wire mode, the Wire interface can be enabled for both master and slave. Even when Dual Mode is used, the correct option must still be selected to enable acting as both master and slave. +The Arduino library defines the array lengths as `BUFFER_LENGTH`, however, this define name might create conflicts, as the name is quite arbitrary. The newest library version uses the name `TWI_BUFFER_LENGTH` instead. However, it is still backwards compatible, as the `BUFFER_LENGTH` is still applied, but a compiler warning is generated to notify about the change. + #### `void setClock()` ```c++ void setClock(uint32_t); @@ -419,7 +433,7 @@ The implementation isn't identical, but the behaviour is unchanged, or is differ #### Write Sequence 1. Master generates start condition. -2. Master clocks out the slave address with out read-bit set.. +2. Master clocks out the slave address without read-bit set.. 3. Slave detects and ACKs. 4. Master clocks out 1 or more data bytes as slave ACKs them. 5. Master generates a Stop Condition. diff --git a/megaavr/libraries/Wire/examples/master_and_slave/master_and_slave.ino b/megaavr/libraries/Wire/examples/master_and_slave/master_and_slave.ino deleted file mode 100644 index 72946fad..00000000 --- a/megaavr/libraries/Wire/examples/master_and_slave/master_and_slave.ino +++ /dev/null @@ -1,84 +0,0 @@ -/* Wire Master and Slave example - * by MX682X - * - * Demonstrates use of the New Wire library - * This example requires the setting Master And Slave - * If anything is sent through the Serial Monitor and terminated with NL and/or CR - * The current millis value is sent to the slave. If enableDualMode is NOT called, - * the slave pins are the same as the master pins. This creates a loopback mode, - * in which the sent millis value is received in the same device and is printed back to - * the Serial Monitor. - * - */ - -#include - -int8_t rxLen = 0; -int8_t len = 0; - -#define MySerial Serial // The serial port connected to the to the computer. - - -void setup() { - Wire.onReceive(receiveDataWire); // Set the slave receive ISR - //Wire.enableDualMode(false) // used to use separate pins for master and slave. If enabled, - // it would disable the loopback feature this example relies upon - //Wire.swap() // used to select the pins of the master and the slave - - Wire.begin(); // Enables the master functionality - Wire.begin(0x54); // Enables the slave functionality - - MySerial.begin(115200); // Use 115200 baud - this is the 2020's, and these are modern AVRs. -} - -void loop() { - if (MySerial.available() > 0) { // as soon as the first byte is received on Serial - readFromSerial(); // read the data from the Serial interface - if (len > 0) { // after the while-loop, if there was useful data, - sendDataWire(); // send the data over I2C - } - len = 0; // since the data was sent, the position is 0 again - } -} - -void readFromSerial() { - while (true) { // in an endless while-loop - while (MySerial.available() == 0);// means we've taken all the bytes in, and are still waiting for a cr/lf. - char c = MySerial.read(); // read the next char, now that there's one available. - if (c == '\n' || c == '\r') { // until a new line or carriage return is found - break; // if so, break the endless while-loop - } // otherwise - len++; // increment the position. data is not saved as it is not needed in this case - } -} -void sendDataWire() { - uint32_t ms = 0x22334455; - #ifndef MILLIS_USE_TIMERNONE - ms = millis(); // overwrite the default value - #endif - - Wire.beginTransmission(0x54); // prepare transmission to slave with address 0x54 - Wire.write((uint8_t) ms); - Wire.write((uint8_t)(ms >> 8)); - Wire.write((uint8_t)(ms >> 16)); - Wire.write((uint8_t)(ms >> 24)); - - Wire.endTransmission(); // finish transmission -} - - -void receiveDataWire(int16_t numBytes) { - // Warning: This is terrible practice! Never print from within an ISR! - // It only works in very simple cases. - if (numBytes != 4) { - MySerial.print("Slave received only "); - MySerial.println(numBytes); - } - MySerial.print("Slave Received: "); - uint32_t temp = 0; - temp |= (uint32_t)Wire.read(); - temp |= (uint32_t)Wire.read() << 8; - temp |= (uint32_t)Wire.read() << 16; - temp |= (uint32_t)Wire.read() << 24; - MySerial.println(temp); -} diff --git a/megaavr/libraries/Wire/examples/master_read/master_read.ino b/megaavr/libraries/Wire/examples/master_read/master_read.ino new file mode 100644 index 00000000..2e9a58a5 --- /dev/null +++ b/megaavr/libraries/Wire/examples/master_read/master_read.ino @@ -0,0 +1,55 @@ +/* Wire Master Read + * by MX682X + * + * Demonstrates use of the New Wire library + * Reads data from an I2C/TWI slave device + * Refer to the "Wire Slave Write" example for use with this + * + * This example takes the input from Serial. If the serial input is 'm' or 'M', + * this code requests 4 bytes from the slave with the address 0x54. + * When using together with the complementary example, the slave sends it's millis() value. + * This value is then sent to the serial monitor + * + * To use this, you need to connect the SCL and SDA pins of this device to the + * SCL and SDA pins of a second device running the Wire Slave Write example. + * + * Pullup resistors must be connected between both data lines and Vcc. + * See the Wire library README.md for more information. + */ + +#define MySerial Serial + +#include + +int8_t rxLen = 0; +int8_t len = 0; + +void setup() { + Wire.begin(); // initialize master + MySerial.begin(115200); +} + +void loop() { + if (MySerial.available() > 0) { // as soon as the first byte is received on Serial + char c = MySerial.read(); // read the data from serial. + if (c == 'm' || c == 'M') { + sendDataWire(); // send the data over I2C + } + len = 0; // since the data was sent, the position is 0 again + } +} + +void sendDataWire() { + uint32_t ms; + if (4 == Wire.requestFrom(0x54, 4, 0x01)) { // request from slave + while (Wire.available()) { + ms = (uint32_t)Wire.read(); // read out 32-bit wide data + ms |= (uint32_t)Wire.read() << 8; + ms |= (uint32_t)Wire.read() << 16; + ms |= (uint32_t)Wire.read() << 24; + MySerial.println(ms); // print the milliseconds from Slave + } + } else { + MySerial.println("Wire.requestFrom() timed out!"); + } +} diff --git a/megaavr/libraries/Wire/examples/master_write/master_write.ino b/megaavr/libraries/Wire/examples/master_write/master_write.ino new file mode 100644 index 00000000..cfaac9fe --- /dev/null +++ b/megaavr/libraries/Wire/examples/master_write/master_write.ino @@ -0,0 +1,64 @@ +/* Wire Master Write + * by MX682X + * + * Demonstrates use of the New Wire library + * Writes data to an I2C/TWI slave device + * Refer to the "Wire Slave Read" example for use with this + * + * Enter any data using serial monitor or other console followed by either or + * both of the line ending characters, and it will be sent to the slave, which + * should print it out on it's serial port. + * + * To use this, you need to connect the SCL and SDA pins of this device to the + * SCL and SDA pins of a second device running the Wire Slave Read example. + * + * Pullup resistors must be connected between both data lines and Vcc. + * See the Wire library README.md for more information. + */ + +#include + +char input[32]; +int8_t len = 0; + +#define MySerial Serial // The serial port connected to the to the computer. + +void setup() { + Wire.begin(); // initialize master + // MySerial.swap(1); // Remember to swap serial pins if you need to do that with your connections. + MySerial.begin(115200); // Use 115200 baud - this is the 2020's, and these are modern AVRs. +} + +void loop() { + if (MySerial.available() > 0) { // as soon as the first byte is received on Serial + readFromSerial(); // read the data from the Serial interface + if (len > 0) { // after the while-loop, if there was useful data, + sendDataWire(); // send the data over I2C + } + len = 0; // since the data was sent, the position is 0 again + } +} + +void readFromSerial() { + while (true) { // in an endless while-loop + while (MySerial.available() == 0);// means we've taken all the bytes in, and are still waiting for a cr/lf. + char c = MySerial.read(); // read the next char, now that there's one available. + if (c == '\n' || c == '\r') { // until a new line or carriage return is found + break; // if so, break the endless while-loop + } // otherwise + input[len] = c; // save the char + len++; // increment the position + if (len > 30) { // if there was too much data + break; // break the while-loop to avoid buffer overflow + } + } +} + +void sendDataWire() { + Wire.beginTransmission(0x54); // prepare transmission to slave with address 0x54 + for (uint8_t i = 0; i < len; i++) { + Wire.write(input[i]); // Write the received data to the bus buffer + } + Wire.write("\r\n"); // add new line and carriage return for the Serial monitor + Wire.endTransmission(); // finish transmission +} diff --git a/megaavr/libraries/Wire/examples/master_write_read/master_write_read.ino b/megaavr/libraries/Wire/examples/master_write_read/master_write_read.ino deleted file mode 100644 index f650c6de..00000000 --- a/megaavr/libraries/Wire/examples/master_write_read/master_write_read.ino +++ /dev/null @@ -1,113 +0,0 @@ -/* Wire Master Write/Read - * by MX682X - * - * Demonstrates use of the New Wire library - * Writes or reads data to/from an I2C/TWI slave device - * Refer to the "Wire slave_write_read" example for use with this - * - * Enter any data using serial monitor or other console followed by either or - * both of the line ending characters. Depending on the first char, one of the - * following happens: - * - if it's a 'm' or 'M', a master write is performed (if the #define is set) - * - otherwise a master read of 4 bytes is performed (if the #define is set) - * - * To use this, you need to connect the SCL and SDA pins of this device to the - * SCL and SDA pins of a second device running the Wire Slave Read example. - * - * Pullup resistors must be connected between both data lines and Vcc. - * See the Wire library README.md for more information. - */ - -#include - -#define MySerial Serial // The serial port connected to the to the computer. - -// The complete example might not fit on some parts. In that case, you can easily -// comment out some functionality. DECODE_ERROR bloats the code the most. -#define ENABLE_WRITE_TO // Enables the master write functionality -#define ENABLE_READ_FROM // Enables the master read functionality -#define DECODE_ERROR // Prints status messages. - -char input[32]; -int8_t len = 0; - - -void setup() { - Wire.begin(); // initialize master - MySerial.begin(115200); // Use 115200 baud - this is the 2020's, and these are modern AVRs. -} - -void loop() { - if (MySerial.available() > 0) { // as soon as the first byte is received on Serial - readFromSerial(); // read the data from the Serial interface - if (len > 0) { // after the while-loop, if there was useful data, - char c = input[0]; - if (c == 'm' || c == 'M') { // If the first char is that - sendDataWire(); // send the data over I2C to the slave - } else { // otherwise - requestDataWire(); // request data from I2C slave - } - } - len = 0; // since the data was sent, the position is 0 again - } -} - -void readFromSerial() { - while (true) { // in an endless while-loop - while (MySerial.available() == 0);// means we've taken all the bytes in, and are still waiting for a cr/lf. - char c = MySerial.read(); // read the next char, now that there's one available. - if (c == '\n' || c == '\r') { // until a new line or carriage return is found - break; // if so, break the endless while-loop - } // otherwise - input[len] = c; // save the char - len++; // increment the position - if (len > 30) { // if there was too much data - break; // break the while-loop to avoid buffer overflow - } - } -} - -void sendDataWire() { - #ifdef ENABLE_WRITE_TO - - uint8_t err = 0; - Wire.beginTransmission(0x54); // prepare transmission to slave with address 0x54 - Wire.write(input, len); - Wire.write("\r\n"); // add new line and carriage return for the Serial monitor - err = Wire.endTransmission(); // finish transmission - - #ifdef DECODE_ERROR - - switch (err) { - case 0x00: MySerial.println("Wire transmit was successful"); break; - case 0x02: MySerial.println("Address was NACK'd"); break; - case 0x03: MySerial.println("Data was NACK'd"); break; - case 0x04: MySerial.println("Unknown error occurred"); break; - case 0x05: MySerial.println("Transmission time-outed"); break; - // The library also supports some extended errors that might give a hint on what is failing. - case 0x10: MySerial.println("Wire is uninitialized"); break; - case 0x11: MySerial.println("Pullups are missing"); break; - case 0x12: MySerial.println("Arbitration lost"); break; - } - - #endif /* DECODE_ERROR */ - #endif /* ENABLE_WRITE_TO */ -} - -void requestDataWire() { - #ifdef ENABLE_READ_FROM - uint8_t len = 4; - uint32_t ms = 0; - if (len == Wire.requestFrom(0x54, len, 0x01)) { // request from slave - ms = (uint32_t)Wire.read(); // read out 32-bit wide data - ms |= (uint32_t)Wire.read() << 8; - ms |= (uint32_t)Wire.read() << 16; - ms |= (uint32_t)Wire.read() << 24; - MySerial.println(ms); // print the milliseconds from Slave - } else { - #ifdef DECODE_ERROR - MySerial.println("Wire.requestFrom() failed"); - #endif - } - #endif -} diff --git a/megaavr/libraries/Wire/examples/slave_read/slave_read.ino b/megaavr/libraries/Wire/examples/slave_read/slave_read.ino new file mode 100644 index 00000000..2e0a8d74 --- /dev/null +++ b/megaavr/libraries/Wire/examples/slave_read/slave_read.ino @@ -0,0 +1,40 @@ +/* Wire Slave Read + * by MX682X + * + * Demonstrates use of the Wire library + * Receives data as an I2C/TWI slave device + * Refer to the "Wire Master Write" example for use with this + * + * This example prints everything that is received on the I2C bus to the + * serial monitor. + * + * To use this, you need to connect the SCL and SDA pins of this device to the + * SCL and SDA pins of a second device running the Master Write example. + * + * Pullup resistors must be connected between both data lines and Vcc. + * See the Wire library README.md for more information. + */ + +#include + +#define MySerial Serial + +void setup() { + Wire.begin(0x54); // join i2c bus with address 0x54 + Wire.onReceive(receiveDataWire); // give the Wire library the name of the function + // that will be called on a master write event + MySerial.begin(115200); +} + +void loop() { + delay(100); +} + +// function that executes whenever data is received from master +// this function is registered as an event, see setup() +void receiveDataWire(int16_t numBytes) { // the Wire library tells us how many bytes + for (uint8_t i = 0; i < numBytes; i++) { // were received so we can for loop for that + char c = Wire.read(); // amount and read the received data + MySerial.write(c); // to print it to the Serial Monitor + } +} diff --git a/megaavr/libraries/Wire/examples/slave_write/slave_write.ino b/megaavr/libraries/Wire/examples/slave_write/slave_write.ino new file mode 100644 index 00000000..e8085e18 --- /dev/null +++ b/megaavr/libraries/Wire/examples/slave_write/slave_write.ino @@ -0,0 +1,37 @@ +/* Wire Slave Write + * + * This sketch responds to a request for data directed at address 0x54 + * with up to 4 bytes of data - namely, the current value of millis. + * + * If millis is disabled, it will instead always return the sequence + * 1234567890 (1,234,567,890, or 0x499602D2 represented in hexadecimal) + * + * To use this, you need to connect the SCL and SDA pins of this device to the + * SCL and SDA pins of a second device running the Master Read example. + * + * Pullup resistors must be connected between both data lines and Vcc. + * See the Wire library README.md for more information. + */ + +#include + +void setup() { + Wire.begin(0x54); // initialize slave + Wire.onRequest(transmitDataWire); // register transmitDataWire as the handler +} + +void loop() { + +} + +void transmitDataWire() { + #if !defined(MILLIS_USE_TIMERNONE) + uint32_t ms = millis(); + #else + uint32_t ms = 123456789UL; // placeholder - there's no millis to send because it's disabled. + #endif + Wire.write((uint8_t) ms); + Wire.write((uint8_t)(ms >> 8)); + Wire.write((uint8_t)(ms >> 16)); + Wire.write((uint8_t)(ms >> 24)); +} diff --git a/megaavr/libraries/Wire/examples/slave_write_read/slave_write_read.ino b/megaavr/libraries/Wire/examples/slave_write_read/slave_write_read.ino deleted file mode 100644 index 5b82e173..00000000 --- a/megaavr/libraries/Wire/examples/slave_write_read/slave_write_read.ino +++ /dev/null @@ -1,67 +0,0 @@ -/* Wire Slave Write/Read - * by MX682X - * - * Demonstrates use of the Wire library - * Writes/Reads data as an I2C/TWI slave device - * Refer to the "Wire master_write_read" example for use with this - * - * If the #define ENABLE_RECEIVE is set, every byte in a Master Write is - * printed to the Serial Monitor. - * - * If the #define ENABLE_REQUEST is set, the slave sends its millis() - * (or placeholder) value to the master. - * - * To use this, you need to connect the SCL and SDA pins of this device to the - * SCL and SDA pins of a second device running the Master Write example. - * - * Pullup resistors must be connected between both data lines and Vcc. - * See the Wire library README.md for more information. - */ - -#include - -#define MySerial Serial - -#define ENABLE_RECEIVE -#define ENABLE_REQUEST - - -void setup() { - #ifdef ENABLE_RECEIVE - Wire.onReceive(receiveDataWire); // give the Wire library the name of the function - // that will be called on a master write event - #endif - #ifdef ENABLE_REQUEST - Wire.onRequest(transmitDataWire); // same as above, but master read event - #endif - - Wire.begin(0x54); // join i2c bus with address 0x54 - MySerial.begin(115200); -} - -void loop() { - delay(100); -} - -#ifdef ENABLE_RECEIVE -void receiveDataWire(int16_t numBytes) { // the Wire API tells us how many bytes - for (uint8_t i = 0; i < numBytes; i++) { // were received so we can for loop for that - char c = Wire.read(); // amount and read the received data - MySerial.write(c); // to print it to the Serial Monitor - } -} -#endif - -#ifdef ENABLE_REQUEST -void transmitDataWire() { - #if !defined(MILLIS_USE_TIMERNONE) - uint32_t ms = millis(); - #else - uint32_t ms = 123456789UL; // placeholder - there's no millis to send because it's disabled. - #endif - Wire.write((uint8_t) ms); - Wire.write((uint8_t)(ms >> 8)); - Wire.write((uint8_t)(ms >> 16)); - Wire.write((uint8_t)(ms >> 24)); -} -#endif diff --git a/megaavr/libraries/Wire/keywords.txt b/megaavr/libraries/Wire/keywords.txt index ac633cbb..c233906d 100644 --- a/megaavr/libraries/Wire/keywords.txt +++ b/megaavr/libraries/Wire/keywords.txt @@ -5,6 +5,8 @@ ####################################### # Datatypes (KEYWORD1) ####################################### +twi_buf_index_t KEYWORD1 +TWI_t KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -18,6 +20,8 @@ setClock KEYWORD2 beginTransmission KEYWORD2 endTransmission KEYWORD2 requestFrom KEYWORD2 +masterTransmit KEYWORD2 +masterReceive KEYWORD2 onReceive KEYWORD2 onRequest KEYWORD2 getIncomingAddress KEYWORD2 @@ -25,6 +29,8 @@ getBytesRead KEYWORD2 slaveTransactionOpen KEYWORD2 checkPinLevels KEYWORD2 specialConfig KEYWORD2 +enableDualMode KEYWORD2 +returnError KEYWORD2 WIRE_SDA_HOLD_OFF KEYWORD2 WIRE_SDA_HOLD_50 KEYWORD2 WIRE_SDA_HOLD_300 KEYWORD2 diff --git a/megaavr/libraries/Wire/library.properties b/megaavr/libraries/Wire/library.properties index 0b78069e..f1150b79 100644 --- a/megaavr/libraries/Wire/library.properties +++ b/megaavr/libraries/Wire/library.properties @@ -1,5 +1,5 @@ name=Wire -version=2.0.10 +version=2.0.11 author=@MX682X, with contributions by @SpenceKonde. Original library, mostly reimplemented, was by Arduino. maintainer=Spence Konde sentence=This library allows you to communicate with I2C devices, acting as either a master, slave, or both master and slave. diff --git a/megaavr/libraries/Wire/src/Wire.cpp b/megaavr/libraries/Wire/src/Wire.cpp index 198418d1..295bced2 100644 --- a/megaavr/libraries/Wire/src/Wire.cpp +++ b/megaavr/libraries/Wire/src/Wire.cpp @@ -21,7 +21,7 @@ Modified 2019-2021 by Spence Konde for megaTinyCore and DxCore. This version is part of megaTinyCore and DxCore; it is not expected to work with other hardware or cores without modifications. - Modified extensively 2021-22 by MX682X for megaTinyCore and DxCore. + Modified extensively 2021-23 by MX682X for megaTinyCore and DxCore. Added Support for Simultaneous host/client, dual mode and Wire1. */ // *INDENT-OFF* astyle wants this file to be completely unreadable with no indentation for the many preprocessor conditionals! @@ -333,8 +333,8 @@ void TwoWire::begin(uint8_t address, bool receive_broadcast, uint8_t second_addr return; } } - - + + #if defined(TWI_MANDS) // Check if the user wants to use Master AND Slave if (_bools._clientEnabled == 1) { // Master is allowed to be enabled, don't re-enable the client though return; @@ -620,24 +620,25 @@ uint8_t TwoWire::specialConfig( __attribute__ ((unused)) bool smbuslvl, __attrib *@brief masterReceive sends a host READ with the specified client address * * This function will read an arbitrary number of bytes from the TWI module - * and store them into the specified buffer. This allows the user to use + * and store them into the specified buffer. This allows the user to use * custom sized buffers and avoids extra copy operations through read. * - *@param auto length - amount of bytes to be read (first arg so it is placed in r24:r25) - *@param uint8_t addr - the address of the client (7-bit) + *@param auto* length - pointer to a 8/16 bit variable containing the length. Will be overwritten with the amount of received bytes *@param uint8_t* buffer - pointer to the memory area to be written upon. + *@param uint8_t addr - the address of the client (7-bit) *@param uint8_t/bool sendStop - if the transaction should be terminated with a STOP condition * - *@return auto (uint8_t/uint16_t) - depends on the usage - *@retval amount of bytes that were actually read. If 0, no read took place due to a bus error. + *@return uint8_t + *@retval error code, see masterTrasnmit */ -auto TwoWire::masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop) { +uint8_t TwoWire::masterReceive(auto *length, uint8_t *buffer, uint8_t addr, uint8_t sendStop) { TWI_t *module = _module; + __asm__ __volatile__("\n\t" : "+z"(module)); TWIR_INIT_ERROR; // local variable for errors - auto dataRead = 0; + auto dataToRead = *length; uint8_t currentSM; uint8_t currentStatus; @@ -645,14 +646,14 @@ auto TwoWire::masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t #if defined (TWI_TIMEOUT_ENABLE) uint16_t timeout = (F_CPU/1000); #endif - + while (true) { currentStatus = module->MSTATUS; currentSM = currentStatus & TWI_BUSSTATE_gm; // get the current mode of the state machine - + if (currentSM == TWI_BUSSTATE_UNKNOWN_gc) { - TWIR_SET_ERROR(TWI_ERR_UNINITIALIZED); - return dataRead; + TWIR_SET_ERROR(TWI_ERR_UNINIT); + break; } #if defined(TWI_TIMEOUT_ENABLE) @@ -667,13 +668,12 @@ auto TwoWire::masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t break; } #endif - + if (currentStatus & TWI_ARBLOST_bm) { // Check for Bus error TWIR_SET_ERROR(TWI_ERR_BUS_ARB); // set error flag break; // leave RX loop } - if (currentSM != TWI_BUSSTATE_BUSY_gc) { if (state == 0x00) { module->MADDR = ADD_READ_BIT(addr); // Send Address with read bit @@ -683,17 +683,16 @@ auto TwoWire::masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t #endif } else { if (currentStatus & TWI_WIF_bm) { - TWIR_SET_ERROR(TWI_ERR_RXACK); // set error flag + TWIR_SET_ERROR(TWI_ERR_ACK_ADR); // set error flag module->MCTRLB = TWI_MCMD_STOP_gc; // free the bus break; } else if (currentStatus & TWI_RIF_bm) { *buffer = module->MDATA; buffer++; - dataRead++; #if defined (TWI_TIMEOUT_ENABLE) timeout = (F_CPU/1000); // reset timeout #endif - if (dataRead < length) { + if (--dataToRead != 0) { module->MCTRLB = TWI_MCMD_RECVTRANS_gc; // send an ACK so the Slave so it can send the next byte } else { if (sendStop != 0) { @@ -707,10 +706,8 @@ auto TwoWire::masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t } } } - #if defined(TWI_READ_ERROR_ENABLED) && defined(TWI_ERROR_ENABLED) - _errors = TWIR_GET_ERROR; // save error flags - #endif - return dataRead; + *length -= dataToRead; + return TWIR_GET_ERROR; } @@ -741,11 +738,12 @@ twi_buf_index_t TwoWire::requestFrom(uint8_t address, twi_buf_index_t quantity } _clientAddress = address << 1; - - twi_buf_index_t count = masterReceive(quantity, _clientAddress, _hostBuffer, sendStop); - _bytesToReadWrite = count; // for available/read/peek + _bytesToReadWrite = quantity; // for available/read/peek _bytesReadWritten = 0; - return count; + + masterReceive(&_bytesToReadWrite, _hostBuffer, _clientAddress, sendStop); // We ignore the error that gets returned + + return _bytesToReadWrite; } @@ -805,8 +803,7 @@ void TwoWire::beginTransmission(uint8_t address) { */ uint8_t TwoWire::endTransmission(bool sendStop) { // transmit (blocking) - - return masterTransmit(_bytesToReadWrite, _clientAddress, _hostBuffer, sendStop); + return masterTransmit(&_bytesToReadWrite, _hostBuffer, _clientAddress, sendStop); } @@ -815,29 +812,30 @@ uint8_t TwoWire::endTransmission(bool sendStop) { *@brief masterTransmit sends a host WRITE with the specified client address * * This function will write an arbitrary number of bytes to the TWI module - * and read the data from the specified buffer. This allows the user to use + * and read the data from the specified buffer. This allows the user to use * custom sized buffers and avoids extra copy operations through write. * - *@param auto length - amount of bytes to be read (first arg so it is placed in r24:r25) - *@param uint8_t addr - the address of the client (7-bit) + *@param auto* length - pointer to a 8/16 bit variable containing the length. Will be overwritten with the amount of written bytes *@param uint8_t* buffer - pointer to the memory area to be read from. + *@param uint8_t addr - the address of the client (7-bit) *@param uint8_t/bool sendStop - if the transaction should be terminated with a STOP condition * *@return uint8_t *@retval errors (see endTransmission) */ -uint8_t TwoWire::masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop) { +uint8_t TwoWire::masterTransmit(auto *length, uint8_t *buffer, uint8_t addr, uint8_t sendStop) { TWI_t* module = _module; __asm__ __volatile__("\n\t" : "+z"(module)); - + TWI_INIT_ERROR; uint8_t currentSM; uint8_t currentStatus; uint8_t stat = 0; + auto dataToWrite = *length; #if defined (TWI_TIMEOUT_ENABLE) uint16_t timeout = (F_CPU/1000); #endif - + if ((module->MCTRLA & TWI_ENABLE_bm) == 0x00) { // If the module is disabled, abort return TWI_ERR_UNINIT; } @@ -845,7 +843,7 @@ uint8_t TwoWire::masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint while (true) { currentStatus = module->MSTATUS; currentSM = currentStatus & TWI_BUSSTATE_gm; // get the current mode of the state machine - + if (currentSM == TWI_BUSSTATE_UNKNOWN_gc) { // If the bus was not initialized return TWI_ERR_UNINIT; // abort } @@ -867,7 +865,7 @@ uint8_t TwoWire::masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint TWI_SET_ERROR(TWI_ERR_BUS_ARB); // set error flag break; // leave TX loop } - + if (currentSM != TWI_BUSSTATE_BUSY_gc) { // Undefined was excluded, so make sure it's IDLE or OWNER if (stat == 0x00) { // At the start, we send the ADDR module->MADDR = ADD_WRITE_BIT(addr); // clear bit 0 @@ -879,17 +877,17 @@ uint8_t TwoWire::masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint if (currentStatus & TWI_WIF_bm) { // ADDR was sent, check for completed write if (currentStatus & TWI_RXACK_bm) { // got a NACK, see how much was written if (stat & 0x02) { // bit 1 set, data was already sent - if (length != 0) // the client may send an ACK at the end. If we + if (dataToWrite != 0) // the client may send an ACK at the end. If we TWI_SET_ERROR(TWI_ERR_ACK_DAT); // transferred everything, we can ignore the NACK } else { // otherwise, no data sent, ADDR NACK TWI_SET_ERROR(TWI_ERR_ACK_ADR); } break; } else { // No NACK on write - if (length != 0) { // check if there is data to be written + if (dataToWrite != 0) { // check if there is data to be written module->MDATA = *buffer; // Writing to the register to send data buffer++; - length--; + dataToWrite--; stat |= 0x02; // remember that we've sent data #if defined (TWI_TIMEOUT_ENABLE) timeout = (F_CPU/1000); // reset timeout @@ -903,7 +901,7 @@ uint8_t TwoWire::masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint } /* currentSM != TWI_BUSSTATE_BUSY_gc */ } /* while */ - + *length -= dataToWrite; if ((sendStop != 0) || (TWI_ERR_SUCCESS != TWI_GET_ERROR)) { module->MCTRLB = TWI_MCMD_STOP_gc; // Send STOP } @@ -1285,7 +1283,7 @@ void TwoWire::deselectSlaveBuffer(void) { -void TwoWire::HandleSlaveIRQ(TwoWire* wire_s) { +void TwoWire::HandleSlaveIRQ(TwoWire *wire_s) { if (wire_s == NULL) { return; } @@ -1314,7 +1312,7 @@ void TwoWire::HandleSlaveIRQ(TwoWire* wire_s) { if (clientStatus & TWI_APIF_bm) { // Address/Stop Bit set - + if ((*head) > 0) { // At this point, we have either a START, REPSTART or a STOP if (wire_s->user_onReceive != NULL) { // at START, head should be 0, as it was set so on the last STOP wire_s->user_onReceive((*head)); // otherwise, we notify the use sketch, as we have an REPSTART/STOP @@ -1345,7 +1343,7 @@ void TwoWire::HandleSlaveIRQ(TwoWire* wire_s) { } } else { // Stop bit set popSleep(); - + (*head) = 0; // clear whatever might be left due errors (*tail) = 0; action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" @@ -1416,13 +1414,6 @@ void TwoWire::onRequest(void (*function)(void)) { } -#if defined(TWI_READ_ERROR_ENABLED) && defined(TWI_ERROR_ENABLED) -uint8_t TwoWire::returnError() { - return vars._errors; -} -#endif - - /** *@brief TWI0 Slave Interrupt vector */ diff --git a/megaavr/libraries/Wire/src/Wire.h b/megaavr/libraries/Wire/src/Wire.h index d0485315..1e9e199d 100644 --- a/megaavr/libraries/Wire/src/Wire.h +++ b/megaavr/libraries/Wire/src/Wire.h @@ -18,7 +18,7 @@ Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts - Modified 2021-2022 by MX682X for megaTinyCore and DxCore. + Modified 2021-2023 by MX682X for megaTinyCore and DxCore. Added Support for Simultaneous master/slave, dual mode and Wire1. */ @@ -43,9 +43,9 @@ * and while the enhanced wire library *will* fit on 2k parts, you have very little flash left for anything else. * and the practicality of using it there is limited. */ - - - #ifndef ADD_READ_BIT + + +#ifndef ADD_READ_BIT #define ADD_READ_BIT(address) (address | 0x01) #endif #ifndef ADD_WRITE_BIT @@ -62,10 +62,11 @@ #error "This part only provides a single Wire interface." #endif +/* Instead of requiring changes to the library to switch between DxCore and megaTinyCore, we can check + * if the part supports dual mode. Goal is that the identical library can be used on both, so updates + * in one can be propagated to the other by just copying files. + */ #if ((defined(TWI0_DUALCTRL) && !defined(TWI_USING_WIRE1)) || (defined(TWI1_DUALCTRL) && defined(TWI_USING_WIRE1))) - /* Instead of requiring changes to the library to switch between DxCore and megaTinyCore, we can check - * if the part supports dual mode. Goal is that the identical library can be used on both, so updates - * in one can be propagated to the other by just copying files. */ #define TWI_DUALCTRL // This identifies if the device supports dual mode, where slave pins are different from the master pins #endif @@ -132,7 +133,7 @@ #define TWI_TIMEOUT_ENABLE // Enabled by default, might be disabled for debugging or other reasons #define TWI_ERROR_ENABLED // Enabled by default, TWI Master Write error functionality -//#define TWI_READ_ERROR_ENABLED // Enabled on Master Read too +#define TWI_READ_ERROR_ENABLED // Enabled on Master Read too //#define DISABLE_NEW_ERRORS // Disables the new error codes and returns TWI_ERR_UNDEFINED instead. // Errors from Arduino documentation: @@ -207,30 +208,30 @@ class TwoWire: public Stream { private: TWI_t *_module; uint8_t MasterCalcBaud(uint32_t frequency); - + uint8_t client_irq_mask; struct twiDataBools _bools; // the structure to hold the bools for the class #if defined(TWI_READ_ERROR_ENABLED) uint8_t _errors; #endif - + void (*user_onRequest)(void); void (*user_onReceive)(int); - + uint8_t _clientAddress; twi_buf_index_t _bytesToReadWrite; twi_buf_index_t _bytesReadWritten; twi_buf_index_t _bytesTransmittedS; #if defined(TWI_MANDS) - uint8_t _incomingAddress; - twi_buf_index_t _bytesToReadWriteS; - twi_buf_index_t _bytesReadWrittenS; + uint8_t _incomingAddress; + twi_buf_index_t _bytesToReadWriteS; + twi_buf_index_t _bytesReadWrittenS; #endif - + uint8_t _hostBuffer[TWI_BUFFER_LENGTH]; #if defined(TWI_MANDS) - uint8_t _clientBuffer[TWI_BUFFER_LENGTH]; + uint8_t _clientBuffer[TWI_BUFFER_LENGTH]; #endif public: @@ -258,9 +259,9 @@ class TwoWire: public Stream { } twi_buf_index_t requestFrom(uint8_t address, twi_buf_index_t quantity, uint8_t sendStop = 1); - - uint8_t masterTransmit(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop); - auto masterReceive(auto length, uint8_t addr, uint8_t* buffer, uint8_t sendStop); + + uint8_t masterTransmit(auto *length, uint8_t *buffer, uint8_t addr, uint8_t sendStop); + uint8_t masterReceive(auto *length, uint8_t *buffer, uint8_t addr, uint8_t sendStop); virtual size_t write(uint8_t); virtual size_t write(const uint8_t *, size_t); @@ -301,11 +302,7 @@ class TwoWire: public Stream { } size_t readBytes(char *data, size_t quantity); - #if defined(TWI_READ_ERROR_ENABLED) && defined(TWI_ERROR_ENABLED) - uint8_t returnError(); - #endif - - static void HandleSlaveIRQ(TwoWire* wire_s); + static void HandleSlaveIRQ(TwoWire *wire_s); }; diff --git a/megaavr/libraries/Wire/src/twi_pins.c b/megaavr/libraries/Wire/src/twi_pins.c index 7a828b9b..6f65970f 100644 --- a/megaavr/libraries/Wire/src/twi_pins.c +++ b/megaavr/libraries/Wire/src/twi_pins.c @@ -216,9 +216,9 @@ bool TWI0_Pins(uint8_t sda_pin, uint8_t scl_pin) { #endif #if defined(PIN_WIRE_SDA_PINSWAP_2) #if !defined(ERRATA_TWI0_MUX2) - if (sda_pin == PIN_WIRE_SDA_PINSWAP_2 && scl_pin == PIN_WIRE_SCL_PINSWAP_2) { - PORTMUX.TWIROUTEA = portmux | PORTMUX_TWI0_ALT2_gc; - return true; + if (sda_pin == PIN_WIRE_SDA_PINSWAP_2 && scl_pin == PIN_WIRE_SCL_PINSWAP_2) { + PORTMUX.TWIROUTEA = portmux | PORTMUX_TWI0_ALT2_gc; + return true; #else return false; #endif @@ -269,7 +269,7 @@ bool TWI0_swap(uint8_t state) { #if defined(PORTMUX_TWI0_bm) if (state == 1) { // Use pin swap - PORTMUX.CTRLB |= PORTMUX_TWI0_bm; + PORTMUX.CTRLB |= PORTMUX_TWI0_bm; return true; } else if (state == 0) { // Use default configuration @@ -305,8 +305,8 @@ bool TWI0_swap(uint8_t state) { if (state == 2) { // Use pin swap #if !defined(ERRATA_TWI0_MUX2) - PORTMUX.TWIROUTEA = portmux | PORTMUX_TWI0_ALT2_gc; - return true; + PORTMUX.TWIROUTEA = portmux | PORTMUX_TWI0_ALT2_gc; + return true; #else return false; #endif @@ -551,7 +551,7 @@ uint8_t TWI0_checkPinLevel(void) { #if defined(TWI1) void TWI1_ClearPins() { #if defined(PIN_WIRE1_SDA_PINSWAP_2) || defined(TWI1_DUALCTRL) - uint8_t portmux = PORTMUX.TWIROUTEA & PORTMUX_TWI1_gm; + uint8_t portmux = PORTMUX.TWIROUTEA & PORTMUX_TWI1_gm; #endif #if defined(PIN_WIRE1_SDA_PINSWAP_2) if (portmux == PORTMUX_TWI1_ALT2_gc) { // make sure we don't get errata'ed @@ -582,7 +582,7 @@ bool TWI1_Pins(uint8_t sda_pin, uint8_t scl_pin) { #if (defined(PIN_WIRE1_SDA_PINSWAP_1) || defined(PIN_WIRE1_SDA_PINSWAP_2)) // Danger: 'portmux' in this context means all the other settings in portmux, since we're replacing the PORTMUX setting for TWI1, and will bitwise-or with the _gc constants. // Elsewhere, 'portmux' refers to the setting for this peripheral only, and we compare it to PORTMUX_TWI1_xxx_gc - uint8_t portmux = PORTMUX.TWIROUTEA & ~PORTMUX_TWI1_gm; + uint8_t portmux = PORTMUX.TWIROUTEA & ~PORTMUX_TWI1_gm; #if defined(PIN_WIRE1_SDA_PINSWAP_2) if (sda_pin == PIN_WIRE1_SDA_PINSWAP_2 && scl_pin == PIN_WIRE1_SCL_PINSWAP_2) { // Use pin swap @@ -628,7 +628,7 @@ bool TWI1_Pins(uint8_t sda_pin, uint8_t scl_pin) { bool TWI1_swap(uint8_t state) { // Danger: 'portmux' in this context means all the other settings in portmux, since we're replacing the PORTMUX setting for TWI1, and will bitwise-or with the _gc constants. // Elsewhere, 'portmux' refers to the setting for this peripheral only, and we compare it to PORTMUX_TWI1_xxx_gc - uint8_t portmux = PORTMUX.TWIROUTEA & (~PORTMUX_TWI1_gm); + uint8_t portmux = PORTMUX.TWIROUTEA & (~PORTMUX_TWI1_gm); #if defined(PIN_WIRE1_SDA_PINSWAP_2) if (state == 2) { // Use pin swap @@ -666,7 +666,7 @@ bool TWI1_swap(uint8_t state) { void TWI1_usePullups() { #if defined(PIN_WIRE1_SDA_PINSWAP_2) || defined(TWI1_DUALCTRL) - uint8_t portmux = PORTMUX.TWIROUTEA & PORTMUX_TWI1_gm; + uint8_t portmux = PORTMUX.TWIROUTEA & PORTMUX_TWI1_gm; #endif PORT_t *port; #if defined(PORTB) //All parts with a TWI1 have a PORTF @@ -678,6 +678,7 @@ void TWI1_usePullups() { #else port = &PORTF; #endif + port->OUTCLR = 0x0C; // bits 2 and 3 port->PIN2CTRL |= PORT_PULLUPEN_bm; port->PIN3CTRL |= PORT_PULLUPEN_bm; @@ -735,8 +736,8 @@ uint8_t TWI1_setConfig(bool smbuslvl, bool longsetup, uint8_t sda_hold, bool smb } else if (sda_hold_dual || smbuslvl_dual) { return 0x04; } - #endif - return 0; + #endif + return 0; } diff --git a/megaavr/libraries/Wire/src/twi_pins.h b/megaavr/libraries/Wire/src/twi_pins.h index 6bb8493b..d18de535 100644 --- a/megaavr/libraries/Wire/src/twi_pins.h +++ b/megaavr/libraries/Wire/src/twi_pins.h @@ -34,9 +34,9 @@ //If a core provides badArg() and badCall(), it should define CORE_HAS_ERRORFUNS to = 1. //otherwise we need to add them in here - this is for ease of using with MegaCoreX #if !defined(CORE_HAS_ERRORFUNS) - void badArg(const char*) __attribute__((error(""))); //used when an argument is known at compile time to be an invalid value + void badArg(const char *) __attribute__((error(""))); //used when an argument is known at compile time to be an invalid value // but the function being called is valid to call at this time. - void badCall(const char*) __attribute__((error(""))); //used when it is known at compile time that a whole function cannot be called, + void badCall(const char *) __attribute__((error(""))); //used when it is known at compile time that a whole function cannot be called, // regardless of what arguments it is passed! Typically this is things like timekeeping when millis is disabled, and functions that // only work on some parts - things like TWI1_swap() which has only 1 option on some parts. #endif From 949491fd932fe9a418db8462abacc37e4a859ce4 Mon Sep 17 00:00:00 2001 From: MX682X Date: Tue, 16 Apr 2024 20:48:52 +0200 Subject: [PATCH 4/6] Fix List of examples to compile --- .github/workflows/compile-examples.yml | 7 ++++--- .../examples/self_and_mutual_mix/self_and_mutual_mix.ino | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 3764e4c1..b3240923 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -52,9 +52,10 @@ jobs: - megaavr/libraries/USERSIG/examples/usersig_put - megaavr/libraries/USERSIG/examples/usersig_read - megaavr/libraries/Wire/examples/digital_potentiometer - - megaavr/libraries/Wire/examples/slave_write_read - - megaavr/libraries/Wire/examples/master_and_slave - - megaavr/libraries/Wire/examples/master_write_read + - megaavr/libraries/Wire/examples/slave_write + - megaavr/libraries/Wire/examples/slave_read + - megaavr/libraries/Wire/examples/master_write + - megaavr/libraries/Wire/examples/master_read - megaavr/libraries/Wire/examples/slave_secondary_address - megaavr/libraries/megaTinyCore/examples/ModernRevSer - megaavr/libraries/megaTinyCore/examples/readTempVcc diff --git a/megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino b/megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino index 052aaf86..b0c5af75 100644 --- a/megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino +++ b/megaavr/libraries/PTC/examples/self_and_mutual_mix/self_and_mutual_mix.ino @@ -3,7 +3,7 @@ * This example creates four different sensing nodes. of two different types. * PA4 and PA5 are the self-cap lines with PB0 acting as shield pin. * PA6 and PA7 are the Y-Lines with PB1 acting as the X-line. - * PTC_CB_EVENT_CONV_MUTUAL_CMPL and + * PTC_CB_EVENT_CONV_MUTUAL_CMPL and * PTC_CB_EVENT_CONV_SHIELD_CMPL can be used to change the type that is converted. * This will create an interlaced conversion, but it is not mandatory to do so. */ From bed90a351bab5e7e08acb1e7bb2a170e2bb7c9fb Mon Sep 17 00:00:00 2001 From: MX682X Date: Sat, 25 May 2024 11:42:46 +0200 Subject: [PATCH 5/6] Fixing #1090 Summed up, made the Wire library more resistant to erroneous beahiour of the physical bus in Client/Slave mode --- megaavr/libraries/Wire/src/Wire.cpp | 86 ++++++++++++++++------------- megaavr/libraries/Wire/src/Wire.h | 5 +- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/megaavr/libraries/Wire/src/Wire.cpp b/megaavr/libraries/Wire/src/Wire.cpp index 295bced2..5fa0bd57 100644 --- a/megaavr/libraries/Wire/src/Wire.cpp +++ b/megaavr/libraries/Wire/src/Wire.cpp @@ -40,10 +40,10 @@ extern "C" { // compiler was complaining when I put twi.h into the upper C in #include "twi_pins.h" } -static uint8_t sleepStack = 0; +volatile uint8_t sleepStack = 0; -void pushSleep(void); -void popSleep(void); +void pauseDeepSleep(uint8_t moduelAddr); +void restoreSleep(uint8_t moduelAddr); TwoWire* twi0_wire; TwoWire* twi1_wire; @@ -1288,7 +1288,6 @@ void TwoWire::HandleSlaveIRQ(TwoWire *wire_s) { return; } - uint8_t *address, *buffer; twi_buf_index_t *head, *tail; #if defined(TWI_MANDS) @@ -1312,22 +1311,21 @@ void TwoWire::HandleSlaveIRQ(TwoWire *wire_s) { if (clientStatus & TWI_APIF_bm) { // Address/Stop Bit set - - if ((*head) > 0) { // At this point, we have either a START, REPSTART or a STOP - if (wire_s->user_onReceive != NULL) { // at START, head should be 0, as it was set so on the last STOP - wire_s->user_onReceive((*head)); // otherwise, we notify the use sketch, as we have an REPSTART/STOP + if (wire_s->_bools._hostDataSent != 0) { // At this point, we have either a START, REPSTART or a STOP + wire_s->_bools._hostDataSent = 0x00; + if (wire_s->user_onReceive != NULL) { // only if the last APIF was a Master Write, + wire_s->user_onReceive((*head)); // we notify the sketch about new Data } } if (clientStatus & TWI_AP_bm) { // Address bit set if ((*head) == 0) { // only if there was no data (START) - pushSleep(); // push the sleep + pauseDeepSleep((uint8_t)((uint16_t)wire_s->_module)); // Only START can wake from deep sleep, change to IDLE } + (*head) = 0; + (*tail) = 0; (*address) = wire_s->_module->SDATA; // read address from data register if (clientStatus & TWI_DIR_bm) { // Master is reading - (*head) = 0; // reset buffer positions for user sketch - (*tail) = 0; - if (wire_s->user_onRequest != NULL) { wire_s->user_onRequest(); } @@ -1337,31 +1335,30 @@ void TwoWire::HandleSlaveIRQ(TwoWire *wire_s) { action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" } } else { // Master is writing - (*head) = 0; // reset buffer positions - (*tail) = 0; - action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" + wire_s->_bools._hostDataSent = 0x01; + action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by slave data interrupt" } } else { // Stop bit set - popSleep(); - - (*head) = 0; // clear whatever might be left due errors + restoreSleep((uint8_t)((uint16_t)wire_s->_module)); + (*head) = 0; (*tail) = 0; action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" } } else if (clientStatus & TWI_DIF_bm) { // Data bit set if (clientStatus & TWI_DIR_bm) { // Master is reading if (clientStatus & wire_s->client_irq_mask) { // If a collision was detected, or RXACK bit is set (when it matters) - (*head) = 0; // Abort further data writes wire_s->client_irq_mask = TWI_COLL_bm; // stop checking for NACK + (*head) = 0; // Abort further data writes action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" } else { // RXACK bit not set, no COLL - wire_s->_bytesTransmittedS++; // increment bytes transmitted counter (for register model) + wire_s->_bytesTransmittedS++; // increment bytes transmitted counter (for register model) wire_s->client_irq_mask = TWI_COLL_bm | TWI_RXACK_bm; // start checking for NACK if ((*tail) < (*head)) { // Data is available wire_s->_module->SDATA = buffer[(*tail)]; // Writing to the register to send data (*tail)++; // Increment counter for sent bytes - action = TWI_SCMD_RESPONSE_gc; // "Execute a byte read operation followed by Acknowledge Action" + action = TWI_SCMD_RESPONSE_gc; // "Execute Acknowledge Action succeeded by reception of next byte" } else { // No more data available + (*head) = 0; // Avoid triggering REPSTART handler action = TWI_SCMD_COMPTRANS_gc; // "Wait for any Start (S/Sr) condition" } } @@ -1433,50 +1430,61 @@ ISR(TWI0_TWIS_vect) { /** - *@brief pushSleep and popSleep handle the sleep guard + *@brief pauseDeepSleep and restoreSleep handle the sleep guard * * When used only by one peripheral, just saving the sleep register is plenty, * But when used by more then one, special care must be taken to restore the - * sleep settings only at the end. + * sleep settings only at the end by using a bitmask on the upper nibble. * e.g. when TWI0 - START, TWI1 - START, TWI0 - STOP, TWI1 - STOP - * so, there is a counter that counts up to 15 when pushing and down to 0 when - * popping. Only at 0, the actual push and pop happen. An overflow will lead to - * unpredictable results. * - *@param none + *@param uint8_t module_lower_Addr - lower byte of the TWI Module address + (used only with two Wire objects) * *@return void */ -void pushSleep() { +void pauseDeepSleep(uint8_t module_lower_Addr) { #if defined(TWI_USING_WIRE1) + uint8_t bit_mask = 0x10; + if (module_lower_Addr == (uint8_t)((uint16_t)&TWI1)){ + bit_mask = 0x20; + } uint8_t sleepStackLoc = sleepStack; - if (sleepStackLoc > 0) { // Increment only if sleep was enabled - sleepStackLoc = (sleepStackLoc + 0x10); // use upper nibble to count - max 15 pushes - } else { + if (sleepStackLoc == 0) { // Save sleep state only if stack empty sleepStackLoc = SLPCTRL.CTRLA; // save sleep settings to sleepStack - SLPCTRL.CTRLA = sleepStackLoc & 0x01; // Set to IDLE if sleep was enabled + SLPCTRL.CTRLA = sleepStackLoc & 0x01; // only leave the SEN bit, if it was set } + sleepStackLoc |= bit_mask; // Remember which module is busy sleepStack = sleepStackLoc; #else - sleepStack = SLPCTRL.CTRLA; // save old sleep State - SLPCTRL.CTRLA = sleepStack & 0x01; // only leave the SEN bit, if it was set + (void) module_lower_Addr; + + if (sleepStack == 0x00) { + uint8_t slp = SLPCTRL.CTRLA; // save current sleep State + sleepStack = slp; // using local variable for less store/load + SLPCTRL.CTRLA = slp & 0x01; // only leave the SEN bit, if it was set + } #endif } -void popSleep() { +void restoreSleep(uint8_t module_lower_Addr) { #if defined(TWI_USING_WIRE1) + uint8_t bit_mask = ~0x10; + if (module_lower_Addr == (uint8_t)((uint16_t)&TWI1)){ + bit_mask = ~0x20; + } uint8_t sleepStackLoc = sleepStack; + sleepStackLoc &= bit_mask; if (sleepStackLoc > 0) { // only do something if sleep was enabled - if (sleepStackLoc > 0x10) { // only decrement if pushed once before - sleepStackLoc = (sleepStackLoc - 0x10); // upper nibble - } else { // at 0 we are about to put sleep back + if (sleepStackLoc < 0x10) { // if upper nibble is clear SLPCTRL.CTRLA = sleepStackLoc; // restore sleep sleepStackLoc = 0; // reset everything } - sleepStack = sleepStackLoc; } + sleepStack = sleepStackLoc; #else + (void) module_lower_Addr; SLPCTRL.CTRLA = sleepStack; + sleepStack = 0; #endif } diff --git a/megaavr/libraries/Wire/src/Wire.h b/megaavr/libraries/Wire/src/Wire.h index 1e9e199d..13311183 100644 --- a/megaavr/libraries/Wire/src/Wire.h +++ b/megaavr/libraries/Wire/src/Wire.h @@ -197,13 +197,12 @@ struct twiDataBools { // using a struct so the compiler can use skip if bool _toggleStreamFn: 1; // used to toggle between Slave and Master elements when TWI_MANDS defined bool _hostEnabled: 1; bool _clientEnabled: 1; - uint8_t _reserved: 5; + bool _hostDataSent: 1; + uint8_t _reserved: 4; }; - - class TwoWire: public Stream { private: TWI_t *_module; From 9f2dc7a9176bbe0830798e31cd4900087dec5776 Mon Sep 17 00:00:00 2001 From: MX682X Date: Sat, 25 May 2024 13:56:39 +0200 Subject: [PATCH 6/6] linting --- megaavr/libraries/Wire/src/Wire.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/megaavr/libraries/Wire/src/Wire.cpp b/megaavr/libraries/Wire/src/Wire.cpp index 5fa0bd57..00712a8b 100644 --- a/megaavr/libraries/Wire/src/Wire.cpp +++ b/megaavr/libraries/Wire/src/Wire.cpp @@ -1457,7 +1457,7 @@ void pauseDeepSleep(uint8_t module_lower_Addr) { sleepStack = sleepStackLoc; #else (void) module_lower_Addr; - + if (sleepStack == 0x00) { uint8_t slp = SLPCTRL.CTRLA; // save current sleep State sleepStack = slp; // using local variable for less store/load