/* I2C_DMAC is a non-blocking I2C library that uses a SERCOM in conjunction with the Direct Memory Access Controller (DMAC). Copyright (C) Martin Lindupp 2018 V1.0.0 -- Initial release V1.1.0 -- Add Arduino MKR and SAMD51 support, plus multiple I2C instances V1.1.1 -- Replaced pinPeripheral() function with port register manipulation V1.1.2 -- Allow other classes to simultaneously use remaining DMAC channels V1.1.3 -- Fixed issue with consecutive calls to writeByte() overwriting data V1.1.4 -- Allow the DMAC to resume normal operation after an early NACK is received V1.1.5 -- Activate internal pull-up resistors and increase driver strength V1.1.6 -- Add SERCOM ALT (alternative) peripheral switch for the Metro M4 V1.1.7 -- Arduino IDE library manager release V1.1.8 -- Code optimisation V1.1.9 -- Use default arguments for begin() member function V1.1.10 -- Remove I2C instance on writeBusy flag in ReadByte() and ReadBytes() functions V1.1.11 -- Arduino IDE library manager release The MIT License (MIT) 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. */ #include "I2C_DMAC.h" volatile I2C_DMAC* I2C_DMAC::i2cDmacPtrs[SERCOM_INST_NUM]; // Array of pointer to each instance (object) of this class volatile uint8_t I2C_DMAC::instanceCounter; // Number of instances (objects) of this class volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC write back descriptor array dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC channel descriptor array dmacdescriptor descriptor __attribute__ ((aligned (16))); // DMAC place holder descriptor // // Initialisation section: prep the the port pins, SERCOM and the DMAC // I2C_DMAC::I2C_DMAC(SERCOM* sercomClass, uint8_t pinSDA, uint8_t pinSCL) : // Constructor to initialise SERCOM, pins and DMAC channel member variables dmacWriteChannel(0), dmacReadChannel(1), dmacPriority(0) { this->pinSCL = pinSCL; // Copy the I2C pins this->pinSDA = pinSDA; if (sercomClass == &sercom0) // Use the sercom class to acquire the underlying SERCOMx peripheral { sercom = SERCOM0; // This is used to allow the variant.h and variant.cpp files to dmacWriteTrigger = SERCOM0_DMAC_ID_TX; // specify the sercom peripheral and the SCL/SDA pins used, just like dmacReadTrigger = SERCOM0_DMAC_ID_RX; // the Wire library #ifdef __SAMD51__ genericClockId = SERCOM0_GCLK_ID_CORE; nvicId = SERCOM0_3_IRQn; #else genericClockId = GCLK_CLKCTRL_ID_SERCOM0_CORE; nvicId = SERCOM0_IRQn; #endif } else if (sercomClass == &sercom1) // The DMAC read and write trigger source as well as the generic clock id { // and sercom's NVIC interrupt id are also copied sercom = SERCOM1; dmacWriteTrigger = SERCOM1_DMAC_ID_TX; dmacReadTrigger = SERCOM1_DMAC_ID_RX; #ifdef __SAMD51__ genericClockId = SERCOM1_GCLK_ID_CORE; nvicId = SERCOM1_3_IRQn; #else genericClockId = GCLK_CLKCTRL_ID_SERCOM1_CORE; nvicId = SERCOM1_IRQn; #endif } else if (sercomClass == &sercom2) { sercom = SERCOM2; dmacWriteTrigger = SERCOM2_DMAC_ID_TX; dmacReadTrigger = SERCOM2_DMAC_ID_RX; #ifdef __SAMD51__ genericClockId = SERCOM2_GCLK_ID_CORE; nvicId = SERCOM2_3_IRQn; #else genericClockId = GCLK_CLKCTRL_ID_SERCOM2_CORE; nvicId = SERCOM2_IRQn; #endif } else if (sercomClass == &sercom3) { sercom = SERCOM3; dmacWriteTrigger = SERCOM3_DMAC_ID_TX; dmacReadTrigger = SERCOM3_DMAC_ID_RX; #ifdef __SAMD51__ genericClockId = SERCOM3_GCLK_ID_CORE; nvicId = SERCOM3_3_IRQn; #else genericClockId = GCLK_CLKCTRL_ID_SERCOM3_CORE; nvicId = SERCOM3_IRQn; #endif } else if (sercomClass == &sercom4) { sercom = SERCOM4; dmacWriteTrigger = SERCOM4_DMAC_ID_TX; dmacReadTrigger = SERCOM4_DMAC_ID_RX; #ifdef __SAMD51__ genericClockId = SERCOM4_GCLK_ID_CORE; nvicId = SERCOM4_3_IRQn; #else genericClockId = GCLK_CLKCTRL_ID_SERCOM4_CORE; nvicId = SERCOM4_IRQn; #endif } else if (sercomClass == &sercom5) { sercom = SERCOM5; dmacWriteTrigger = SERCOM5_DMAC_ID_TX; dmacReadTrigger = SERCOM5_DMAC_ID_RX; #ifdef __SAMD51__ genericClockId = SERCOM5_GCLK_ID_CORE; nvicId = SERCOM5_3_IRQn; #else genericClockId = GCLK_CLKCTRL_ID_SERCOM5_CORE; nvicId = SERCOM5_IRQn; #endif } } void I2C_DMAC::begin(uint32_t baudrate, uint8_t regAddrMode, EPioType ulPeripheral) // Set baud rate and the register address mode: 8-bit or 16-bit { if (ulPeripheral != PIO_SERCOM && ulPeripheral != PIO_SERCOM_ALT) // Check that the peripheral mutliplexer is set to a SERCOM or SERCOM_ALT { return; } this->regAddrMode = regAddrMode; // Enable the SCL and SDA pins on the sercom: includes increased driver strength, pull-up resistors and pin multiplexer PORT->Group[g_APinDescription[pinSCL].ulPort].PINCFG[g_APinDescription[pinSCL].ulPin].reg = PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; PORT->Group[g_APinDescription[pinSDA].ulPort].PINCFG[g_APinDescription[pinSDA].ulPin].reg = PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN; PORT->Group[g_APinDescription[pinSDA].ulPort].PMUX[g_APinDescription[pinSDA].ulPin >> 1].reg = PORT_PMUX_PMUXO(ulPeripheral) | PORT_PMUX_PMUXE(ulPeripheral); if (!DMAC->CTRL.bit.DMAENABLE) // Enable the DMAC, if it hasn't already been enabled { #ifdef __SAMD51__ for (uint8_t i = 0; i < 5; i++) // Iterate through the SAMD51's 5 DMAC interrupt channels { NVIC_ClearPendingIRQ((IRQn_Type)(DMAC_0_IRQn + i)); // Clear any pending DMAC interrupts NVIC_SetPriority((IRQn_Type)(DMAC_0_IRQn + i), 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for the DMAC to 0 (highest) NVIC_EnableIRQ((IRQn_Type)(DMAC_0_IRQn + i)); // Connect the DMAC to the Nested Vector Interrupt Controller (NVIC) } #else NVIC_ClearPendingIRQ(DMAC_IRQn); // Clear any pending DMAC interrupts NVIC_SetPriority(DMAC_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for the DMAC to 0 (highest) NVIC_EnableIRQ(DMAC_IRQn); // Connect the DMAC to the Nested Vector Interrupt Controller (NVIC) #endif DMAC->CTRL.bit.SWRST = 1; // Reset the DMAC while (DMAC->CTRL.bit.SWRST); // Wait for synchronization DMAC->BASEADDR.reg = (uint32_t)descriptor_section; // Set the DMAC descriptor base address DMAC->WRBADDR.reg = (uint32_t)wrb; // Set the DMAC descriptor write-back address DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral and enable priority levels } NVIC_ClearPendingIRQ(nvicId); // Clear any Nested Vector Interrupt Controller (NVIC) pending interrupts NVIC_SetPriority(nvicId, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for the selected sercom to 0 (highest) NVIC_EnableIRQ(nvicId); // Connect selected sercom to the Nested Vector Interrupt Controller (NVIC) #ifdef __SAMD51__ // Enable GCLK1 (48MHz) on the selected sercom GCLK->PCHCTRL[genericClockId].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK1; #else // Enable GCLK0 (48MHz) on the selected sercom GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK0 to the selected sercom GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 as source GCLK_CLKCTRL_ID(genericClockId); // Select the selected sercom as destination while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization #endif sercom->I2CM.CTRLA.bit.SWRST = 1; // Reset the SERCOM while (sercom->I2CM.CTRLA.bit.SWRST || sercom->I2CM.SYNCBUSY.bit.SWRST); // Wait for synchronization sercom->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE(I2C_MASTER_OPERATION); // Set I2C master mode sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; // Enable Smart Mode sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / (2 * baudrate) - 7; // Set I2C master SCL baud rate sercom->I2CM.CTRLA.bit.ENABLE = 1 ; // Enable SERCOM in I2C master mode while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization sercom->I2CM.STATUS.bit.BUSSTATE = 0x01; // Set the I2C bus to IDLE state while (sercom->I2CM.SYNCBUSY.bit.SYSOP); // Wait for synchronization sercom->I2CM.INTENSET.bit.ERROR = 1; // Enable SERCOM ERROR interrupts i2cDmacPtrs[instanceCounter++] = this; // Copy the pointer to "this" object to the I2C_DMAC pointer array } // and increment the instance counter // // Tear down and tidy up resources // void I2C_DMAC::end() { for (uint8_t i = 0; i < instanceCounter; i++) // Iterate through each I2C_DMAC instance { if (i2cDmacPtrs[i] == this) // Find the location of "this" instance in the I2C_DMAC array pointer { i2cDmacPtrs[i] = 0; // Pull this instance pointer from the array if (i < instanceCounter - 1) // If the current instance not the last instance { for (uint8_t j = i; j < instanceCounter - 1; j++) { i2cDmacPtrs[j] = i2cDmacPtrs[j + 1]; // Shift the I2C_DMAC pointer down the array } i2cDmacPtrs[instanceCounter - 1] = 0; // Set the last instance pointer to NULL } instanceCounter--; // Decrement the instance counter break; // Escape from the (for) loop } } // Return the SCL and SDA lines on the sercom to GPIO PORT->Group[g_APinDescription[pinSCL].ulPort].PINCFG[g_APinDescription[pinSCL].ulPin].reg = 0; PORT->Group[g_APinDescription[pinSDA].ulPort].PINCFG[g_APinDescription[pinSDA].ulPin].reg = 0; PORT->Group[g_APinDescription[pinSDA].ulPort].PMUX[g_APinDescription[pinSDA].ulPin >> 1].reg = PORT_PMUX_PMUXO(0) | PORT_PMUX_PMUXE(0); sercom->I2CM.CTRLA.bit.ENABLE = 0; // Disable the I2C master mode while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization sercom->I2CM.CTRLA.bit.SWRST = 1; // Reset SERCOM3 while (sercom->I2CM.CTRLA.bit.SWRST || sercom->I2CM.SYNCBUSY.bit.SWRST); // Wait for synchronization #ifdef __SAMD51__ // Disable GCLK1 (48Mhz) on the selected sercom GCLK->PCHCTRL[genericClockId].reg = /*GCLK_PCHCTRL_CHEN |*/ GCLK_PCHCTRL_GEN_GCLK1; #else // Disable GCLK0 (48MHz) on the selected sercom GCLK->CLKCTRL.reg = /*GCLK_CLKCTRL_CLKEN |*/ // Disable GCLK0 to the selected sercom - intentionally commented out GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 as source GCLK_CLKCTRL_ID(genericClockId); // Select the selected sercom as destination while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization #endif if (instanceCounter == 0) { NVIC_DisableIRQ(nvicId); // Disconnect selected sercom from the Nested Vector Interrupt Controller (NVIC) DMAC->CTRL.bit.DMAENABLE = 0; // Disable the DMAC while(DMAC->CTRL.bit.DMAENABLE); // Wait for synchronization DMAC->CTRL.bit.SWRST = 1; // Reset the DMAC while (DMAC->CTRL.bit.SWRST); // Wait for synchronization #ifdef __SAMD51__ for (uint8_t i = 0; i < 5; i++) // Iterate through the SAMD51's 5 DMAC interrupt channels { NVIC_DisableIRQ((IRQn_Type)(DMAC_0_IRQn + i)); // Disconnect the DMAC from the Nested Vector Interrupt Controller (NVIC) } #else NVIC_DisableIRQ(DMAC_IRQn); // Disconnect the DMAC from the Nested Vector Interrupt Controller (NVIC) #endif } } void I2C_DMAC::setClock(uint32_t baudrate) { sercom->I2CM.CTRLA.bit.ENABLE = 0; // Disable SERCOM3 in I2C master mode while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / (2 * baudrate) - 7; // Set I2C master SCL baud rate sercom->I2CM.CTRLA.bit.ENABLE = 1 ; // Enable SERCOM3 in I2C master mode while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization } void I2C_DMAC::setWriteChannel(uint8_t channel) { dmacWriteChannel = channel < DMAC_CH_NUM ? channel : dmacWriteChannel; // Set the write DMAC channel, (default channel 0) } void I2C_DMAC::setReadChannel(uint8_t channel) { dmacReadChannel = channel < DMAC_CH_NUM ? channel : dmacReadChannel; // Set the read DMAC channel, (default channel 1) } void I2C_DMAC::setPriority(uint8_t priority) { dmacPriority = priority < DMAC_LVL_NUM ? priority : dmacPriority; // Set the priority of both write and read channels (0 lowest, 3 highest) } // // DMAC Section: Load the DMAC's transfer descriptors // void I2C_DMAC::setRegAddrMode(uint8_t regAddrMode) { this->regAddrMode = regAddrMode; // Set the register address mode: REG_ADDR_8BIT or REG_ADDR_16BIT } // Generic initialise write DMAC transfer function void I2C_DMAC::initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count, uint8_t regAddrLength) { while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE { this->devAddress = devAddress; // Copy the device address, plus write byte count and register address length writeCount = count; this->regAddrLength = regAddrLength; #ifdef __SAMD51__ // Set the DMAC level, trigger source and trigger action to burst (trigger for every byte transmitted) DMAC->Channel[dmacWriteChannel].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(dmacWriteTrigger) | DMAC_CHCTRLA_TRIGACT_BURST; DMAC->Channel[dmacWriteChannel].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL(dmacPriority); // Set the channel priority level DMAC->Channel[dmacWriteChannel].CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR #else DMAC->CHID.reg = DMAC_CHID_ID(dmacWriteChannel); // Activate specified DMAC write channel // Set the DMAC level, trigger source and trigger action to beat (trigger for every byte transmitted) DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(dmacPriority) | DMAC_CHCTRLB_TRIGSRC(dmacWriteTrigger) | DMAC_CHCTRLB_TRIGACT_BEAT; DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR #endif if (regAddrLength == 0) // Writing data only { descriptor.descaddr = 0; // Set this to the last descriptor (no linked list) descriptor.srcaddr = (uint32_t)data + count; // Set the source address descriptor.dstaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the destination address descriptor.btcnt = count; // Number of data bytes to transmit descriptor.btctrl = DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID; // Increment source address on BEAT transfer and validate descriptor memcpy(&descriptor_section[dmacWriteChannel], &descriptor, sizeof(dmacdescriptor)); // Copy the descriptor into SRAM descriptor array return; // Data only initialisation complete } else if (regAddrLength == 1) // 8-bit write address mode { this->regAddress[0] = (uint8_t)regAddress; // Copy the 8-bit register address } else // 16-bit write address mode { this->regAddress[0] = (uint8_t)(regAddress >> 8); // Copy the 16-bit register address MSB this->regAddress[1] = (uint8_t)(regAddress & 0xFF); // Copy the 16-bit register address LSB } descriptor.descaddr = count > 0 ? (uint32_t)&linked_descriptor : 0; // Link to next descriptor if there's data descriptor.srcaddr = (uint32_t)this->regAddress + regAddrLength; // Set the source address descriptor.dstaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the destination address descriptor.btcnt = regAddrLength; // Size of the register address in bytes descriptor.btctrl = DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID; // Increment source address on BEAT transfer and validate descriptor memcpy(&descriptor_section[dmacWriteChannel], &descriptor, sizeof(dmacdescriptor)); // Copy the descriptor into SRAM descriptor array if (count > 0) // Append write data as linked descriptor { linked_descriptor.descaddr = 0; // Set linked_descriptor to last in the list linked_descriptor.srcaddr = (uint32_t)data + count; // Set the source address linked_descriptor.dstaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the destination address linked_descriptor.btcnt = count; // Number of data bytes to transmit linked_descriptor.btctrl = DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID; // Increment source address on BEAT transfer and validate descriptor } } } // // Base (1st) layer functions - Independent DMAC initialisation with separate read/write // void I2C_DMAC::initWriteBytes(uint8_t devAddress, uint8_t* data, uint8_t count) { initWriteBytes(devAddress, 0, data, count, 0); // Initialise DMAC write transfer: data only, no register address } void I2C_DMAC::initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count) { initWriteBytes(devAddress, regAddress, data, count, regAddrMode); // Initialise DMAC write transfer: register address + data } void I2C_DMAC::initWriteByte(uint8_t devAddress, uint16_t regAddress, uint8_t data) { while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE { this->data = data; initWriteBytes(devAddress, regAddress, (uint8_t*)&this->data, 1, regAddrMode); // Initialise DMAC write transfer: register address + 1 data byte } } void I2C_DMAC::initWriteRegAddr(uint8_t devAddress, uint16_t regAddress) { initWriteBytes(devAddress, regAddress, 0, 0, regAddrMode); // Initialise DMAC write transfer: register address only, no data } uint8_t I2C_DMAC::getData() { return data; // Return the received data byte } void I2C_DMAC::initReadBytes(uint8_t devAddress, uint8_t* data, uint8_t count) // Initialise DMAC read transfer: count bytes of data { while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE { this->devAddress = devAddress; // Copy device address and read byte count readCount = count; #ifdef __SAMD51__ // Set the DMAC level, trigger source and trigger action to burst (trigger for every byte received) DMAC->Channel[dmacReadChannel].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(dmacReadTrigger) | DMAC_CHCTRLA_TRIGACT_BURST; DMAC->Channel[dmacReadChannel].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL(dmacPriority); // Set the channel priority level DMAC->Channel[dmacReadChannel].CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR #else DMAC->CHID.reg = DMAC_CHID_ID(dmacReadChannel); // Activate the specified DMAC channel // Set the DMAC level, trigger source and trigger action to beat (trigger for every byte received) DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(dmacPriority) | DMAC_CHCTRLB_TRIGSRC(dmacReadTrigger) | DMAC_CHCTRLB_TRIGACT_BEAT; DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR #endif descriptor.descaddr = 0; // Single descriptor (no linked list) descriptor.srcaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the source address descriptor.dstaddr = (uint32_t)data + count; // Set the destination address descriptor.btcnt = count; // Number of data bytes to receive descriptor.btctrl = DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_VALID; // Increment destination address on BEAT transfer and validate descriptor memcpy(&descriptor_section[dmacReadChannel], &descriptor, sizeof(dmacdescriptor)); // Copy the descriptor into SRAM descriptor array } } void I2C_DMAC::initReadByte(uint8_t devAddress) { this->devAddress = devAddress; initReadBytes(devAddress, &data, 1); // Initialise DMAC read transfer: 1 byte of data } void I2C_DMAC::write() // Initiate DMAC write transmission on the I2C bus { while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE { writeBusy = true; #ifdef __SAMD51__ DMAC->Channel[dmacWriteChannel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC write channel #else DMAC->CHID.reg = DMAC_CHID_ID(dmacWriteChannel); // Activate the DMAC write channel DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC write channel #endif sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_LEN(writeCount + regAddrLength) | // Load the device address into the SERCOM3 ADDR register SERCOM_I2CM_ADDR_LENEN | SERCOM_I2CM_ADDR_ADDR(devAddress << 1 | WRITE); while (sercom->I2CM.SYNCBUSY.bit.SYSOP); // Wait for synchronization } } void I2C_DMAC::read() // Initiate DMAC read transmission on the I2C bus { while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE { readBusy = true; #ifdef __SAMD51__ DMAC->Channel[dmacReadChannel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC read channel #else DMAC->CHID.reg = DMAC_CHID_ID(dmacReadChannel); // Activate the DMAC read channel DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC read channel #endif sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_LEN(readCount) | // Load the I2C slave device address into the SERCOM3 ADDR register SERCOM_I2CM_ADDR_LENEN | SERCOM_I2CM_ADDR_ADDR(devAddress << 1 | READ); while (sercom->I2CM.SYNCBUSY.bit.SYSOP); // Wait for synchronization } } // // 2nd layer functions - Combined DMAC initialisation with read or write // void I2C_DMAC::writeBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count) { initWriteBytes(devAddress, regAddress, data, count); // Initialise DMAC write transfer: register address + data write(); // Transmit the register address + data } void I2C_DMAC::writeByte(uint8_t devAddress, uint16_t regAddress, uint8_t data) { initWriteByte(devAddress, regAddress, data); // Initialise DMAC write transfer: register address + 1 byte data write(); // Transmit the register address + data } void I2C_DMAC::writeRegAddr(uint8_t devAddress, uint16_t regAddress) { initWriteRegAddr(devAddress, regAddress); // Initialise DMAC write transfer: register address, no data write(); // Transmit the register address } void I2C_DMAC::readBytes(uint8_t devAddress, uint8_t* data, uint8_t count) { initReadBytes(devAddress, data, count); // Initialise DMAC read transfer: data read(); // Receive the data } void I2C_DMAC::readByte(uint8_t devAddress) { initReadByte(devAddress); // Initialise DMAC read transfer: 1 byte data read(); // Receive the data } // // 3rd layer functions - Combined DMAC initialisation with read and write // void I2C_DMAC::readBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count) { writeRegAddr(devAddress, regAddress); // Set the register address on the I2C slave device while(writeBusy); // Wait for the write to complete readBytes(devAddress, data, count); // Receive the returned data } void I2C_DMAC::readByte(uint8_t devAddress, uint16_t regAddress) { writeRegAddr(devAddress, regAddress); // Set the register address on the I2C slave device while(writeBusy); // Wait for the write to complete readByte(devAddress); // Receive the returned data byte } // // DMAC Interrupt Handler: Busy Flag and Callback Section // void I2C_DMAC::attachWriteCallback(voidFuncPtr callback) { writeCallback = callback; // Attach a write complete callback function } void I2C_DMAC::attachReadCallback(voidFuncPtr callback) { readCallback = callback; // Attach a read complete callback function } void I2C_DMAC::attachDmacErrorCallback(voidFuncPtr callback) { errorDmacCallback = callback; // Attach a DMAC error callback function } void I2C_DMAC::attachSercomErrorCallback(voidFuncPtr callback) { errorSercomCallback = callback; // Attach a SERCOM error callback function } void I2C_DMAC::detachWriteCallback() { writeCallback = 0; // Detach the write complete callback function } void I2C_DMAC::detachReadCallback() { readCallback = 0; // Detach the read complete callback function } void I2C_DMAC::detachDmacErrorCallback() { errorDmacCallback = 0; // Detach the DMAC error callback function } void I2C_DMAC::detachSercomErrorCallback() { errorSercomCallback = 0; // Detach the SERCOM error callback function } void I2C_DMAC::DMAC_IrqHandler() { //__disable_irq(); // Disable interrupts volatile I2C_DMAC* i2cDmacPtr = i2cDmacPtrs[0]; // Set I2C_DMAC pointer to the first I2C_DMAC instance uint8_t activeChannel = DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk; // Get DMAC channel number if (instanceCounter > 1) // If there's only one instance of the I2C_DMAC object, { // skip finding the active channel instance for (uint8_t i = 0; i < instanceCounter; i++) // Find the I2C_DMAC instance from the active channel { if (activeChannel == i2cDmacPtrs[i]->dmacWriteChannel || // Compare the active channel against the object's activeChannel == i2cDmacPtrs[i]->dmacReadChannel) // { i2cDmacPtr = i2cDmacPtrs[i]; // Assign I2C_DMAC pointer to the object instance with } // the active channel } } #ifdef __SAMD51__ if (DMAC->Channel[activeChannel].CHINTFLAG.bit.TERR) // DMAC Transfer error (TERR) #else DMAC->CHID.reg = DMAC_CHID_ID(activeChannel); // Switch to the active DMAC channel if (DMAC->CHINTFLAG.bit.TERR) // DMAC Transfer error (TERR) #endif { if (i2cDmacPtr->errorDmacCallback) // Check if there's a DMAC error callback function { i2cDmacPtr->errorDmacCallback(); // Call the callback function } } #ifdef __SAMD51__ else if (DMAC->Channel[activeChannel].CHINTFLAG.bit.TCMPL) // DMAC transfer complete (TCMPL) #else else if (DMAC->CHINTFLAG.bit.TCMPL) // DMAC transfer complete (TCMPL) #endif { if (activeChannel == i2cDmacPtr->dmacWriteChannel) // Check write DMAC channel { i2cDmacPtr->writeBusy = false; // Clear the write busy flag if (i2cDmacPtr->writeCallback) // Check if there's a write callback function { i2cDmacPtr->writeCallback(); // Call the write callback function } } else if (activeChannel == i2cDmacPtr->dmacReadChannel) // Check read DMAC channel { i2cDmacPtr->readBusy = false; // Clear the read busy flag if (i2cDmacPtr->readCallback) // Check if there's a read callback function { i2cDmacPtr->readCallback(); // Call the read callback function } } } #ifdef __SAMD51__ DMAC->Channel[activeChannel].CHINTFLAG.reg = DMAC_CHINTFLAG_MASK; // Clear the DMAC channel interrupt flags #else DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_MASK; // Clear the DMAC channel interrupt flags #endif //__enable_irq(); // Enable interrupts } void I2C_DMAC::SERCOM_IrqHandler() { if (sercom->I2CM.INTFLAG.bit.ERROR && sercom->I2CM.INTENSET.bit.ERROR) { #ifdef __SAMD51__ DMAC->Channel[dmacWriteChannel].CHCTRLA.bit.ENABLE = 0; // Disable the DMAC write channel DMAC->Channel[dmacReadChannel].CHCTRLA.bit.ENABLE = 0; // Disable the DMAC read channel #else DMAC->CHID.reg = DMAC_CHID_ID(dmacWriteChannel); // Switch to the active DMAC write channel DMAC->CHCTRLA.bit.ENABLE = 0; // Disable the DMAC write channel DMAC->CHID.reg = DMAC_CHID_ID(dmacReadChannel); // Switch to the active DMAC read channel DMAC->CHCTRLA.bit.ENABLE = 0; // Disable the DMAC read channel #endif writeBusy = false; // Clear the write busy flag readBusy = false; // Clear the read busy flag if (errorSercomCallback) // Check if there's a SERCOM3 error callback function { errorSercomCallback(); // Call the SERCOM3 error callback function } sercom->I2CM.STATUS.reg |= SERCOM_I2CM_STATUS_LENERR | // Clear the status register flags - cleared by SERCOM_I2CM_STATUS_BUSERR; // writing to SERCOM3 ADDR.ADDR register anyway sercom->I2CM.INTFLAG.bit.ERROR = 1; // Clear the SERCOM error interrupt flag } } #ifdef __SAMD51__ void DMAC_0_Handler() __attribute__((weak)); // Set as weakly declared linker symbol, so that the function can be overriden void DMAC_0_Handler() // The DMAC_0_Handler() ISR { I2C_DMAC::DMAC_IrqHandler(); // Call the I2C_DMAC's DMAC interrupt handler member function } // Set the DMAC Handler function for the other DMAC channels, assign to weak aliases void DMAC_1_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); void DMAC_2_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); void DMAC_3_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); void DMAC_4_Handler(void) __attribute__((weak, alias("DMAC_0_Handler"))); #else void DMAC_Handler() __attribute__((weak)); // Set as weakly declared linker symbol, so that the function can be overriden void DMAC_Handler() // The DMAC_Handler() ISR { I2C_DMAC::DMAC_IrqHandler(); // Call the I2C_DMAC's DMAC interrupt handler member function } #endif #if WIRE_INTERFACES_COUNT > 0 // /* In case new variant doesn't define these macros, * we put here the ones for Arduino Zero. * * These values should be different on some variants! */ #ifndef PERIPH_WIRE #define PERIPH_WIRE sercom3 #ifdef __SAMD51__ #define WIRE_IT_HANDLER SERCOM3_3_Handler #else #define WIRE_IT_HANDLER SERCOM3_Handler #endif #endif // PERIPH_WIRE I2C_DMAC I2C(&PERIPH_WIRE, PIN_WIRE_SDA, PIN_WIRE_SCL); // Instantiate the I2C object void WIRE_IT_HANDLER() __attribute__((weak)); void WIRE_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function { I2C.SERCOM_IrqHandler(); } #endif #if WIRE_INTERFACES_COUNT > 1 I2C_DMAC I2C1(&PERIPH_WIRE1, PIN_WIRE1_SDA, PIN_WIRE1_SCL); void WIRE1_IT_HANDLER() __attribute__((weak)); void WIRE1_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function { I2C1.SERCOM_IrqHandler(); } #endif #if WIRE_INTERFACES_COUNT > 2 I2C_DMAC I2C2(&PERIPH_WIRE2, PIN_WIRE2_SDA, PIN_WIRE2_SCL); void WIRE2_IT_HANDLER() __attribute__((weak)); void WIRE2_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function { I2C2.SERCOM_IrqHandler(); } #endif #if WIRE_INTERFACES_COUNT > 3 I2C_DMAC I2C3(&PERIPH_WIRE3, PIN_WIRE3_SDA, PIN_WIRE3_SCL); void WIRE3_IT_HANDLER() __attribute__((weak)); void WIRE3_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function { I2C3.SERCOM_IrqHandler(); } #endif #if WIRE_INTERFACES_COUNT > 4 I2C_DMAC I2C4(&PERIPH_WIRE4, PIN_WIRE4_SDA, PIN_WIRE4_SCL); void WIRE4_IT_HANDLER() __attribute__((weak)); void WIRE4_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function { I2C4.SERCOM_IrqHandler(); } #endif #if WIRE_INTERFACES_COUNT > 5 I2C_DMAC I2C5(&PERIPH_WIRE5, PIN_WIRE5_SDA, PIN_WIRE5_SCL); void WIRE5_IT_HANDLER() __attribute__((weak)); void WIRE5_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function { I2C5.SERCOM_IrqHandler(); } #endif