A low cost DIY sound pressure level sensor for enabling environmental noise awareness. https://lukasschwarz.org/noise-sensor
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

735 lines
34 KiB

  1. /*
  2. I2C_DMAC is a non-blocking I2C library that uses a SERCOM in
  3. conjunction with the Direct Memory Access Controller (DMAC).
  4. Copyright (C) Martin Lindupp 2018
  5. V1.0.0 -- Initial release
  6. V1.1.0 -- Add Arduino MKR and SAMD51 support, plus multiple I2C instances
  7. V1.1.1 -- Replaced pinPeripheral() function with port register manipulation
  8. V1.1.2 -- Allow other classes to simultaneously use remaining DMAC channels
  9. V1.1.3 -- Fixed issue with consecutive calls to writeByte() overwriting data
  10. V1.1.4 -- Allow the DMAC to resume normal operation after an early NACK is received
  11. V1.1.5 -- Activate internal pull-up resistors and increase driver strength
  12. V1.1.6 -- Add SERCOM ALT (alternative) peripheral switch for the Metro M4
  13. V1.1.7 -- Arduino IDE library manager release
  14. V1.1.8 -- Code optimisation
  15. V1.1.9 -- Use default arguments for begin() member function
  16. V1.1.10 -- Remove I2C instance on writeBusy flag in ReadByte() and ReadBytes() functions
  17. V1.1.11 -- Arduino IDE library manager release
  18. The MIT License (MIT)
  19. Permission is hereby granted, free of charge, to any person obtaining a copy
  20. of this software and associated documentation files (the "Software"), to deal
  21. in the Software without restriction, including without limitation the rights
  22. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  23. copies of the Software, and to permit persons to whom the Software is
  24. furnished to do so, subject to the following conditions:
  25. The above copyright notice and this permission notice shall be included in all
  26. copies or substantial portions of the Software.
  27. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  28. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  29. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  30. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  31. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  32. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  33. SOFTWARE.
  34. */
  35. #include "I2C_DMAC.h"
  36. volatile I2C_DMAC* I2C_DMAC::i2cDmacPtrs[SERCOM_INST_NUM]; // Array of pointer to each instance (object) of this class
  37. volatile uint8_t I2C_DMAC::instanceCounter; // Number of instances (objects) of this class
  38. volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC write back descriptor array
  39. dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC channel descriptor array
  40. dmacdescriptor descriptor __attribute__ ((aligned (16))); // DMAC place holder descriptor
  41. //
  42. // Initialisation section: prep the the port pins, SERCOM and the DMAC
  43. //
  44. I2C_DMAC::I2C_DMAC(SERCOM* sercomClass, uint8_t pinSDA, uint8_t pinSCL) : // Constructor to initialise SERCOM, pins and DMAC channel member variables
  45. dmacWriteChannel(0), dmacReadChannel(1), dmacPriority(0)
  46. {
  47. this->pinSCL = pinSCL; // Copy the I2C pins
  48. this->pinSDA = pinSDA;
  49. if (sercomClass == &sercom0) // Use the sercom class to acquire the underlying SERCOMx peripheral
  50. {
  51. sercom = SERCOM0; // This is used to allow the variant.h and variant.cpp files to
  52. dmacWriteTrigger = SERCOM0_DMAC_ID_TX; // specify the sercom peripheral and the SCL/SDA pins used, just like
  53. dmacReadTrigger = SERCOM0_DMAC_ID_RX; // the Wire library
  54. #ifdef __SAMD51__
  55. genericClockId = SERCOM0_GCLK_ID_CORE;
  56. nvicId = SERCOM0_3_IRQn;
  57. #else
  58. genericClockId = GCLK_CLKCTRL_ID_SERCOM0_CORE;
  59. nvicId = SERCOM0_IRQn;
  60. #endif
  61. }
  62. else if (sercomClass == &sercom1) // The DMAC read and write trigger source as well as the generic clock id
  63. { // and sercom's NVIC interrupt id are also copied
  64. sercom = SERCOM1;
  65. dmacWriteTrigger = SERCOM1_DMAC_ID_TX;
  66. dmacReadTrigger = SERCOM1_DMAC_ID_RX;
  67. #ifdef __SAMD51__
  68. genericClockId = SERCOM1_GCLK_ID_CORE;
  69. nvicId = SERCOM1_3_IRQn;
  70. #else
  71. genericClockId = GCLK_CLKCTRL_ID_SERCOM1_CORE;
  72. nvicId = SERCOM1_IRQn;
  73. #endif
  74. }
  75. else if (sercomClass == &sercom2)
  76. {
  77. sercom = SERCOM2;
  78. dmacWriteTrigger = SERCOM2_DMAC_ID_TX;
  79. dmacReadTrigger = SERCOM2_DMAC_ID_RX;
  80. #ifdef __SAMD51__
  81. genericClockId = SERCOM2_GCLK_ID_CORE;
  82. nvicId = SERCOM2_3_IRQn;
  83. #else
  84. genericClockId = GCLK_CLKCTRL_ID_SERCOM2_CORE;
  85. nvicId = SERCOM2_IRQn;
  86. #endif
  87. }
  88. else if (sercomClass == &sercom3)
  89. {
  90. sercom = SERCOM3;
  91. dmacWriteTrigger = SERCOM3_DMAC_ID_TX;
  92. dmacReadTrigger = SERCOM3_DMAC_ID_RX;
  93. #ifdef __SAMD51__
  94. genericClockId = SERCOM3_GCLK_ID_CORE;
  95. nvicId = SERCOM3_3_IRQn;
  96. #else
  97. genericClockId = GCLK_CLKCTRL_ID_SERCOM3_CORE;
  98. nvicId = SERCOM3_IRQn;
  99. #endif
  100. }
  101. else if (sercomClass == &sercom4)
  102. {
  103. sercom = SERCOM4;
  104. dmacWriteTrigger = SERCOM4_DMAC_ID_TX;
  105. dmacReadTrigger = SERCOM4_DMAC_ID_RX;
  106. #ifdef __SAMD51__
  107. genericClockId = SERCOM4_GCLK_ID_CORE;
  108. nvicId = SERCOM4_3_IRQn;
  109. #else
  110. genericClockId = GCLK_CLKCTRL_ID_SERCOM4_CORE;
  111. nvicId = SERCOM4_IRQn;
  112. #endif
  113. }
  114. else if (sercomClass == &sercom5)
  115. {
  116. sercom = SERCOM5;
  117. dmacWriteTrigger = SERCOM5_DMAC_ID_TX;
  118. dmacReadTrigger = SERCOM5_DMAC_ID_RX;
  119. #ifdef __SAMD51__
  120. genericClockId = SERCOM5_GCLK_ID_CORE;
  121. nvicId = SERCOM5_3_IRQn;
  122. #else
  123. genericClockId = GCLK_CLKCTRL_ID_SERCOM5_CORE;
  124. nvicId = SERCOM5_IRQn;
  125. #endif
  126. }
  127. }
  128. 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
  129. {
  130. if (ulPeripheral != PIO_SERCOM && ulPeripheral != PIO_SERCOM_ALT) // Check that the peripheral mutliplexer is set to a SERCOM or SERCOM_ALT
  131. {
  132. return;
  133. }
  134. this->regAddrMode = regAddrMode;
  135. // Enable the SCL and SDA pins on the sercom: includes increased driver strength, pull-up resistors and pin multiplexer
  136. PORT->Group[g_APinDescription[pinSCL].ulPort].PINCFG[g_APinDescription[pinSCL].ulPin].reg =
  137. PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
  138. PORT->Group[g_APinDescription[pinSDA].ulPort].PINCFG[g_APinDescription[pinSDA].ulPin].reg =
  139. PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
  140. PORT->Group[g_APinDescription[pinSDA].ulPort].PMUX[g_APinDescription[pinSDA].ulPin >> 1].reg =
  141. PORT_PMUX_PMUXO(ulPeripheral) | PORT_PMUX_PMUXE(ulPeripheral);
  142. if (!DMAC->CTRL.bit.DMAENABLE) // Enable the DMAC, if it hasn't already been enabled
  143. {
  144. #ifdef __SAMD51__
  145. for (uint8_t i = 0; i < 5; i++) // Iterate through the SAMD51's 5 DMAC interrupt channels
  146. {
  147. NVIC_ClearPendingIRQ((IRQn_Type)(DMAC_0_IRQn + i)); // Clear any pending DMAC interrupts
  148. NVIC_SetPriority((IRQn_Type)(DMAC_0_IRQn + i), 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for the DMAC to 0 (highest)
  149. NVIC_EnableIRQ((IRQn_Type)(DMAC_0_IRQn + i)); // Connect the DMAC to the Nested Vector Interrupt Controller (NVIC)
  150. }
  151. #else
  152. NVIC_ClearPendingIRQ(DMAC_IRQn); // Clear any pending DMAC interrupts
  153. NVIC_SetPriority(DMAC_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for the DMAC to 0 (highest)
  154. NVIC_EnableIRQ(DMAC_IRQn); // Connect the DMAC to the Nested Vector Interrupt Controller (NVIC)
  155. #endif
  156. DMAC->CTRL.bit.SWRST = 1; // Reset the DMAC
  157. while (DMAC->CTRL.bit.SWRST); // Wait for synchronization
  158. DMAC->BASEADDR.reg = (uint32_t)descriptor_section; // Set the DMAC descriptor base address
  159. DMAC->WRBADDR.reg = (uint32_t)wrb; // Set the DMAC descriptor write-back address
  160. DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf); // Enable the DMAC peripheral and enable priority levels
  161. }
  162. NVIC_ClearPendingIRQ(nvicId); // Clear any Nested Vector Interrupt Controller (NVIC) pending interrupts
  163. NVIC_SetPriority(nvicId, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for the selected sercom to 0 (highest)
  164. NVIC_EnableIRQ(nvicId); // Connect selected sercom to the Nested Vector Interrupt Controller (NVIC)
  165. #ifdef __SAMD51__
  166. // Enable GCLK1 (48MHz) on the selected sercom
  167. GCLK->PCHCTRL[genericClockId].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK1;
  168. #else
  169. // Enable GCLK0 (48MHz) on the selected sercom
  170. GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK0 to the selected sercom
  171. GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 as source
  172. GCLK_CLKCTRL_ID(genericClockId); // Select the selected sercom as destination
  173. while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
  174. #endif
  175. sercom->I2CM.CTRLA.bit.SWRST = 1; // Reset the SERCOM
  176. while (sercom->I2CM.CTRLA.bit.SWRST || sercom->I2CM.SYNCBUSY.bit.SWRST); // Wait for synchronization
  177. sercom->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE(I2C_MASTER_OPERATION); // Set I2C master mode
  178. sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN; // Enable Smart Mode
  179. sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / (2 * baudrate) - 7; // Set I2C master SCL baud rate
  180. sercom->I2CM.CTRLA.bit.ENABLE = 1 ; // Enable SERCOM in I2C master mode
  181. while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization
  182. sercom->I2CM.STATUS.bit.BUSSTATE = 0x01; // Set the I2C bus to IDLE state
  183. while (sercom->I2CM.SYNCBUSY.bit.SYSOP); // Wait for synchronization
  184. sercom->I2CM.INTENSET.bit.ERROR = 1; // Enable SERCOM ERROR interrupts
  185. i2cDmacPtrs[instanceCounter++] = this; // Copy the pointer to "this" object to the I2C_DMAC pointer array
  186. } // and increment the instance counter
  187. //
  188. // Tear down and tidy up resources
  189. //
  190. void I2C_DMAC::end()
  191. {
  192. for (uint8_t i = 0; i < instanceCounter; i++) // Iterate through each I2C_DMAC instance
  193. {
  194. if (i2cDmacPtrs[i] == this) // Find the location of "this" instance in the I2C_DMAC array pointer
  195. {
  196. i2cDmacPtrs[i] = 0; // Pull this instance pointer from the array
  197. if (i < instanceCounter - 1) // If the current instance not the last instance
  198. {
  199. for (uint8_t j = i; j < instanceCounter - 1; j++)
  200. {
  201. i2cDmacPtrs[j] = i2cDmacPtrs[j + 1]; // Shift the I2C_DMAC pointer down the array
  202. }
  203. i2cDmacPtrs[instanceCounter - 1] = 0; // Set the last instance pointer to NULL
  204. }
  205. instanceCounter--; // Decrement the instance counter
  206. break; // Escape from the (for) loop
  207. }
  208. }
  209. // Return the SCL and SDA lines on the sercom to GPIO
  210. PORT->Group[g_APinDescription[pinSCL].ulPort].PINCFG[g_APinDescription[pinSCL].ulPin].reg = 0;
  211. PORT->Group[g_APinDescription[pinSDA].ulPort].PINCFG[g_APinDescription[pinSDA].ulPin].reg = 0;
  212. PORT->Group[g_APinDescription[pinSDA].ulPort].PMUX[g_APinDescription[pinSDA].ulPin >> 1].reg =
  213. PORT_PMUX_PMUXO(0) | PORT_PMUX_PMUXE(0);
  214. sercom->I2CM.CTRLA.bit.ENABLE = 0; // Disable the I2C master mode
  215. while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization
  216. sercom->I2CM.CTRLA.bit.SWRST = 1; // Reset SERCOM3
  217. while (sercom->I2CM.CTRLA.bit.SWRST || sercom->I2CM.SYNCBUSY.bit.SWRST); // Wait for synchronization
  218. #ifdef __SAMD51__
  219. // Disable GCLK1 (48Mhz) on the selected sercom
  220. GCLK->PCHCTRL[genericClockId].reg = /*GCLK_PCHCTRL_CHEN |*/ GCLK_PCHCTRL_GEN_GCLK1;
  221. #else
  222. // Disable GCLK0 (48MHz) on the selected sercom
  223. GCLK->CLKCTRL.reg = /*GCLK_CLKCTRL_CLKEN |*/ // Disable GCLK0 to the selected sercom - intentionally commented out
  224. GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 as source
  225. GCLK_CLKCTRL_ID(genericClockId); // Select the selected sercom as destination
  226. while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
  227. #endif
  228. if (instanceCounter == 0)
  229. {
  230. NVIC_DisableIRQ(nvicId); // Disconnect selected sercom from the Nested Vector Interrupt Controller (NVIC)
  231. DMAC->CTRL.bit.DMAENABLE = 0; // Disable the DMAC
  232. while(DMAC->CTRL.bit.DMAENABLE); // Wait for synchronization
  233. DMAC->CTRL.bit.SWRST = 1; // Reset the DMAC
  234. while (DMAC->CTRL.bit.SWRST); // Wait for synchronization
  235. #ifdef __SAMD51__
  236. for (uint8_t i = 0; i < 5; i++) // Iterate through the SAMD51's 5 DMAC interrupt channels
  237. {
  238. NVIC_DisableIRQ((IRQn_Type)(DMAC_0_IRQn + i)); // Disconnect the DMAC from the Nested Vector Interrupt Controller (NVIC)
  239. }
  240. #else
  241. NVIC_DisableIRQ(DMAC_IRQn); // Disconnect the DMAC from the Nested Vector Interrupt Controller (NVIC)
  242. #endif
  243. }
  244. }
  245. void I2C_DMAC::setClock(uint32_t baudrate)
  246. {
  247. sercom->I2CM.CTRLA.bit.ENABLE = 0; // Disable SERCOM3 in I2C master mode
  248. while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization
  249. sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / (2 * baudrate) - 7; // Set I2C master SCL baud rate
  250. sercom->I2CM.CTRLA.bit.ENABLE = 1 ; // Enable SERCOM3 in I2C master mode
  251. while (sercom->I2CM.SYNCBUSY.bit.ENABLE); // Wait for synchronization
  252. }
  253. void I2C_DMAC::setWriteChannel(uint8_t channel)
  254. {
  255. dmacWriteChannel = channel < DMAC_CH_NUM ? channel : dmacWriteChannel; // Set the write DMAC channel, (default channel 0)
  256. }
  257. void I2C_DMAC::setReadChannel(uint8_t channel)
  258. {
  259. dmacReadChannel = channel < DMAC_CH_NUM ? channel : dmacReadChannel; // Set the read DMAC channel, (default channel 1)
  260. }
  261. void I2C_DMAC::setPriority(uint8_t priority)
  262. {
  263. dmacPriority = priority < DMAC_LVL_NUM ? priority : dmacPriority; // Set the priority of both write and read channels (0 lowest, 3 highest)
  264. }
  265. //
  266. // DMAC Section: Load the DMAC's transfer descriptors
  267. //
  268. void I2C_DMAC::setRegAddrMode(uint8_t regAddrMode)
  269. {
  270. this->regAddrMode = regAddrMode; // Set the register address mode: REG_ADDR_8BIT or REG_ADDR_16BIT
  271. }
  272. // Generic initialise write DMAC transfer function
  273. void I2C_DMAC::initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count, uint8_t regAddrLength)
  274. {
  275. while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY
  276. if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE
  277. {
  278. this->devAddress = devAddress; // Copy the device address, plus write byte count and register address length
  279. writeCount = count;
  280. this->regAddrLength = regAddrLength;
  281. #ifdef __SAMD51__
  282. // Set the DMAC level, trigger source and trigger action to burst (trigger for every byte transmitted)
  283. DMAC->Channel[dmacWriteChannel].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(dmacWriteTrigger) | DMAC_CHCTRLA_TRIGACT_BURST;
  284. DMAC->Channel[dmacWriteChannel].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL(dmacPriority); // Set the channel priority level
  285. DMAC->Channel[dmacWriteChannel].CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR
  286. #else
  287. DMAC->CHID.reg = DMAC_CHID_ID(dmacWriteChannel); // Activate specified DMAC write channel
  288. // Set the DMAC level, trigger source and trigger action to beat (trigger for every byte transmitted)
  289. DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(dmacPriority) | DMAC_CHCTRLB_TRIGSRC(dmacWriteTrigger) | DMAC_CHCTRLB_TRIGACT_BEAT;
  290. DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR
  291. #endif
  292. if (regAddrLength == 0) // Writing data only
  293. {
  294. descriptor.descaddr = 0; // Set this to the last descriptor (no linked list)
  295. descriptor.srcaddr = (uint32_t)data + count; // Set the source address
  296. descriptor.dstaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the destination address
  297. descriptor.btcnt = count; // Number of data bytes to transmit
  298. descriptor.btctrl = DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID; // Increment source address on BEAT transfer and validate descriptor
  299. memcpy(&descriptor_section[dmacWriteChannel], &descriptor, sizeof(dmacdescriptor)); // Copy the descriptor into SRAM descriptor array
  300. return; // Data only initialisation complete
  301. }
  302. else if (regAddrLength == 1) // 8-bit write address mode
  303. {
  304. this->regAddress[0] = (uint8_t)regAddress; // Copy the 8-bit register address
  305. }
  306. else // 16-bit write address mode
  307. {
  308. this->regAddress[0] = (uint8_t)(regAddress >> 8); // Copy the 16-bit register address MSB
  309. this->regAddress[1] = (uint8_t)(regAddress & 0xFF); // Copy the 16-bit register address LSB
  310. }
  311. descriptor.descaddr = count > 0 ? (uint32_t)&linked_descriptor : 0; // Link to next descriptor if there's data
  312. descriptor.srcaddr = (uint32_t)this->regAddress + regAddrLength; // Set the source address
  313. descriptor.dstaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the destination address
  314. descriptor.btcnt = regAddrLength; // Size of the register address in bytes
  315. descriptor.btctrl = DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID; // Increment source address on BEAT transfer and validate descriptor
  316. memcpy(&descriptor_section[dmacWriteChannel], &descriptor, sizeof(dmacdescriptor)); // Copy the descriptor into SRAM descriptor array
  317. if (count > 0) // Append write data as linked descriptor
  318. {
  319. linked_descriptor.descaddr = 0; // Set linked_descriptor to last in the list
  320. linked_descriptor.srcaddr = (uint32_t)data + count; // Set the source address
  321. linked_descriptor.dstaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the destination address
  322. linked_descriptor.btcnt = count; // Number of data bytes to transmit
  323. linked_descriptor.btctrl = DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_VALID; // Increment source address on BEAT transfer and validate descriptor
  324. }
  325. }
  326. }
  327. //
  328. // Base (1st) layer functions - Independent DMAC initialisation with separate read/write
  329. //
  330. void I2C_DMAC::initWriteBytes(uint8_t devAddress, uint8_t* data, uint8_t count)
  331. {
  332. initWriteBytes(devAddress, 0, data, count, 0); // Initialise DMAC write transfer: data only, no register address
  333. }
  334. void I2C_DMAC::initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count)
  335. {
  336. initWriteBytes(devAddress, regAddress, data, count, regAddrMode); // Initialise DMAC write transfer: register address + data
  337. }
  338. void I2C_DMAC::initWriteByte(uint8_t devAddress, uint16_t regAddress, uint8_t data)
  339. {
  340. while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY
  341. if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE
  342. {
  343. this->data = data;
  344. initWriteBytes(devAddress, regAddress, (uint8_t*)&this->data, 1, regAddrMode); // Initialise DMAC write transfer: register address + 1 data byte
  345. }
  346. }
  347. void I2C_DMAC::initWriteRegAddr(uint8_t devAddress, uint16_t regAddress)
  348. {
  349. initWriteBytes(devAddress, regAddress, 0, 0, regAddrMode); // Initialise DMAC write transfer: register address only, no data
  350. }
  351. uint8_t I2C_DMAC::getData()
  352. {
  353. return data; // Return the received data byte
  354. }
  355. void I2C_DMAC::initReadBytes(uint8_t devAddress, uint8_t* data, uint8_t count) // Initialise DMAC read transfer: count bytes of data
  356. {
  357. while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY
  358. if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE
  359. {
  360. this->devAddress = devAddress; // Copy device address and read byte count
  361. readCount = count;
  362. #ifdef __SAMD51__
  363. // Set the DMAC level, trigger source and trigger action to burst (trigger for every byte received)
  364. DMAC->Channel[dmacReadChannel].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(dmacReadTrigger) | DMAC_CHCTRLA_TRIGACT_BURST;
  365. DMAC->Channel[dmacReadChannel].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL(dmacPriority); // Set the channel priority level
  366. DMAC->Channel[dmacReadChannel].CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR
  367. #else
  368. DMAC->CHID.reg = DMAC_CHID_ID(dmacReadChannel); // Activate the specified DMAC channel
  369. // Set the DMAC level, trigger source and trigger action to beat (trigger for every byte received)
  370. DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(dmacPriority) | DMAC_CHCTRLB_TRIGSRC(dmacReadTrigger) | DMAC_CHCTRLB_TRIGACT_BEAT;
  371. DMAC->CHINTENSET.reg = DMAC_CHINTENSET_MASK; // Enable all 3 interrupts: SUSP, TCMPL and TERR
  372. #endif
  373. descriptor.descaddr = 0; // Single descriptor (no linked list)
  374. descriptor.srcaddr = (uint32_t)&sercom->I2CM.DATA.reg; // Set the source address
  375. descriptor.dstaddr = (uint32_t)data + count; // Set the destination address
  376. descriptor.btcnt = count; // Number of data bytes to receive
  377. descriptor.btctrl = DMAC_BTCTRL_DSTINC | DMAC_BTCTRL_VALID; // Increment destination address on BEAT transfer and validate descriptor
  378. memcpy(&descriptor_section[dmacReadChannel], &descriptor, sizeof(dmacdescriptor)); // Copy the descriptor into SRAM descriptor array
  379. }
  380. }
  381. void I2C_DMAC::initReadByte(uint8_t devAddress)
  382. {
  383. this->devAddress = devAddress;
  384. initReadBytes(devAddress, &data, 1); // Initialise DMAC read transfer: 1 byte of data
  385. }
  386. void I2C_DMAC::write() // Initiate DMAC write transmission on the I2C bus
  387. {
  388. while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY
  389. if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE
  390. {
  391. writeBusy = true;
  392. #ifdef __SAMD51__
  393. DMAC->Channel[dmacWriteChannel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC write channel
  394. #else
  395. DMAC->CHID.reg = DMAC_CHID_ID(dmacWriteChannel); // Activate the DMAC write channel
  396. DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC write channel
  397. #endif
  398. sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_LEN(writeCount + regAddrLength) | // Load the device address into the SERCOM3 ADDR register
  399. SERCOM_I2CM_ADDR_LENEN |
  400. SERCOM_I2CM_ADDR_ADDR(devAddress << 1 | WRITE);
  401. while (sercom->I2CM.SYNCBUSY.bit.SYSOP); // Wait for synchronization
  402. }
  403. }
  404. void I2C_DMAC::read() // Initiate DMAC read transmission on the I2C bus
  405. {
  406. while (sercom->I2CM.STATUS.bit.BUSSTATE == 0x2); // Wait while the I2C bus is BUSY
  407. if (sercom->I2CM.STATUS.bit.BUSSTATE == 0x1) // Check if the I2C bus state is at IDLE
  408. {
  409. readBusy = true;
  410. #ifdef __SAMD51__
  411. DMAC->Channel[dmacReadChannel].CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC read channel
  412. #else
  413. DMAC->CHID.reg = DMAC_CHID_ID(dmacReadChannel); // Activate the DMAC read channel
  414. DMAC->CHCTRLA.reg |= DMAC_CHCTRLA_ENABLE; // Enable the DMAC read channel
  415. #endif
  416. sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_LEN(readCount) | // Load the I2C slave device address into the SERCOM3 ADDR register
  417. SERCOM_I2CM_ADDR_LENEN |
  418. SERCOM_I2CM_ADDR_ADDR(devAddress << 1 | READ);
  419. while (sercom->I2CM.SYNCBUSY.bit.SYSOP); // Wait for synchronization
  420. }
  421. }
  422. //
  423. // 2nd layer functions - Combined DMAC initialisation with read or write
  424. //
  425. void I2C_DMAC::writeBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count)
  426. {
  427. initWriteBytes(devAddress, regAddress, data, count); // Initialise DMAC write transfer: register address + data
  428. write(); // Transmit the register address + data
  429. }
  430. void I2C_DMAC::writeByte(uint8_t devAddress, uint16_t regAddress, uint8_t data)
  431. {
  432. initWriteByte(devAddress, regAddress, data); // Initialise DMAC write transfer: register address + 1 byte data
  433. write(); // Transmit the register address + data
  434. }
  435. void I2C_DMAC::writeRegAddr(uint8_t devAddress, uint16_t regAddress)
  436. {
  437. initWriteRegAddr(devAddress, regAddress); // Initialise DMAC write transfer: register address, no data
  438. write(); // Transmit the register address
  439. }
  440. void I2C_DMAC::readBytes(uint8_t devAddress, uint8_t* data, uint8_t count)
  441. {
  442. initReadBytes(devAddress, data, count); // Initialise DMAC read transfer: data
  443. read(); // Receive the data
  444. }
  445. void I2C_DMAC::readByte(uint8_t devAddress)
  446. {
  447. initReadByte(devAddress); // Initialise DMAC read transfer: 1 byte data
  448. read(); // Receive the data
  449. }
  450. //
  451. // 3rd layer functions - Combined DMAC initialisation with read and write
  452. //
  453. void I2C_DMAC::readBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count)
  454. {
  455. writeRegAddr(devAddress, regAddress); // Set the register address on the I2C slave device
  456. while(writeBusy); // Wait for the write to complete
  457. readBytes(devAddress, data, count); // Receive the returned data
  458. }
  459. void I2C_DMAC::readByte(uint8_t devAddress, uint16_t regAddress)
  460. {
  461. writeRegAddr(devAddress, regAddress); // Set the register address on the I2C slave device
  462. while(writeBusy); // Wait for the write to complete
  463. readByte(devAddress); // Receive the returned data byte
  464. }
  465. //
  466. // DMAC Interrupt Handler: Busy Flag and Callback Section
  467. //
  468. void I2C_DMAC::attachWriteCallback(voidFuncPtr callback)
  469. {
  470. writeCallback = callback; // Attach a write complete callback function
  471. }
  472. void I2C_DMAC::attachReadCallback(voidFuncPtr callback)
  473. {
  474. readCallback = callback; // Attach a read complete callback function
  475. }
  476. void I2C_DMAC::attachDmacErrorCallback(voidFuncPtr callback)
  477. {
  478. errorDmacCallback = callback; // Attach a DMAC error callback function
  479. }
  480. void I2C_DMAC::attachSercomErrorCallback(voidFuncPtr callback)
  481. {
  482. errorSercomCallback = callback; // Attach a SERCOM error callback function
  483. }
  484. void I2C_DMAC::detachWriteCallback()
  485. {
  486. writeCallback = 0; // Detach the write complete callback function
  487. }
  488. void I2C_DMAC::detachReadCallback()
  489. {
  490. readCallback = 0; // Detach the read complete callback function
  491. }
  492. void I2C_DMAC::detachDmacErrorCallback()
  493. {
  494. errorDmacCallback = 0; // Detach the DMAC error callback function
  495. }
  496. void I2C_DMAC::detachSercomErrorCallback()
  497. {
  498. errorSercomCallback = 0; // Detach the SERCOM error callback function
  499. }
  500. void I2C_DMAC::DMAC_IrqHandler()
  501. {
  502. //__disable_irq(); // Disable interrupts
  503. volatile I2C_DMAC* i2cDmacPtr = i2cDmacPtrs[0]; // Set I2C_DMAC pointer to the first I2C_DMAC instance
  504. uint8_t activeChannel = DMAC->INTPEND.reg & DMAC_INTPEND_ID_Msk; // Get DMAC channel number
  505. if (instanceCounter > 1) // If there's only one instance of the I2C_DMAC object,
  506. { // skip finding the active channel instance
  507. for (uint8_t i = 0; i < instanceCounter; i++) // Find the I2C_DMAC instance from the active channel
  508. {
  509. if (activeChannel == i2cDmacPtrs[i]->dmacWriteChannel || // Compare the active channel against the object's
  510. activeChannel == i2cDmacPtrs[i]->dmacReadChannel) //
  511. {
  512. i2cDmacPtr = i2cDmacPtrs[i]; // Assign I2C_DMAC pointer to the object instance with
  513. } // the active channel
  514. }
  515. }
  516. #ifdef __SAMD51__
  517. if (DMAC->Channel[activeChannel].CHINTFLAG.bit.TERR) // DMAC Transfer error (TERR)
  518. #else
  519. DMAC->CHID.reg = DMAC_CHID_ID(activeChannel); // Switch to the active DMAC channel
  520. if (DMAC->CHINTFLAG.bit.TERR) // DMAC Transfer error (TERR)
  521. #endif
  522. {
  523. if (i2cDmacPtr->errorDmacCallback) // Check if there's a DMAC error callback function
  524. {
  525. i2cDmacPtr->errorDmacCallback(); // Call the callback function
  526. }
  527. }
  528. #ifdef __SAMD51__
  529. else if (DMAC->Channel[activeChannel].CHINTFLAG.bit.TCMPL) // DMAC transfer complete (TCMPL)
  530. #else
  531. else if (DMAC->CHINTFLAG.bit.TCMPL) // DMAC transfer complete (TCMPL)
  532. #endif
  533. {
  534. if (activeChannel == i2cDmacPtr->dmacWriteChannel) // Check write DMAC channel
  535. {
  536. i2cDmacPtr->writeBusy = false; // Clear the write busy flag
  537. if (i2cDmacPtr->writeCallback) // Check if there's a write callback function
  538. {
  539. i2cDmacPtr->writeCallback(); // Call the write callback function
  540. }
  541. }
  542. else if (activeChannel == i2cDmacPtr->dmacReadChannel) // Check read DMAC channel
  543. {
  544. i2cDmacPtr->readBusy = false; // Clear the read busy flag
  545. if (i2cDmacPtr->readCallback) // Check if there's a read callback function
  546. {
  547. i2cDmacPtr->readCallback(); // Call the read callback function
  548. }
  549. }
  550. }
  551. #ifdef __SAMD51__
  552. DMAC->Channel[activeChannel].CHINTFLAG.reg = DMAC_CHINTFLAG_MASK; // Clear the DMAC channel interrupt flags
  553. #else
  554. DMAC->CHINTFLAG.reg = DMAC_CHINTFLAG_MASK; // Clear the DMAC channel interrupt flags
  555. #endif
  556. //__enable_irq(); // Enable interrupts
  557. }
  558. void I2C_DMAC::SERCOM_IrqHandler()
  559. {
  560. if (sercom->I2CM.INTFLAG.bit.ERROR && sercom->I2CM.INTENSET.bit.ERROR)
  561. {
  562. #ifdef __SAMD51__
  563. DMAC->Channel[dmacWriteChannel].CHCTRLA.bit.ENABLE = 0; // Disable the DMAC write channel
  564. DMAC->Channel[dmacReadChannel].CHCTRLA.bit.ENABLE = 0; // Disable the DMAC read channel
  565. #else
  566. DMAC->CHID.reg = DMAC_CHID_ID(dmacWriteChannel); // Switch to the active DMAC write channel
  567. DMAC->CHCTRLA.bit.ENABLE = 0; // Disable the DMAC write channel
  568. DMAC->CHID.reg = DMAC_CHID_ID(dmacReadChannel); // Switch to the active DMAC read channel
  569. DMAC->CHCTRLA.bit.ENABLE = 0; // Disable the DMAC read channel
  570. #endif
  571. writeBusy = false; // Clear the write busy flag
  572. readBusy = false; // Clear the read busy flag
  573. if (errorSercomCallback) // Check if there's a SERCOM3 error callback function
  574. {
  575. errorSercomCallback(); // Call the SERCOM3 error callback function
  576. }
  577. sercom->I2CM.STATUS.reg |= SERCOM_I2CM_STATUS_LENERR | // Clear the status register flags - cleared by
  578. SERCOM_I2CM_STATUS_BUSERR; // writing to SERCOM3 ADDR.ADDR register anyway
  579. sercom->I2CM.INTFLAG.bit.ERROR = 1; // Clear the SERCOM error interrupt flag
  580. }
  581. }
  582. #ifdef __SAMD51__
  583. void DMAC_0_Handler() __attribute__((weak)); // Set as weakly declared linker symbol, so that the function can be overriden
  584. void DMAC_0_Handler() // The DMAC_0_Handler() ISR
  585. {
  586. I2C_DMAC::DMAC_IrqHandler(); // Call the I2C_DMAC's DMAC interrupt handler member function
  587. }
  588. // Set the DMAC Handler function for the other DMAC channels, assign to weak aliases
  589. void DMAC_1_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
  590. void DMAC_2_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
  591. void DMAC_3_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
  592. void DMAC_4_Handler(void) __attribute__((weak, alias("DMAC_0_Handler")));
  593. #else
  594. void DMAC_Handler() __attribute__((weak)); // Set as weakly declared linker symbol, so that the function can be overriden
  595. void DMAC_Handler() // The DMAC_Handler() ISR
  596. {
  597. I2C_DMAC::DMAC_IrqHandler(); // Call the I2C_DMAC's DMAC interrupt handler member function
  598. }
  599. #endif
  600. #if WIRE_INTERFACES_COUNT > 0 //
  601. /* In case new variant doesn't define these macros,
  602. * we put here the ones for Arduino Zero.
  603. *
  604. * These values should be different on some variants!
  605. */
  606. #ifndef PERIPH_WIRE
  607. #define PERIPH_WIRE sercom3
  608. #ifdef __SAMD51__
  609. #define WIRE_IT_HANDLER SERCOM3_3_Handler
  610. #else
  611. #define WIRE_IT_HANDLER SERCOM3_Handler
  612. #endif
  613. #endif // PERIPH_WIRE
  614. I2C_DMAC I2C(&PERIPH_WIRE, PIN_WIRE_SDA, PIN_WIRE_SCL); // Instantiate the I2C object
  615. void WIRE_IT_HANDLER() __attribute__((weak));
  616. void WIRE_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function
  617. {
  618. I2C.SERCOM_IrqHandler();
  619. }
  620. #endif
  621. #if WIRE_INTERFACES_COUNT > 1
  622. I2C_DMAC I2C1(&PERIPH_WIRE1, PIN_WIRE1_SDA, PIN_WIRE1_SCL);
  623. void WIRE1_IT_HANDLER() __attribute__((weak));
  624. void WIRE1_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function
  625. {
  626. I2C1.SERCOM_IrqHandler();
  627. }
  628. #endif
  629. #if WIRE_INTERFACES_COUNT > 2
  630. I2C_DMAC I2C2(&PERIPH_WIRE2, PIN_WIRE2_SDA, PIN_WIRE2_SCL);
  631. void WIRE2_IT_HANDLER() __attribute__((weak));
  632. void WIRE2_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function
  633. {
  634. I2C2.SERCOM_IrqHandler();
  635. }
  636. #endif
  637. #if WIRE_INTERFACES_COUNT > 3
  638. I2C_DMAC I2C3(&PERIPH_WIRE3, PIN_WIRE3_SDA, PIN_WIRE3_SCL);
  639. void WIRE3_IT_HANDLER() __attribute__((weak));
  640. void WIRE3_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function
  641. {
  642. I2C3.SERCOM_IrqHandler();
  643. }
  644. #endif
  645. #if WIRE_INTERFACES_COUNT > 4
  646. I2C_DMAC I2C4(&PERIPH_WIRE4, PIN_WIRE4_SDA, PIN_WIRE4_SCL);
  647. void WIRE4_IT_HANDLER() __attribute__((weak));
  648. void WIRE4_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function
  649. {
  650. I2C4.SERCOM_IrqHandler();
  651. }
  652. #endif
  653. #if WIRE_INTERFACES_COUNT > 5
  654. I2C_DMAC I2C5(&PERIPH_WIRE5, PIN_WIRE5_SDA, PIN_WIRE5_SCL);
  655. void WIRE5_IT_HANDLER() __attribute__((weak));
  656. void WIRE5_IT_HANDLER() // Call the I2C_DMAC's SERCOM interrupt handler member function
  657. {
  658. I2C5.SERCOM_IrqHandler();
  659. }
  660. #endif