/* 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. */ #ifndef _I2C_DMAC_h #define _I2C_DMAC_h #include #include "DMAC.h" enum { REG_ADDR_8BIT = 1, REG_ADDR_16BIT }; // Register address mode definitions, 8-bit address or 16-bit address class I2C_DMAC { public: I2C_DMAC(SERCOM* s, uint8_t pinSDA, uint8_t pinSCL); // Class constructor to initialise, SERCOM class, pins and member variables void begin(uint32_t baudrate = 100000, // Begin with 100kHz default clock rate and 8-bit register address mode uint8_t regAddrMode = REG_ADDR_8BIT, EPioType ulPeripheral = PIO_SERCOM); void end(); // Tear down and tidy up resources void setClock(uint32_t baudrate); // Set the I2C bus clock speed to the specified baud rate void setWriteChannel(uint8_t channel); // Set the DMAC write channel number (0 - 11), default 0 void setReadChannel(uint8_t channel); // Set the DMAC read channel number (0 - 11), default 1 void setPriority(uint8_t priority); // Set the priority level for both read and write DMAC channels (0 - 3), default 0 void setRegAddrMode(uint8_t regAddrMode); // Set the register address mode REG_ADDR_8BIT or REG_ADDR_16BIT void initWriteBytes(uint8_t devAddress, uint8_t* data, uint8_t count); // Initialise DMAC to send data, (no register address) void initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count); // Initialise DMAC to send register address + data void initWriteByte(uint8_t devAddress, uint16_t regAddress, uint8_t data); // Initialise DMAC to send register address + 1 data byte void initWriteRegAddr(uint8_t devAddress, uint16_t regAddress); // Initialise DMAC to send register address, (no data) void initReadBytes(uint8_t devAddress, uint8_t* data, uint8_t count); // Initialise DMAC to receive data void initReadByte(uint8_t devAddress); // Initialise DMAC to receive 1 data byte uint8_t getData(); // Retrieve the received data byte void write(); // Transmit on I2C bus void read(); // Receive on I2C bus void writeBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count); // Transmit data to the I2C device's register address void writeByte(uint8_t devAddress, uint16_t regAddress, uint8_t data); // Transmit 1 data byte to the I2C device's register address void writeRegAddr(uint8_t devAddress, uint16_t regAddress); // Write the register address to the I2C device void readBytes(uint8_t devAddress, uint8_t* data, uint8_t count); // Receive data from the I2C device void readByte(uint8_t devAddress); // Receive 1 data byte from the I2C device void readBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count); // Receive data from the I2C device's register address void readByte(uint8_t devAddress, uint16_t regAddress); // Receive a byte from the I2C device's register address void attachWriteCallback(voidFuncPtr callback); // Attach a DMAC write callback function void attachReadCallback(voidFuncPtr callback); // Attach a DMAC read callback function void attachDmacErrorCallback(voidFuncPtr callback); // Attach a DMAC error callback function void attachSercomErrorCallback(voidFuncPtr callback); // Attach a SERCOM error callback function void detachWriteCallback(); // Detach the DMAC write callback function void detachReadCallback(); // Detach the DMAC read callback function void detachDmacErrorCallback(); // Detach the DAMC error callback function void detachSercomErrorCallback(); // Detach the SERCOM error callback function static void DMAC_IrqHandler(); // DMAC interrupt handler function void SERCOM_IrqHandler(); // SERCOM interrupt handler function volatile boolean writeBusy; // Write busy flag - indicates write transfer in progress volatile boolean readBusy; // Read busy flag - indicates read transfer in progress static volatile I2C_DMAC* i2cDmacPtrs[SERCOM_INST_NUM]; // Array of pointer to each instance (object) of this class static volatile uint8_t instanceCounter; // Number of instances (objects) of this class protected: // Generic initialise write DMAC transfer function void initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count, uint8_t readAddrLength); private: enum { WRITE, READ }; // I2C read and write bits definitions, WRITE: 0, READ: 1 dmacdescriptor linked_descriptor __attribute__ ((aligned (16))); // DMAC linked descriptor Sercom* sercom; // Pointer to the selected SERCOMx peripheral uint8_t pinSDA; // The selected SDA pin uint8_t pinSCL; // The selected SCL pin uint8_t devAddress; // The I2C device address uint8_t regAddress[2]; // The I2C device's register address sent MSB first, LSB last uint8_t data; // Used for single byte data transfer uint8_t writeCount; // The number of data bytes to transmit uint8_t readCount; // The number of data bytes to receive uint8_t regAddrLength; // The length of the register address, 0: data only, 1: 8-bit, 2: 16-bit uint8_t regAddrMode; // The current register address mode: REG_ADDR_8BIT or REG_ADDR_16BIT uint8_t dmacPriority; // The DMAC read and write priority level uint8_t dmacWriteTrigger; // The DMAC write peripheral trigger source uint8_t dmacReadTrigger; // The DMAC read peripheral trigger source uint8_t genericClockId; // The generic clock ID used to select the sercom peripheral to be clocked IRQn_Type nvicId; // The Nested Vector Interrupt (NVIC) interrupt type for the SERCOMx peripheral typedef void (*voidFuncPtr)(void); // Alias for function pointer declarations volatile voidFuncPtr writeCallback; // Write callback function pointer volatile voidFuncPtr readCallback; // Read callback function pointer volatile voidFuncPtr errorDmacCallback; // DMAC error callback function pointer volatile voidFuncPtr errorSercomCallback; // SERCOM error callback function pointer volatile uint8_t dmacWriteChannel; // DMAC write channel number (0 to 11), default 0 volatile uint8_t dmacReadChannel; // DMAC read channel number (0 to 11), default 1 }; #if WIRE_INTERFACES_COUNT > 0 extern I2C_DMAC I2C; // Indicate that the I2C object is externally instantiated #endif #if WIRE_INTERFACES_COUNT > 1 extern I2C_DMAC I2C1; // Indicate that the I2C1 object is externally instantiated #endif #if WIRE_INTERFACES_COUNT > 2 extern I2C_DMAC I2C2; // Indicate that the I2C2 object is externally instantiated #endif #if WIRE_INTERFACES_COUNT > 3 extern I2C_DMAC I2C3; // Indicate that the I2C3 object is externally instantiated #endif #if WIRE_INTERFACES_COUNT > 4 extern I2C_DMAC I2C4; // Indicate that the I2C4 object is externally instantiated #endif #if WIRE_INTERFACES_COUNT > 5 extern I2C_DMAC I2C5; // Indicate that the I2C5 object is externally instantiated #endif #endif