|
- /*
- 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
|