Browse Source

Add Arduino and ESP32 code

master
Lukas 3 years ago
parent
commit
ac60943b62
13 changed files with 2083 additions and 0 deletions
  1. +46
    -0
      Arduino/DMAC.h
  2. +734
    -0
      Arduino/I2C_DMAC.cpp
  3. +141
    -0
      Arduino/I2C_DMAC.h
  4. +284
    -0
      Arduino/NoiseSensor.cpp
  5. +90
    -0
      Arduino/NoiseSensor.h
  6. +11
    -0
      Arduino/NoiseSensorNode.cpp
  7. +21
    -0
      Arduino/NoiseSensorNode.h
  8. +225
    -0
      Arduino/noisesensor_arduino.ino
  9. +268
    -0
      ESP32/NoiseSensor.cpp
  10. +91
    -0
      ESP32/NoiseSensor.h
  11. +11
    -0
      ESP32/NoiseSensorNode.cpp
  12. +21
    -0
      ESP32/NoiseSensorNode.h
  13. +140
    -0
      ESP32/noisesensor_esp32.ino

+ 46
- 0
Arduino/DMAC.h View File

@@ -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

+ 734
- 0
Arduino/I2C_DMAC.cpp View File

@@ -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

+ 141
- 0
Arduino/I2C_DMAC.h View File

@@ -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

+ 284
- 0
Arduino/NoiseSensor.cpp View File

@@ -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;

+ 90
- 0
Arduino/NoiseSensor.h View File

@@ -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;

+ 11
- 0
Arduino/NoiseSensorNode.cpp View File

@@ -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];
}

+ 21
- 0
Arduino/NoiseSensorNode.h View File

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

+ 225
- 0
Arduino/noisesensor_arduino.ino View File

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

+ 268
- 0
ESP32/NoiseSensor.cpp View File

@@ -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;

+ 91
- 0
ESP32/NoiseSensor.h View File

@@ -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;

+ 11
- 0
ESP32/NoiseSensorNode.cpp View File

@@ -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];
}

+ 21
- 0
ESP32/NoiseSensorNode.h View File

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

+ 140
- 0
ESP32/noisesensor_esp32.ino View File

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

Loading…
Cancel
Save