@@ -0,0 +1,46 @@ | |||
/* | |||
DMAC descriptor configuration file. | |||
Copyright (C) Martin Lindupp 2018 | |||
V1.0.0 -- Initial release | |||
V1.0.1 -- Include SAMD51 support for 32 channels | |||
V1.0.2 -- Allow other classes to simultaneously use remaining DMAC channels | |||
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. | |||
*/ | |||
// DMAC descriptor configuration file | |||
#ifndef _DMAC_h | |||
#define _DMAC_h | |||
typedef struct { // DMAC descriptor structure | |||
uint16_t btctrl; | |||
uint16_t btcnt; | |||
uint32_t srcaddr; | |||
uint32_t dstaddr; | |||
uint32_t descaddr; | |||
} dmacdescriptor ; | |||
extern volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC write back descriptor array | |||
extern dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16))); // DMAC channel descriptor array | |||
extern dmacdescriptor descriptor __attribute__ ((aligned (16))); // DMAC place holder descriptor | |||
#endif |
@@ -0,0 +1,734 @@ | |||
/* | |||
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 |
@@ -0,0 +1,141 @@ | |||
/* | |||
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 <Arduino.h> | |||
#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 |
@@ -0,0 +1,284 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#include <TimeLib.h> | |||
#include "NoiseSensor.h" | |||
//#include "NoiseSensorNode.h" | |||
bool NoiseSensorClass::begin() { | |||
_PACKET_SIZE = ( _tick_count + LORA_HEADER_LENGTH + BATTERY_FIELD_SIZE ); | |||
_REQUEST_SIZE = ( PACKET_COUNT * _PACKET_SIZE * 2 ); // * 2, it's 16 bit. | |||
for (int i = 0; i < PACKET_COUNT; i++) { | |||
_nodes[i] = new NoiseSensorNode(i, _PACKET_SIZE * 2); | |||
} | |||
if (this->_tick_duration < 2) { | |||
Serial.println("--> Using a tick duration of less than two seconds. This " | |||
"will fail unless you've increased the MCUs SYSCLK!"); | |||
} | |||
if (this->_tick_count >= 100) { | |||
Serial.println( | |||
"--> Using a tick count of more than 100 gets dangerously close to the " | |||
"LoRa transmission limitations and is not supported as of now."); | |||
} else if (this->_tick_count < 10) { | |||
Serial.println( | |||
"--> Using a tick count smaller than 10 might screw things up in a " | |||
"unpredictable manner and is not recommended."); | |||
} | |||
if (this->_tick_count >= 120) { | |||
Serial.println( | |||
"--> Increased the tick count over 120. This results in I²C packages of " | |||
"(tick_count + 3 ) * 2. " | |||
"Note that I²C packets longer than 256 bytes will need to be split into " | |||
"smaller chunks to allow them to transfer." | |||
} | |||
if (!setupNTPClient()) { | |||
Serial.println("!!!-------------------------!!!"); | |||
Serial.println("NTPClient couldn't be started."); | |||
Serial.println("!!!-------------------------!!!"); | |||
return false; | |||
} | |||
I2C.begin(400000); | |||
this->sendSyncBeacon(); | |||
while (I2C.writeBusy) { | |||
delay(250); | |||
Serial.println("Writing!"); | |||
} | |||
this->_first_sync = 1; | |||
} | |||
bool NoiseSensorClass::begin(uint8_t tick_count) { | |||
this->_tick_count = tick_count; | |||
this->begin(); | |||
} | |||
bool NoiseSensorClass::begin(uint8_t tick_count, uint8_t tick_duration) { | |||
this->_tick_count = tick_count; | |||
this->_tick_duration = tick_duration; | |||
this->begin(); | |||
} | |||
bool NoiseSensorClass::begin(uint8_t tick_count, uint8_t tick_duration, | |||
uint8_t tx_offset) { | |||
this->_tick_count = tick_count; | |||
this->_tick_duration = tick_duration; | |||
this->_tx_offset = tx_offset; | |||
this->begin(); | |||
} | |||
void NoiseSensorClass::setIds(const char* sensebox_ids[], const char* sensor_ids[]) { | |||
for (int i = 0; i < PACKET_COUNT; i++) { | |||
char tmp[24] = {0}; | |||
_nodes[i]->sensebox_id = sensebox_ids[i]; | |||
_nodes[i]->sensor_id = sensor_ids[i]; | |||
} | |||
} | |||
bool NoiseSensorClass::beaconReady() { | |||
// if (this->_time_client->getEpochTime() - this->_last_sync_time == | |||
if (now() - _last_sync_time == | |||
(this->_tick_count * this->_tick_duration)) { | |||
Serial.println("---->Beacon ready"); | |||
return true; | |||
} | |||
return false; | |||
} | |||
bool NoiseSensorClass::requestReady() { | |||
// 1 second as tolerance | |||
if (!this->_first_sync && | |||
//this->_time_client->getEpochTime() - this->_last_sync_time == | |||
now() - _last_sync_time == | |||
(5 * this->_tx_offset * this->_tick_duration + 1)) { | |||
Serial.println("---->Request ready"); | |||
return true; | |||
} | |||
return false; | |||
} | |||
bool NoiseSensorClass::read() { | |||
Serial.println("Starting read"); | |||
for (int pkt = 0; pkt < PACKET_COUNT; pkt++) { | |||
// Calculate the time of the sync message that triggered this packet. | |||
uint32_t time_offset = (_tick_count * _tick_duration) + 1; // TODO Move | |||
this->_nodes[pkt]->_sync_message_ts = this->_last_sync_time - time_offset; | |||
Serial.print("Last sync time: "); | |||
Serial.println(_last_sync_time); | |||
Serial.print("Offset: "); | |||
Serial.println(time_offset); | |||
Serial.print("TS: "); | |||
Serial.println(_nodes[pkt]->_sync_message_ts); | |||
Serial.print(minute(_nodes[pkt]->_sync_message_ts)); | |||
Serial.print(":"); | |||
Serial.println(second(_nodes[pkt]->_sync_message_ts)); | |||
/* | |||
if (_nodes[pkt]->missing_answers > 0) { | |||
_nodes[pkt]->_sync_message_ts -= time_offset * _nodes[pkt]->missing_answers; | |||
} | |||
*/ | |||
I2C.readBytes(this->_i2c_address, (uint8_t *) this->_nodes[pkt]->_measurements, this->_PACKET_SIZE * 2); | |||
} | |||
uint32_t read_start_time = millis(); | |||
while (I2C.readBusy) { | |||
Serial.println("Reading!"); | |||
delay(1); | |||
// Restart I²C if no response for more than a second. | |||
if (millis() - read_start_time >= 1000) { | |||
Serial.println("Restarting I²C"); | |||
I2C.end(); | |||
I2C.begin(400000); | |||
return false; | |||
} | |||
} | |||
// Skip 0, it has no battery. | |||
for (int pkt = 1; pkt < PACKET_COUNT; pkt++) { | |||
// TODO Move this to a sane place (own function). | |||
if (_nodes[pkt]->_measurements[_PACKET_SIZE - 1]) { | |||
Serial.print("Battery low on device"); | |||
Serial.println(pkt); | |||
} | |||
} | |||
Serial.println("Reading done."); | |||
return true; | |||
} | |||
void NoiseSensorClass::printMeasurements(uint8_t node_id) { | |||
uint32_t ts = this->_nodes[node_id]->_sync_message_ts; | |||
char printbuf[32]; | |||
sprintf(printbuf, "- ID %u - ", node_id); | |||
Serial.println(printbuf); | |||
for (int i = 2; i < (this->_PACKET_SIZE - BATTERY_FIELD_SIZE); i++) { | |||
sprintf(printbuf, "%02i:%02i:%02i -> ", hour(ts), minute(ts), second(ts)); | |||
Serial.print(printbuf); | |||
Serial.println(this->_nodes[node_id]->_measurements[i]); | |||
ts += this->_tick_duration; | |||
} | |||
} | |||
bool NoiseSensorClass::sendSyncBeacon() { | |||
//_last_sync_time = _time_client->getEpochTime(); | |||
uint8_t setup_values[3]; | |||
setup_values[0] = this->_tick_count; | |||
setup_values[1] = this->_tick_duration; | |||
setup_values[2] = this->_tx_offset; | |||
Serial.println("Sent beacon"); | |||
I2C.writeBytes(this->_i2c_address, 0x01, (uint8_t *)setup_values, | |||
sizeof(setup_values)); | |||
this->_first_sync = 0; | |||
_last_sync_time = now(); | |||
Serial.println(_last_sync_time); | |||
Serial.print(minute(_last_sync_time)); | |||
Serial.print(":"); | |||
Serial.println(second(_last_sync_time)); | |||
while (I2C.writeBusy) { | |||
delay(250); | |||
Serial.println("Writing!"); | |||
} | |||
} | |||
String NoiseSensorClass::buildSenseBoxJSON(uint8_t device_id) { | |||
// _time_client->update(); | |||
NoiseSensorNode* node = _nodes[device_id]; | |||
uint32_t ts = node->_sync_message_ts; | |||
// Each measurement line is max. 94 chars long + 2 curly brackets. | |||
uint16_t bufsize = _tick_count * 94 + 2; | |||
char json_buf[bufsize] = {0}; | |||
char sensor_id[25] = {0}; | |||
node->sensor_id.toCharArray(sensor_id, 25); | |||
// Build a json string in a very comprehensible way... | |||
strcat(json_buf, "[\n"); | |||
for (int i = 2; i < (_PACKET_SIZE - BATTERY_FIELD_SIZE); i++) { | |||
char tmpbuf[1024] = {0}; | |||
char timestamp[24] = {0}; | |||
float m = node->_measurements[i] / 10.0; // From fixed point to float. | |||
if (m >= 29 && m <= 120) { | |||
sprintf(timestamp, "%i-%02i-%02iT%02i:%02i:%02iZ", year(ts), month(ts), | |||
day(ts), hour(ts), minute(ts), second(ts)); | |||
sprintf(tmpbuf, "{\"sensor\":\"%s\", \"value\":\"%.1f\", \"createdAt\":\"%s\"}", sensor_id, m, timestamp); | |||
// No trailing comma on the last entry. | |||
if (i != _PACKET_SIZE - BATTERY_FIELD_SIZE - 1) { | |||
strcat(tmpbuf, ","); | |||
} | |||
strcat(json_buf, tmpbuf); | |||
strcat(json_buf, "\n"); | |||
} | |||
ts += this->_tick_duration; | |||
} | |||
/* | |||
if (strlen(json_buf) > 2) { | |||
node->missing_answers = 0; | |||
Serial.println("Missing answers reset."); | |||
} else { | |||
node->missing_answers++; | |||
Serial.print("Missing answers: "); | |||
Serial.println(node->missing_answers); | |||
} | |||
*/ | |||
strcat(json_buf, "]"); | |||
return json_buf; | |||
} | |||
String NoiseSensorClass::buildHTTPHeader(uint8_t device_id, const char* server, uint16_t content_length) { | |||
char sb_id[25] = {0}; | |||
_nodes[device_id]->sensebox_id.toCharArray(sb_id, 25); | |||
char uploadbuf[2048] = {0}; | |||
sprintf(uploadbuf, "POST /boxes/%s/data HTTP/1.1\n" | |||
"Host: %s\n" | |||
"content-type: application/json\n" | |||
"Connection: close\n" | |||
"Content-Length: %u", sb_id, server, content_length); | |||
return String(uploadbuf); | |||
} | |||
// Private functions | |||
bool NoiseSensorClass::setupNTPClient() { | |||
uint32_t start_time = millis(); | |||
while (timeStatus() != timeSet) { | |||
if (millis() - start_time >= 200) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
//setSyncProvider( | |||
//WiFiUDP ntpUDP; | |||
//this->_time_client = new NTPClient(ntpUDP); | |||
//this->_time_client->begin(); | |||
//if (this->_time_client->update()) { return true; } | |||
} | |||
NoiseSensorClass NoiseSensor; |
@@ -0,0 +1,90 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#include "I2C_DMAC.h" | |||
#include "NoiseSensorNode.h" | |||
//#include <NTPClient.h> | |||
#include <WiFiUdp.h> | |||
#define LORA_HEADER_LENGTH 2 | |||
#define BATTERY_FIELD_SIZE 1 | |||
#define PACKET_COUNT 5 | |||
class NoiseSensorClass { | |||
private: | |||
//NTPClient* _time_client; | |||
uint32_t _last_sync_time; | |||
uint8_t _tick_count = 20; | |||
uint8_t _tick_duration = 2; // in seconds | |||
uint8_t _tx_offset = 1; | |||
uint8_t _i2c_address = 0x3C; | |||
bool _first_sync = true; | |||
// These aren't _real_ const but they shouldn't be redefined nonetheless. | |||
uint16_t _PACKET_SIZE; | |||
uint16_t _REQUEST_SIZE; | |||
NoiseSensorNode* _nodes[]; | |||
uint8_t getDeviceID(); | |||
bool setupNTPClient(); | |||
public: | |||
/** | |||
* Begin the measurement cycle by sending a sync message. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(); | |||
/** | |||
* Call begin() with a custom tick count. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(uint8_t tick_count); | |||
/** | |||
* Call begin() with a custom tick count and tick duration. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(uint8_t tick_count, uint8_t tick_duration); | |||
/** | |||
* Call begin() with a custom tick count, tick duration and tx_offset. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(uint8_t tick_count, uint8_t tick_duration, uint8_t tx_offset); | |||
void setIds(const char* sensebox_ids[], const char* sensor_ids[]); | |||
String buildHTTPHeader(uint8_t device_id, const char* server, uint16_t content_length); | |||
bool beaconReady(); | |||
bool requestReady(); | |||
/** | |||
* Read the newly available data from the slave. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool read(); | |||
/** | |||
* Print the measurements in a pretty format. | |||
*/ | |||
void printMeasurements(uint8_t device_id); | |||
/** | |||
* Send the sync message to the slave. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool sendSyncBeacon(); | |||
String buildSenseBoxJSON(uint8_t device_id); | |||
}; | |||
extern NoiseSensorClass NoiseSensor; |
@@ -0,0 +1,11 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#include "NoiseSensorNode.h" | |||
#include "NoiseSensor.h" | |||
NoiseSensorNode::NoiseSensorNode() {}; | |||
NoiseSensorNode::NoiseSensorNode(uint8_t node_id, uint8_t size) { | |||
this->_node_id = node_id; | |||
this->_measurements = new uint16_t[size]; | |||
} |
@@ -0,0 +1,21 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#pragma once | |||
#include <Arduino.h> | |||
class NoiseSensorNode { | |||
private: | |||
uint8_t _node_id; | |||
public: | |||
uint32_t _sync_message_ts; | |||
uint16_t* _measurements; // For a dynamic array later. | |||
String sensebox_id; | |||
String sensor_id; | |||
uint8_t missing_answers = 0; | |||
bool _battery_low = 0; | |||
NoiseSensorNode(); | |||
NoiseSensorNode(uint8_t node_id, uint8_t size); | |||
}; |
@@ -0,0 +1,225 @@ | |||
#include "NoiseSensor.h" | |||
#include <SPI.h> // For WiFi101 | |||
#include <WiFi101.h> | |||
#include <senseBoxIO.h> | |||
#include <TimeLib.h> | |||
// ---> Fill in your WiFi name (SSID) and password here. <--- | |||
#define SECRET_SSID "CHANGEME" | |||
#define SECRET_PASSWORD "CHANGEME" | |||
// Keep these values like this unless you know what you're doing. | |||
const char server[] = "api.opensensemap.org"; | |||
const uint8_t tick_count = 60; | |||
const bool verbose = 1; | |||
// ---> Fill this in yourself. <--- | |||
// SenseBox IDs from the web interface in order 0, 1, 2, 3, 4. Master is ID 0! | |||
const char *sensebox_ids[] = {"CHANGEME", "", "CHANGEME", "CHANGEME", ""}; | |||
// Sensor IDs from the web interface in the same order as above. | |||
const char *sensor_ids[] = {"CHANGEME", "", "CHANGEME", "CHANGEME", ""}; | |||
// ================= | |||
WiFiClient client; | |||
uint32_t last_reading; | |||
WiFiUDP udp; | |||
static const char ntpServerName[] = "de.pool.ntp.org"; | |||
const int timeZone = 0; // Central European Time | |||
void setup() { | |||
sleep(2000); | |||
Serial.begin(115200); | |||
Serial.println("\nStarting wifi connection..."); | |||
// Retry WiFi connection until it succeeds. | |||
do { | |||
WiFi.begin(SECRET_SSID, SECRET_PASSWORD); | |||
uint32_t wifi_start_time = millis(); | |||
while (WiFi.status() != WL_CONNECTED) { | |||
senseBoxIO.statusRed(); | |||
delay(500); | |||
Serial.print("."); | |||
senseBoxIO.statusGreen(); | |||
if (millis() - wifi_start_time >= 5000) { | |||
Serial.println("Couldn't connect to Wifi. Try again."); | |||
WiFi.end(); | |||
break; | |||
} | |||
} | |||
} | |||
while (WiFi.status() != WL_CONNECTED); | |||
Serial.print("Connected to AP "); | |||
Serial.println(SECRET_SSID); | |||
udp.begin(8888); | |||
setSyncProvider(getNtpTime); | |||
setSyncInterval(300); | |||
// Let the sensors know we're ready and start measuring. | |||
if (!NoiseSensor.begin(tick_count)) { | |||
Serial.println("Something didn't work. Please restart the system."); | |||
while (1) { | |||
senseBoxIO.statusRed(); | |||
delay(100); | |||
senseBoxIO.statusNone(); | |||
delay(100); | |||
} | |||
} | |||
NoiseSensor.setIds(sensebox_ids, sensor_ids); | |||
sleep(1000); | |||
} | |||
void loop() { | |||
// Shows the openSenseMap's response. | |||
if (!verbose) { | |||
while (client.available()) { | |||
char c = client.read(); | |||
Serial.write(c); | |||
} | |||
} | |||
// This check is clumsy but will be replaced by a proper timer interrupt in the future. | |||
if (NoiseSensor.beaconReady()) { | |||
senseBoxIO.statusRed(); | |||
NoiseSensor.sendSyncBeacon(); | |||
senseBoxIO.statusGreen(); | |||
} | |||
// This check is clumsy but will be replaced by a proper timer interrupt in the future. | |||
if (millis() - last_reading >= 2000 && NoiseSensor.requestReady()) { | |||
senseBoxIO.statusRed(); | |||
Serial.println(); | |||
NoiseSensor.read(); | |||
// ---> Choose which sensor's data you want to upload by calling upload(device_id). | |||
upload(0, server); | |||
upload(2, server); | |||
upload(3, server); | |||
senseBoxIO.statusGreen(); | |||
// Record the last upload time to make sure we don't read and upload multiple times per second. | |||
last_reading = millis(); | |||
} | |||
} | |||
void upload(uint8_t device_id, const char* server) { | |||
Serial.println("Starting upload"); | |||
if (client.connected()) { | |||
client.stop(); | |||
sleep(500); | |||
} | |||
// If there's a successful connection: | |||
if (client.connectSSL(server, 443)) { | |||
String uploadbuf; | |||
String json = NoiseSensor.buildSenseBoxJSON(device_id); | |||
uint16_t content_length = json.length(); | |||
uploadbuf = NoiseSensor.buildHTTPHeader(device_id, server, content_length); | |||
client.println(uploadbuf); | |||
if (verbose) { Serial.println(uploadbuf); } | |||
client.println(); | |||
if (verbose) { Serial.println(); } | |||
// For some reason that nobody knows, client.print has an arbitrary input string limit. | |||
// Therefore, we need to split it at some arbitrary length and provide chunks. | |||
for (uint16_t idx = 0; idx < json.length(); idx += 1000) { | |||
client.print(json.substring(idx, idx + 1000)); | |||
if (verbose) { Serial.print(json.substring(idx, idx + 1000)); } | |||
} | |||
client.println(); | |||
if (verbose) { Serial.println(); } | |||
Serial.println("done!"); | |||
} else { | |||
// If we couldn't make a connection: | |||
Serial.println("connection failed. Restart System."); | |||
sleep(1000); | |||
} | |||
Serial.println("Upload done"); | |||
} | |||
void sleep(unsigned long ms) { // ms: duration | |||
unsigned long start = millis(); // start: timestamp | |||
for (;;) { | |||
unsigned long now = millis(); // now: timestamp | |||
unsigned long elapsed = now - start; // elapsed: duration | |||
if (elapsed >= ms) // comparing durations: OK | |||
return; | |||
} | |||
} | |||
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message | |||
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets | |||
time_t getNtpTime() | |||
{ | |||
IPAddress ntpServerIP;// = IPAddress(176, 9 , 42, 91); // NTP server's ip address | |||
while (udp.parsePacket() > 0) ; // discard any previously received packets | |||
WiFi.hostByName(ntpServerName, ntpServerIP); | |||
if (verbose) { | |||
Serial.println("Transmit NTP Request"); | |||
Serial.print(ntpServerName); | |||
Serial.print(": "); | |||
Serial.println(ntpServerIP); | |||
} | |||
sendNTPpacket(ntpServerIP); | |||
uint32_t beginWait = millis(); | |||
while (millis() - beginWait < 1500) { | |||
int size = udp.parsePacket(); | |||
if (size >= NTP_PACKET_SIZE) { | |||
if (verbose) { Serial.println("Receive NTP Response"); } | |||
udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer | |||
unsigned long secsSince1900; | |||
// convert four bytes starting at location 40 to a long integer | |||
secsSince1900 = (unsigned long)packetBuffer[40] << 24; | |||
secsSince1900 |= (unsigned long)packetBuffer[41] << 16; | |||
secsSince1900 |= (unsigned long)packetBuffer[42] << 8; | |||
secsSince1900 |= (unsigned long)packetBuffer[43]; | |||
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; | |||
} | |||
} | |||
Serial.println("No NTP Response :-("); | |||
return 0; // return 0 if unable to get the time | |||
} | |||
// send an NTP request to the time server at the given address | |||
void sendNTPpacket(IPAddress &address) | |||
{ | |||
// set all bytes in the buffer to 0 | |||
memset(packetBuffer, 0, NTP_PACKET_SIZE); | |||
// Initialize values needed to form NTP request | |||
// (see URL above for details on the packets) | |||
packetBuffer[0] = 0b11100011; // LI, Version, Mode | |||
packetBuffer[1] = 0; // Stratum, or type of clock | |||
packetBuffer[2] = 6; // Polling Interval | |||
packetBuffer[3] = 0xEC; // Peer Clock Precision | |||
// 8 bytes of zero for Root Delay & Root Dispersion | |||
packetBuffer[12] = 49; | |||
packetBuffer[13] = 0x4E; | |||
packetBuffer[14] = 49; | |||
packetBuffer[15] = 52; | |||
// all NTP fields have been given values, now | |||
// you can send a packet requesting a timestamp: | |||
udp.beginPacket(address, 123); //NTP requests are to port 123 | |||
udp.write(packetBuffer, NTP_PACKET_SIZE); | |||
udp.endPacket(); | |||
} |
@@ -0,0 +1,268 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#include "NoiseSensor.h" | |||
#include "time.h" | |||
#include <ESP32Time.h> | |||
#include <TimeLib.h> | |||
ESP32Time t; | |||
bool NoiseSensorClass::begin() { | |||
_PACKET_SIZE = ( _tick_count + LORA_HEADER_LENGTH + BATTERY_FIELD_SIZE ); | |||
_REQUEST_SIZE = ( PACKET_COUNT * _PACKET_SIZE * 2 ); // * 2, it's 16 bit. | |||
for (int i = 0; i < PACKET_COUNT; i++) { | |||
_nodes[i] = new NoiseSensorNode(i, _PACKET_SIZE * 2); | |||
} | |||
if (this->_tick_duration < 2) { | |||
Serial.println("--> Using a tick duration of less than two seconds. This " | |||
"will fail unless you've increased the MCUs SYSCLK!"); | |||
} | |||
if (this->_tick_count >= 100) { | |||
Serial.println( | |||
"--> Using a tick count of more than 100 gets dangerously close to the " | |||
"LoRa transmission limitations and is not supported as of now."); | |||
} | |||
if (!setupNTPClient()) { | |||
Serial.println("!!!-------------------------!!!"); | |||
Serial.println("NTPClient couldn't be started."); | |||
Serial.println("!!!-------------------------!!!"); | |||
return false; | |||
} | |||
Wire.begin(21, 22, 400000); | |||
sendSyncBeacon(); | |||
this->_first_sync = 1; | |||
} | |||
bool NoiseSensorClass::begin(uint8_t tick_count) { | |||
this->_tick_count = tick_count; | |||
this->begin(); | |||
} | |||
bool NoiseSensorClass::begin(uint8_t tick_count, uint8_t tick_duration) { | |||
this->_tick_count = tick_count; | |||
this->_tick_duration = tick_duration; | |||
this->begin(); | |||
} | |||
bool NoiseSensorClass::begin(uint8_t tick_count, uint8_t tick_duration, | |||
uint8_t tx_offset) { | |||
this->_tick_count = tick_count; | |||
this->_tick_duration = tick_duration; | |||
this->_tx_offset = tx_offset; | |||
this->begin(); | |||
} | |||
void NoiseSensorClass::setIds(const char* sensebox_ids[], const char* sensor_ids[]) { | |||
for (int i = 0; i < PACKET_COUNT; i++) { | |||
char tmp[24] = {0}; | |||
_nodes[i]->sensebox_id = sensebox_ids[i]; | |||
_nodes[i]->sensor_id = sensor_ids[i]; | |||
} | |||
} | |||
bool NoiseSensorClass::beaconReady() { | |||
if (t.getEpoch() - _last_sync_time == | |||
(this->_tick_count * this->_tick_duration)) { | |||
Serial.println("---->Beacon ready"); | |||
return true; | |||
} | |||
return false; | |||
} | |||
bool NoiseSensorClass::requestReady() { | |||
// 1 second as tolerance | |||
if (!this->_first_sync && | |||
t.getEpoch() - _last_sync_time == | |||
(5 * this->_tx_offset * this->_tick_duration + 1)) { | |||
Serial.println("---->Request ready"); | |||
return true; | |||
} | |||
return false; | |||
} | |||
bool NoiseSensorClass::read() { | |||
for (int pkt = 0; pkt < PACKET_COUNT; pkt++) { | |||
NoiseSensorNode* node = _nodes[pkt]; | |||
// Calculate the time of the sync message that triggered this packet. | |||
uint32_t time_offset = (_tick_count * _tick_duration); // TODO Move | |||
node->_sync_message_ts = this->_last_sync_time - time_offset; | |||
/* | |||
Serial.print("Last sync time: "); | |||
Serial.println(_last_sync_time); | |||
Serial.print("Offset: "); | |||
Serial.println(time_offset); | |||
Serial.print("TS: "); | |||
Serial.println(_nodes[pkt]->_sync_message_ts); | |||
Serial.print(minute(_nodes[pkt]->_sync_message_ts)); | |||
Serial.print(":"); | |||
Serial.println(second(_nodes[pkt]->_sync_message_ts)); | |||
*/ | |||
/* | |||
if (node->missing_answers > 0) { | |||
node->_sync_message_ts -= time_offset * node->missing_answers; | |||
} | |||
*/ | |||
Wire.requestFrom(_i2c_address, _PACKET_SIZE * 2); | |||
char req_buf[_PACKET_SIZE * 2] = {0}; | |||
uint8_t count = 0; | |||
while (Wire.available()) { | |||
char c = Wire.read(); | |||
req_buf[count] = c; | |||
count++; | |||
} | |||
// TODO There's probably a neater way than doing these low level operations. | |||
for (int i = 0; i < _PACKET_SIZE * 2; i+=2) { | |||
uint16_t n = req_buf[i+1] << 8; | |||
n |= req_buf[i]; | |||
node->_measurements[i/2] = n; | |||
Serial.print(n); | |||
Serial.print(" "); | |||
} | |||
Serial.println(); | |||
} | |||
// Skip 0, it has no battery. | |||
for (int pkt = 1; pkt < PACKET_COUNT; pkt++) { | |||
// TODO Move this to a sane place (own function). | |||
if (_nodes[pkt]->_measurements[_PACKET_SIZE - 1]) { | |||
Serial.print("Battery low on device"); | |||
Serial.println(pkt); | |||
} | |||
} | |||
Serial.println("Reading done."); | |||
return true; | |||
} | |||
void NoiseSensorClass::printMeasurements(uint8_t node_id) { | |||
uint32_t ts = this->_nodes[node_id]->_sync_message_ts; | |||
char printbuf[32]; | |||
sprintf(printbuf, "- ID %u - ", node_id); | |||
Serial.println(printbuf); | |||
for (int i = 2; i < (this->_PACKET_SIZE - BATTERY_FIELD_SIZE); i++) { | |||
// TODO Timestamps | |||
Serial.println(this->_nodes[node_id]->_measurements[i]); | |||
ts += this->_tick_duration; | |||
} | |||
} | |||
bool NoiseSensorClass::sendSyncBeacon() { | |||
uint8_t setup_values[4]; | |||
setup_values[0] = 1; | |||
setup_values[1] = this->_tick_count; | |||
setup_values[2] = this->_tick_duration; | |||
setup_values[3] = this->_tx_offset; | |||
Serial.println("Sent beacon"); | |||
Wire.beginTransmission(_i2c_address); | |||
Wire.write((uint8_t *) setup_values, sizeof(setup_values)); | |||
Wire.endTransmission(); | |||
this->_first_sync = 0; | |||
_last_sync_time = t.getEpoch(); | |||
Serial.println(_last_sync_time); | |||
} | |||
String NoiseSensorClass::buildSenseBoxJSON(uint8_t device_id) { | |||
NoiseSensorNode* node = _nodes[device_id]; | |||
uint32_t ts = node->_sync_message_ts; | |||
// Each measurement line is max. 94 chars long + 2 curly brackets. | |||
uint16_t bufsize = _tick_count * 94 + 2; | |||
char json_buf[bufsize] = {0}; | |||
char sensor_id[25] = {0}; | |||
node->sensor_id.toCharArray(sensor_id, 25); | |||
// Build a json string in a very comprehensible way... | |||
strcat(json_buf, "[\n"); | |||
for (int i = 2; i < (_PACKET_SIZE - BATTERY_FIELD_SIZE); i++) { | |||
char tmpbuf[1024] = {0}; | |||
char timestamp[24] = {0}; | |||
float m = node->_measurements[i] / 10.0; // From fixed point to float. | |||
if (m >= 29 && m <= 120) { | |||
sprintf(timestamp, "%i-%02i-%02iT%02i:%02i:%02iZ", year(ts), month(ts), | |||
day(ts), hour(ts), minute(ts), second(ts)); | |||
sprintf(tmpbuf, "{\"sensor\":\"%s\", \"value\":\"%.1f\", \"createdAt\":\"%s\"}", sensor_id, m, timestamp); | |||
// No trailing comma on the last entry. | |||
if (i != _PACKET_SIZE - BATTERY_FIELD_SIZE - 1) { | |||
strcat(tmpbuf, ","); | |||
} | |||
strcat(json_buf, tmpbuf); | |||
strcat(json_buf, "\n"); | |||
} | |||
ts += this->_tick_duration; | |||
} | |||
/* | |||
if (strlen(json_buf) > 2) { | |||
node->missing_answers = 0; | |||
Serial.println("Missing answers reset."); | |||
} else { | |||
node->missing_answers++; | |||
Serial.print("Missing answers: "); | |||
Serial.println(node->missing_answers); | |||
} | |||
*/ | |||
strcat(json_buf, "]"); | |||
return json_buf; | |||
} | |||
String NoiseSensorClass::buildHTTPHeader(uint8_t device_id, const char* server, uint16_t content_length) { | |||
char sb_id[25] = {0}; | |||
_nodes[device_id]->sensebox_id.toCharArray(sb_id, 25); | |||
char uploadbuf[2048] = {0}; | |||
sprintf(uploadbuf, "POST /boxes/%s/data HTTP/1.1\n" | |||
"Host: %s\n" | |||
"content-type: application/json\n" | |||
"Connection: close\n" | |||
"Content-Length: %u", sb_id, server, content_length); | |||
return String(uploadbuf); | |||
} | |||
// Private functions | |||
bool NoiseSensorClass::setupNTPClient() { | |||
configTime(0, 0, "de.pool.ntp.org"); | |||
// Allow processing of the package. | |||
delay(1000); | |||
struct tm timeinfo; | |||
if(!getLocalTime(&timeinfo)){ | |||
Serial.println("Failed to obtain time"); | |||
return false; | |||
} | |||
char timestamp[24]; | |||
uint32_t ts = t.getEpoch(); | |||
sprintf(timestamp, "%i-%02i-%02iT%02i:%02i:%02iZ", year(ts), month(ts), | |||
day(ts), hour(ts), minute(ts), second(ts)); | |||
Serial.println(timestamp); | |||
return true; | |||
} | |||
NoiseSensorClass NoiseSensor; |
@@ -0,0 +1,91 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#include <Wire.h> | |||
#include "NoiseSensorNode.h" | |||
#include <WiFiUdp.h> | |||
#define LORA_HEADER_LENGTH 2 | |||
#define BATTERY_FIELD_SIZE 1 | |||
#define PACKET_COUNT 5 | |||
class NoiseSensorClass { | |||
private: | |||
//NTPClient* _time_client; | |||
uint32_t _last_sync_time; | |||
uint8_t _tick_count = 20; | |||
uint8_t _tick_duration = 2; // in seconds | |||
uint8_t _tx_offset = 1; | |||
uint8_t _i2c_address = 0x3C; | |||
bool _first_sync = true; | |||
// These aren't _real_ const but they shouldn't be redefined nonetheless. | |||
uint16_t _PACKET_SIZE; | |||
uint16_t _REQUEST_SIZE; | |||
NoiseSensorNode* _nodes[]; | |||
uint8_t getDeviceID(); | |||
bool setupNTPClient(); | |||
uint32_t getEpoch(); | |||
public: | |||
/** | |||
* Begin the measurement cycle by sending a sync message. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(); | |||
/** | |||
* Call begin() with a custom tick count. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(uint8_t tick_count); | |||
/** | |||
* Call begin() with a custom tick count and tick duration. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(uint8_t tick_count, uint8_t tick_duration); | |||
/** | |||
* Call begin() with a custom tick count, tick duration and tx_offset. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool begin(uint8_t tick_count, uint8_t tick_duration, uint8_t tx_offset); | |||
void setIds(const char* sensebox_ids[], const char* sensor_ids[]); | |||
String buildHTTPHeader(uint8_t device_id, const char* server, uint16_t content_length); | |||
bool beaconReady(); | |||
bool requestReady(); | |||
/** | |||
* Read the newly available data from the slave. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool read(); | |||
/** | |||
* Print the measurements in a pretty format. | |||
*/ | |||
void printMeasurements(uint8_t device_id); | |||
/** | |||
* Send the sync message to the slave. | |||
* | |||
* @return true on success, false on failure | |||
*/ | |||
bool sendSyncBeacon(); | |||
String buildSenseBoxJSON(uint8_t device_id); | |||
}; | |||
extern NoiseSensorClass NoiseSensor; |
@@ -0,0 +1,11 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#include "NoiseSensorNode.h" | |||
#include "NoiseSensor.h" | |||
NoiseSensorNode::NoiseSensorNode() {}; | |||
NoiseSensorNode::NoiseSensorNode(uint8_t node_id, uint8_t size) { | |||
this->_node_id = node_id; | |||
this->_measurements = new uint16_t[size]; | |||
} |
@@ -0,0 +1,21 @@ | |||
// vim: noai:sw=2:tw=88 | |||
#pragma once | |||
#include <Arduino.h> | |||
class NoiseSensorNode { | |||
private: | |||
uint8_t _node_id; | |||
public: | |||
uint32_t _sync_message_ts; | |||
uint16_t* _measurements; // For a dynamic array later. | |||
String sensebox_id; | |||
String sensor_id; | |||
uint8_t missing_answers; | |||
bool _battery_low = 0; | |||
NoiseSensorNode(); | |||
NoiseSensorNode(uint8_t node_id, uint8_t size); | |||
}; |
@@ -0,0 +1,140 @@ | |||
#include "NoiseSensor.h" | |||
#include <SPI.h> // For WiFi, I think | |||
#include <WiFiClientSecure.h> | |||
#include "time.h" | |||
#define SNTP_UPDATE_DELAY 300000 | |||
// ---> Fill in your WiFi name (SSID) and password here. <--- | |||
#define SECRET_SSID "CHANGEME" | |||
#define SECRET_PASSWORD "CHANGEME" | |||
// Keep these values like this unless you know what you're doing. | |||
const char server[] = "api.opensensemap.org"; | |||
const uint8_t tick_count = 30; | |||
const bool verbose = 1; | |||
// ---> Fill this in yourself. <--- | |||
// SenseBox IDs from the web interface in order 0, 1, 2, 3, 4. Master is ID 0! | |||
const char *sensebox_ids[] = {"CHANGEME", "", "CHANGEME", "", ""}; | |||
// Sensor IDs from the web interface in the same order as above. | |||
const char *sensor_ids[] = {"CHANGEME", "", "CHANGEME", "", ""}; | |||
// ================= | |||
WiFiClientSecure client; | |||
uint32_t last_reading; | |||
WiFiUDP udp; | |||
void setup() { | |||
delay(2000); | |||
Serial.begin(115200); | |||
Serial.println("\nStarting wifi connection..."); | |||
// Retry WiFi connection until it succeeds. | |||
do { | |||
WiFi.begin(SECRET_SSID, SECRET_PASSWORD); | |||
uint32_t wifi_start_time = millis(); | |||
while (WiFi.status() != WL_CONNECTED) { | |||
delay(500); | |||
Serial.print("."); | |||
if (millis() - wifi_start_time >= 5000) { | |||
Serial.println("\nCouldn't connect to Wifi. Try again."); | |||
//WiFi.end(); | |||
break; | |||
} | |||
} | |||
} | |||
while (WiFi.status() != WL_CONNECTED); | |||
Serial.print("\nConnected to AP "); | |||
Serial.println(SECRET_SSID); | |||
client.setInsecure(); | |||
// Let the sensors know we're ready and start measuring. | |||
if (!NoiseSensor.begin(tick_count)) { | |||
Serial.println("Something didn't work. Please restart the system."); | |||
while (1) { | |||
Serial.println("Error, please restart."); | |||
} | |||
} | |||
NoiseSensor.setIds(sensebox_ids, sensor_ids); | |||
delay(1000); | |||
} | |||
void loop() { | |||
// Shows the openSenseMap's response. | |||
if (verbose) { | |||
while (client.available()) { | |||
char c = client.read(); | |||
Serial.write(c); | |||
} | |||
} | |||
// This check is clumsy but will be replaced by a proper timer interrupt in the future. | |||
if (NoiseSensor.beaconReady()) { | |||
NoiseSensor.sendSyncBeacon(); | |||
} | |||
// This check is clumsy but will be replaced by a proper timer interrupt in the future. | |||
if (millis() - last_reading >= 2000 && NoiseSensor.requestReady()) { | |||
Serial.println(); | |||
NoiseSensor.read(); | |||
// ---> Choose which sensor's data you want to upload by calling upload(device_id). | |||
upload(0, server); | |||
upload(2, server); | |||
// Record the last upload time to make sure we don't read and upload multiple times per second. | |||
last_reading = millis(); | |||
} | |||
} | |||
void upload(uint8_t device_id, const char* server) { | |||
Serial.println("Start upload"); | |||
if (client.connected()) { | |||
client.stop(); | |||
delay(500); | |||
} | |||
// If there's a successful connection: | |||
if (client.connect(server, 443)) { | |||
String uploadbuf; | |||
String json = NoiseSensor.buildSenseBoxJSON(device_id); | |||
uint16_t content_length = json.length(); | |||
Serial.println("connecting..."); | |||
uploadbuf = NoiseSensor.buildHTTPHeader(device_id, server, content_length); | |||
client.println(uploadbuf); | |||
if (verbose) { Serial.println(uploadbuf); } | |||
client.println(); | |||
if (verbose) { Serial.println(); } | |||
// For some reason that nobody knows, client.print has an arbitrary input string limit. | |||
// Therefore, we need to split it at some arbitrary length and provide chunks. | |||
for (uint16_t idx = 0; idx < json.length(); idx += 1000) { | |||
client.print(json.substring(idx, idx + 1000)); | |||
if (verbose) { Serial.print(json.substring(idx, idx + 1000)); } | |||
} | |||
client.println(); | |||
if (verbose) { Serial.println(); } | |||
Serial.println("done!"); | |||
} else { | |||
// If we couldn't make a connection: | |||
Serial.println("connection failed. Restart System."); | |||
delay(1000); | |||
} | |||
Serial.println("Upload done"); | |||
} |