@@ -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"); | |||||
} |