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