From b8de8ae44cd1e4feb6744fd2c946ac7200449598 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 14 Mar 2018 14:23:34 -0400 Subject: [PATCH 01/26] Implement timers for rx --- src/SDI12.cpp | 299 ++++++++++++++++++++++++++++++-------------------- src/SDI12.h | 19 +++- 2 files changed, 195 insertions(+), 123 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index bd99bde..da39779 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -124,31 +124,57 @@ SDI-12.org, official site of the SDI-12 Support Group. 0.1 - Include the header file for this library. 0.2 - defines the size of the buffer -0.3 - defines value for DISABLED state (see section 2) -0.4 - defines value for ENABLED state (not used, reserved for future) -0.5 - defines value for DISABLED state (see section 2) -0.6 - defines value for TRANSMITTING state (see section 2) -0.7 - defines value for LISTENING state -0.8 - defines value for the spacing of bits. +0.3 - defines value for the spacing of bits. 1200 bits per second implies 833 microseconds per bit. 830 seems to be a reliable value given the overhead of the call. -0.10 - a static pointer to the active object. See section 6. -0.11 - a reference to the data pin, used throughout the library -0.12 - holds the buffer overflow status +0.4 - a static pointer to the active object. See section 6. */ #include "SDI12.h" // 0.1 header file for this library #define SDI12_BUFFER_SIZE 64 // 0.2 max RX buffer size -#define DISABLED 0 // 0.3 value for DISABLED state -#define ENABLED 1 // 0.4 value for ENABLED state -#define HOLDING 2 // 0.5 value for DISABLED state -#define TRANSMITTING 3 // 0.6 value for TRANSMITTING state -#define LISTENING 4 // 0.7 value for LISTENING state -#define SPACING 830 // 0.8 bit timing in microseconds (1200 baud = 1200 bits/second ~ 830 µs/bit) +#define SPACING 830 // 0.3 bit timing in microseconds (1200 baud = 1200 bits/second ~ 830 µs/bit) + +SDI12 *SDI12::_activeObject = NULL; // 0.4 pointer to active SDI12 object + +#if F_CPU == 16000000L + #define TCNTX TCNT0 + #define PCI_FLAG_REGISTER PCIFR +#elif F_CPU == 8000000L + #if defined(__AVR_ATtiny25__) | \ + defined(__AVR_ATtiny45__) | \ + defined(__AVR_ATtiny85__) + #define TCNTX TCNT1 + #define PCI_FLAG_REGISTER GIFR + #else + #define TCNTX TCNT2 + #define PCI_FLAG_REGISTER PCIFR + #endif +#endif + +static const uint8_t txBitWidth = (uint8_t) 208; // The size of a bit in processor "ticks" + // 1200 baud bit width in units of 4µs (4µs = 1 processor tick at 16MHz) +static const uint8_t bitsPerTick_Q10 = 5; // The number of bits per tick, times a multiplier + // 1200 bps * 0.000004 s * 2^10 "multiplier" +static const uint8_t rxWindowWidth = 5; // A fudge factor to make things work +static const uint8_t waitingForStartBit = 0xFF; + +static uint8_t rxState; // 0: got start bit; >0: bits rcvd +static uint8_t prev_t0; // previous RX transition: timer0 time stamp (4us) +static uint8_t rxMask; // bit mask for building received character +static uint8_t rxValue; // character being built + +// Multiply two 8-bit intergers to get a 16-bit interger +static uint16_t mul8x8to16(uint8_t x, uint8_t y) +{return x*y;} -SDI12 *SDI12::_activeObject = NULL; // 0.10 pointer to active SDI12 object +// Calculate how many bit times have passed +static uint16_t bitTimes( uint8_t dt ) +{ + return mul8x8to16( dt + rxWindowWidth, bitsPerTick_Q10 ) >> 10; + +} /* =========== 1. Buffer Setup ============================================ @@ -255,64 +281,49 @@ uint8_t SDI12::parity_even_bit(uint8_t v) } #endif -// 2.2 - Returns the current state -const char *SDI12::getStateName(uint8_t state) -{ - const char * retval = "UNKNOWN"; - if (state == HOLDING) { - retval = "HOLDING"; - } - else if (state == TRANSMITTING) { - retval = "TRANSMITTING"; - } - else if (state == LISTENING) { - retval = "LISTENING"; +// 2.3 - sets the state of the SDI-12 object. +void SDI12::setState(SDI12_STATES state){ + switch (state) + { + case HOLDING: + { + pinMode(_dataPin,INPUT); // added to make output work after pinMode to OUTPUT (don't know why, but works) + pinMode(_dataPin,OUTPUT); // Pin mode = output + digitalWrite(_dataPin,LOW); // Pin state = low + #if defined __AVR__ + *digitalPinToPCMSK(_dataPin) &= ~(1<receiveChar(); + if (_activeObject) _activeObject->receiveISR(); } -// 7.2 - Quickly reads a new character into the buffer. -void SDI12::receiveChar() +// 7.2 - Creates a blank slate of bits for an incoming character +void SDI12::startChar() { - if (digitalRead(_dataPin)) // 7.2.1 - Start bit? - { - uint8_t newChar = 0; // 7.2.2 - Make room for char. + rxState = 0; // got a start bit + rxMask = 0x01; // bit mask, lsb first + rxValue = 0x00; // RX character to be, a blank slate - delayMicroseconds(SPACING/2); // 7.2.3 - Wait 1/2 SPACING +} // startChar - for (uint8_t i=0x1; i<0x80; i <<= 1) // 7.2.4 - read the 7 data bits - { - delayMicroseconds(SPACING); - uint8_t noti = ~i; - if (!digitalRead(_dataPin)) - newChar |= i; - else - newChar &= noti; +// 7.3 - The actual interrupt service routine +void SDI12::receiveISR() +{ + uint8_t t0 = TCNTX; // time of this data transition (plus ISR latency) + uint8_t d = digitalRead(_dataPin); // current RX data level + + // Check if we're ready for a start bit, and if this could possibly be it + // Otherwise, just ignore the interrupt and exit + if (rxState == waitingForStartBit) { + if (d == 0) return; // it just became low so not a start bit, exit + startChar(); + } + + // if we aren't ready for a start bit, it means it has already come in and + // this new change is from a data, parity, or stop bit + else { + + // check how many bit times have passed since the last change + uint16_t rxBits = bitTimes( t0-prev_t0 ); + // calculate how many bit should be left, ignoring parity bit and stop bit + uint8_t bitsLeft = 8 - rxState; + // check again if this should be a new character + bool nextCharStarted = (rxBits > bitsLeft); + + // check how many bits should have been sent since the last change + uint8_t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; + // Advance the receive state by that many bits + rxState += bitsThisFrame; + + // Set all the bits received between the last change and this change + // If the current state is HIGH (and it just became so), then all bits between + // the last change and now must have been LOW. + if (d == 1) { + // back fill previous bits with 1's (inverse logic - LOW = 1) + while (bitsThisFrame-- > 0) { + rxValue |= rxMask; + rxMask = rxMask << 1; + } + rxMask = rxMask << 1; } + // If the current state is LOW (and it just became so), then this bit is LOW + // but all bits between the last change and now must have been HIGH + else { // d==0 + // previous bits were 0's so only this bit is a 1 (inverse logic - LOW = 1) + rxMask = rxMask << (bitsThisFrame-1); + rxValue |= rxMask; + } + + // After setting bits, if this is the 7th bit, parity bit, or stop bit + // then the character is complete. + if (rxState > 6) { + charToBuffer(rxValue); // Put the finished character into the buffer + + if ((d == 1) || !nextCharStarted) { + rxState = waitingForStartBit; + // DISABLE STOP BIT TIMER - delayMicroseconds(SPACING); // 7.2.5 - Skip the parity bit. - delayMicroseconds(SPACING); // 7.2.6 - Skip the stop bit. + } else { + // The last char ended with 1's, so this 0 is actually + // the start bit of the next character. - // 7.2.7 - Overflow? If not, proceed. - if ((_rxBufferTail + 1) % SDI12_BUFFER_SIZE == _rxBufferHead) - { _bufferOverflow = true; - } else { // 7.2.8 - Save char, advance tail. - _rxBuffer[_rxBufferTail] = newChar; - _rxBufferTail = (_rxBufferTail + 1) % SDI12_BUFFER_SIZE; + startChar(); + } } + prev_t0 = t0; // remember time stamp of this change! + } +} + +// 7.4 - Put a new character in the buffer +void SDI12::charToBuffer( uint8_t c ) +{ + // Check for a buffer overflow. If not, proceed. + if ((_rxBufferTail + 1) % SDI12_BUFFER_SIZE == _rxBufferHead) + { _bufferOverflow = true; } + // Save the character, advance buffer tail. + else + { + _rxBuffer[_rxBufferTail] = c; + _rxBufferTail = (_rxBufferTail + 1) % SDI12_BUFFER_SIZE; } } -// 7.3 - Define AVR interrupts +// 7.5 - Define AVR interrupts #if defined __AVR__ // Only AVR processors use interrupts like this diff --git a/src/SDI12.h b/src/SDI12.h index 52aed18..848e17f 100644 --- a/src/SDI12.h +++ b/src/SDI12.h @@ -79,13 +79,24 @@ class SDI12 : public Stream int peekNextDigit(LookaheadMode lookahead, bool detectDecimal); private: + + // For the various SDI12 states + typedef enum SDI12_STATES + { + DISABLED = 0, + ENABLED = 1, + HOLDING = 2, + TRANSMITTING = 3, + LISTENING = 4 + } SDI12_STATES; + static SDI12 *_activeObject; // static pointer to active SDI12 instance - void setState(uint8_t state); // sets the state of the SDI12 objects + void setState(SDI12_STATES state); // sets the state of the SDI12 objects void wakeSensors(); // used to wake up the SDI12 bus void writeChar(uint8_t out); // used to send a char out on the data line - void receiveChar(); // used by the ISR to grab a char from data line - - static const char * getStateName(uint8_t state); // get state name (in ASCII) + void startChar(); // creates a blank slate for a new character + void receiveISR(); // the actual function responding to changes in rx line state + void charToBuffer(uint8_t c); // puts a finished character into the SDI12 instance buffer #ifndef __AVR__ static uint8_t parity_even_bit(uint8_t v); From 1574337709c35112301c2d925c80ee995c86628e Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 14 Mar 2018 16:07:42 -0400 Subject: [PATCH 02/26] Playing with using micros(). Doesn't work. --- src/SDI12.cpp | 63 +++++++++++++-------------------------------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index da39779..f9fa04e 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -124,9 +124,6 @@ SDI-12.org, official site of the SDI-12 Support Group. 0.1 - Include the header file for this library. 0.2 - defines the size of the buffer -0.3 - defines value for the spacing of bits. - 1200 bits per second implies 833 microseconds per bit. - 830 seems to be a reliable value given the overhead of the call. 0.4 - a static pointer to the active object. See section 6. */ @@ -134,47 +131,19 @@ SDI-12.org, official site of the SDI-12 Support Group. #include "SDI12.h" // 0.1 header file for this library #define SDI12_BUFFER_SIZE 64 // 0.2 max RX buffer size -#define SPACING 830 // 0.3 bit timing in microseconds (1200 baud = 1200 bits/second ~ 830 µs/bit) SDI12 *SDI12::_activeObject = NULL; // 0.4 pointer to active SDI12 object -#if F_CPU == 16000000L - #define TCNTX TCNT0 - #define PCI_FLAG_REGISTER PCIFR -#elif F_CPU == 8000000L - #if defined(__AVR_ATtiny25__) | \ - defined(__AVR_ATtiny45__) | \ - defined(__AVR_ATtiny85__) - #define TCNTX TCNT1 - #define PCI_FLAG_REGISTER GIFR - #else - #define TCNTX TCNT2 - #define PCI_FLAG_REGISTER PCIFR - #endif -#endif - -static const uint8_t txBitWidth = (uint8_t) 208; // The size of a bit in processor "ticks" - // 1200 baud bit width in units of 4µs (4µs = 1 processor tick at 16MHz) -static const uint8_t bitsPerTick_Q10 = 5; // The number of bits per tick, times a multiplier - // 1200 bps * 0.000004 s * 2^10 "multiplier" -static const uint8_t rxWindowWidth = 5; // A fudge factor to make things work +static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in microseconds + // 1200 baud = 1200 bits/second ~ 833.333 µs/bit + // 830 seems to be a reliable value given the overhead of the call. +static const uint8_t rxWindowWidth = 9; // A fudge factor to make things work static const uint8_t waitingForStartBit = 0xFF; -static uint8_t rxState; // 0: got start bit; >0: bits rcvd -static uint8_t prev_t0; // previous RX transition: timer0 time stamp (4us) -static uint8_t rxMask; // bit mask for building received character -static uint8_t rxValue; // character being built - -// Multiply two 8-bit intergers to get a 16-bit interger -static uint16_t mul8x8to16(uint8_t x, uint8_t y) -{return x*y;} - -// Calculate how many bit times have passed -static uint16_t bitTimes( uint8_t dt ) -{ - return mul8x8to16( dt + rxWindowWidth, bitsPerTick_Q10 ) >> 10; - -} +static uint8_t rxState; // 0: got start bit; >0: bits rcvd +static uint16_t prev_t0; // previous RX transition: timer0 time stamp (4us) +static uint8_t rxMask; // bit mask for building received character +static uint8_t rxValue; // character being built /* =========== 1. Buffer Setup ============================================ @@ -449,7 +418,7 @@ significant bit position, since the characters we are using are only Then we use the '|=' operator to set the bit if necessary. + 4.2.2 - Send the start bit. The start bit is always a '0', so we simply -write the dataPin HIGH for SPACING microseconds. +write the dataPin HIGH for bitWidth_micros microseconds. + 4.2.3 - Send the payload (the 7 character bits and the parity bit) least significant bit first. This is accomplished bitwise AND operations on a @@ -468,7 +437,7 @@ if(out & mask){ } + 4.2.4 - Send the stop bit. The stop bit is always a '1', so we simply -write the dataPin LOW for SPACING microseconds. +write the dataPin LOW for bitWidth_micros microseconds. 4.3 - sendCommand(String cmd) is a publicly accessible function that wakes sensors and sends out a String byte by byte the command line. @@ -495,7 +464,7 @@ void SDI12::writeChar(uint8_t out) out |= (parity_even_bit(out)<<7); // 4.2.1 - parity bit digitalWrite(_dataPin, HIGH); // 4.2.2 - start bit - delayMicroseconds(SPACING); + delayMicroseconds(bitWidth_micros); for (byte mask = 0x01; mask; mask<<=1){ // 4.2.3 - send payload if(out & mask){ @@ -504,11 +473,11 @@ void SDI12::writeChar(uint8_t out) else{ digitalWrite(_dataPin, HIGH); } - delayMicroseconds(SPACING); + delayMicroseconds(bitWidth_micros); } digitalWrite(_dataPin, LOW); // 4.2.4 - stop bit - delayMicroseconds(SPACING); + delayMicroseconds(bitWidth_micros); } // 4.3 - this function sends out the characters of the String cmd, one by one @@ -890,7 +859,7 @@ void SDI12::startChar() // 7.3 - The actual interrupt service routine void SDI12::receiveISR() { - uint8_t t0 = TCNTX; // time of this data transition (plus ISR latency) + uint16_t t0 = micros(); // time of this data transition (plus ISR latency) uint8_t d = digitalRead(_dataPin); // current RX data level // Check if we're ready for a start bit, and if this could possibly be it @@ -905,10 +874,10 @@ void SDI12::receiveISR() else { // check how many bit times have passed since the last change - uint16_t rxBits = bitTimes( t0-prev_t0 ); + uint16_t rxBits = (t0-prev_t0 + rxWindowWidth)/bitWidth_micros; // calculate how many bit should be left, ignoring parity bit and stop bit uint8_t bitsLeft = 8 - rxState; - // check again if this should be a new character + // mark a new character started if more bits have been received than should be left bool nextCharStarted = (rxBits > bitsLeft); // check how many bits should have been sent since the last change From fdb9941d88da00a7beccc1f8844f52b3158e5d7d Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Tue, 27 Mar 2018 18:54:27 -0400 Subject: [PATCH 03/26] Var renames --- src/SDI12.cpp | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index f9fa04e..8ab538c 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -137,13 +137,13 @@ SDI12 *SDI12::_activeObject = NULL; // 0.4 pointer to active SDI12 object static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in microseconds // 1200 baud = 1200 bits/second ~ 833.333 µs/bit // 830 seems to be a reliable value given the overhead of the call. -static const uint8_t rxWindowWidth = 9; // A fudge factor to make things work -static const uint8_t waitingForStartBit = 0xFF; +static const uint8_t rxWindowWidth = 0; // A fudge factor to make things work +static const uint8_t allBitsComplete = 0b11111111; -static uint8_t rxState; // 0: got start bit; >0: bits rcvd -static uint16_t prev_t0; // previous RX transition: timer0 time stamp (4us) -static uint8_t rxMask; // bit mask for building received character -static uint8_t rxValue; // character being built +static uint16_t prevBitMicros; // previous RX transition in micros +static uint8_t rxBitsCompleted; // 0: got start bit; >0: bits rcvd +static uint8_t rxMask; // bit mask for building received character +static uint8_t rxValue; // character being built /* =========== 1. Buffer Setup ============================================ @@ -466,7 +466,7 @@ void SDI12::writeChar(uint8_t out) digitalWrite(_dataPin, HIGH); // 4.2.2 - start bit delayMicroseconds(bitWidth_micros); - for (byte mask = 0x01; mask; mask<<=1){ // 4.2.3 - send payload + for (byte mask = 0b00000001; mask; mask<<=1){ // 4.2.3 - send payload if(out & mask){ digitalWrite(_dataPin, LOW); } @@ -850,22 +850,22 @@ void SDI12::handleInterrupt(){ // 7.2 - Creates a blank slate of bits for an incoming character void SDI12::startChar() { - rxState = 0; // got a start bit - rxMask = 0x01; // bit mask, lsb first - rxValue = 0x00; // RX character to be, a blank slate + rxBitsCompleted = 0; // got a start bit + rxMask = 0b00000001; // bit mask, lsb first + rxValue = 0b00000000; // RX character to be, a blank slate } // startChar // 7.3 - The actual interrupt service routine void SDI12::receiveISR() { - uint16_t t0 = micros(); // time of this data transition (plus ISR latency) - uint8_t d = digitalRead(_dataPin); // current RX data level + uint16_t thisBitMicros = micros(); // time of this data transition in micros (plus ISR latency) + uint8_t pinLevel = digitalRead(_dataPin); // current RX data level // Check if we're ready for a start bit, and if this could possibly be it // Otherwise, just ignore the interrupt and exit - if (rxState == waitingForStartBit) { - if (d == 0) return; // it just became low so not a start bit, exit + if (rxBitsCompleted == allBitsComplete) { + if (pinLevel == LOW) return; // it just became low so not a start bit, exit startChar(); } @@ -874,21 +874,22 @@ void SDI12::receiveISR() else { // check how many bit times have passed since the last change - uint16_t rxBits = (t0-prev_t0 + rxWindowWidth)/bitWidth_micros; - // calculate how many bit should be left, ignoring parity bit and stop bit - uint8_t bitsLeft = 8 - rxState; + uint16_t rxBits = (thisBitMicros - prevBitMicros + rxWindowWidth)/bitWidth_micros; + // calculate how many bits should be left, ignoring parity bit and stop bit + uint8_t bitsLeft = 8 - rxBitsCompleted; // mark a new character started if more bits have been received than should be left bool nextCharStarted = (rxBits > bitsLeft); // check how many bits should have been sent since the last change + // translation: if nextCharStarted bitsThisFrame = bitsLeft, else bitsThisFrame = rxBits uint8_t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; // Advance the receive state by that many bits - rxState += bitsThisFrame; + rxBitsCompleted += bitsThisFrame; // Set all the bits received between the last change and this change // If the current state is HIGH (and it just became so), then all bits between // the last change and now must have been LOW. - if (d == 1) { + if (pinLevel == HIGH) { // back fill previous bits with 1's (inverse logic - LOW = 1) while (bitsThisFrame-- > 0) { rxValue |= rxMask; @@ -898,7 +899,7 @@ void SDI12::receiveISR() } // If the current state is LOW (and it just became so), then this bit is LOW // but all bits between the last change and now must have been HIGH - else { // d==0 + else { // pinLevel==LOW // previous bits were 0's so only this bit is a 1 (inverse logic - LOW = 1) rxMask = rxMask << (bitsThisFrame-1); rxValue |= rxMask; @@ -906,11 +907,11 @@ void SDI12::receiveISR() // After setting bits, if this is the 7th bit, parity bit, or stop bit // then the character is complete. - if (rxState > 6) { + if (rxBitsCompleted > 6) { charToBuffer(rxValue); // Put the finished character into the buffer - if ((d == 1) || !nextCharStarted) { - rxState = waitingForStartBit; + if ((pinLevel == HIGH) || !nextCharStarted) { + rxBitsCompleted = allBitsComplete; // DISABLE STOP BIT TIMER } else { @@ -920,7 +921,7 @@ void SDI12::receiveISR() startChar(); } } - prev_t0 = t0; // remember time stamp of this change! + prevBitMicros = thisBitMicros; // remember time stamp of this change! } } From 1f41ee82b11dc026d32a427520a5c68684567994 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Tue, 27 Mar 2018 21:26:27 -0400 Subject: [PATCH 04/26] Pull some changes from develop --- src/SDI12.cpp | 81 ++++++++++++++++++++++++++++++--------------------- src/SDI12.h | 6 +++- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index 8ab538c..ef85b13 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -124,7 +124,7 @@ SDI-12.org, official site of the SDI-12 Support Group. 0.1 - Include the header file for this library. 0.2 - defines the size of the buffer -0.4 - a static pointer to the active object. See section 6. +0.3 - a static pointer to the active object. See section 6. */ @@ -132,7 +132,7 @@ SDI-12.org, official site of the SDI-12 Support Group. #define SDI12_BUFFER_SIZE 64 // 0.2 max RX buffer size -SDI12 *SDI12::_activeObject = NULL; // 0.4 pointer to active SDI12 object +SDI12 *SDI12::_activeObject = NULL; // 0.3 pointer to active SDI12 object static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in microseconds // 1200 baud = 1200 bits/second ~ 833.333 µs/bit @@ -213,12 +213,10 @@ HOLDING --> TRANSMITTING --> LISTENING --> done reading, forceHold(); HOLDING 2.1 - Sets up parity and interrupts for different processor types - that is, imports the interrupts and parity for the AVR processors where they exist. -2.2 - A function to return a string with the current line state. +2.2 - A private helper function to turn pin interupts on or off 2.3 - Sets the proper state. This is a private function, and only used -internally. It uses #define values of HOLDING, TRANSMITTING, LISTENING, -and DISABLED to determine which state should be set. The grid above -defines the settings applied in changing to each state. +internally. The grid above defines the settings applied in changing to each state. 2.4 - A public function which forces the line into a "holding" state. This is generally unneeded, but for deployments where interference is an @@ -250,15 +248,22 @@ uint8_t SDI12::parity_even_bit(uint8_t v) } #endif -// 2.3 - sets the state of the SDI-12 object. -void SDI12::setState(SDI12_STATES state){ - switch (state) - { - case HOLDING: +// 2.2 - a helper function to switch pin interrupts on or off +void SDI12::setPinInterrupts(bool enable) +{ + // #ifndef SDI12_EXTERNAL_PCINT + if (enable) + { + #if defined __AVR__ + *digitalPinToPCICR(_dataPin) |= (1< Date: Tue, 27 Mar 2018 21:27:29 -0400 Subject: [PATCH 05/26] Update ReadMe from master --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1d1311d..75c3fcb 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,61 @@ Arduino-SDI-12 ============== -Arduino library for SDI-12 communications to a wide variety of environmental sensors. This library provides a general software solution, without requiring any additional hardware, to implement the SDI-12 communication protocol between an Arduino-based data logger and SDI12-enabled sensors. +Arduino library for SDI-12 communications to a wide variety of environmental sensors. This library provides a general software solution, without requiring any additional hardware, to implement the SDI-12 communication protocol between an Arduino-based data logger and SDI-12-enabled sensors. [SDI-12](http://www.sdi-12.org/) is an asynchronous, ASCII, serial communications protocol that was developed for intelligent sensory instruments that typically monitor environmental data. [Advantages of SDI-12](http://en.wikipedia.org/wiki/SDI-12) include the ability to use a single available data channel for many sensors. -This project is part of the [EnviroDIY](http://envirodiy.org/) vision to create an open source hardware and software stack to deliver near real time environmental data from wireless sensor networks, such as the Arduino-compatible [EnviroDIY™ Mayfly Data Logger](http://envirodiy.org/mayfly/). +This work is motivated by the [EnviroDIY community](http://envirodiy.org/) vision to create an open source hardware and software stack to deliver near real time environmental data from wireless sensor networks, such as the Arduino-compatible [EnviroDIY™ Mayfly Data Logger](http://envirodiy.org/mayfly/). -The library has been tested with an Arduino Uno (AtMega328p), EnviroDIY Mayfly (AtMega1284p), Adafruit Feather 32u4 (AtMega32u4, identical to Arduino Leonardo), and an Adafruit Feather M0 (SAMD21G18, identical to Arduino Zero). Not all pins are usable on all boards. These pins will work on those processors: +## Getting Started -**AtMega328p / Arduino Uno:** All pins -**Arduino Mega or Mega 2560:** 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69) -**AtMega32u4 / Arduino Leonardo:** 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI) -**AtMega1284p / EnviroDIY Mayfly:** All pins -**SAMD21G18 / Arduino Zero:** All pins (except 4 on the zero) +Learn more, below, about this library's: +* [Origins and Inherited Limitations](https://github.com/EnviroDIY/Arduino-SDI-12#origins-and-inherited-limitations); +* [Compatibility Considerations](https://github.com/EnviroDIY/Arduino-SDI-12#compatibility-considerations); +* [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) we created to overcome some limitations. -Note that this library will, by nature, conflict with SoftwareSerial and any other library that monopolize all pin change interrupt vectors for all AVR boards. If you would like to use a different pin change interrupt library, uncomment the line ```#define SDI12_EXTERNAL_PCINT``` in SDI12.h and recompile the library. Then, in your own code call SDI12::handleInterrupt() as the interrupt for the SDI12 pin using the other interrupt library. Example j shows doing this using GreyGnome's [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) library. +Try running our [Example sketches](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples) with your Arduino board and SDI-12 sensor. -This library also shares some of the other limitations of SoftwareSerial, that is, all interrupts are disabled during transmission and it might be sensitive to interrupt use by other libraries. Because SDI-12 operates at a very slow baud rate (only 1200 baud), the disabling of interrupts during transmission could possibly be for more time than you might expect. For that reason, please don't try to send and receive data through the SDI-12 library while transmitting other serial data or looking for other pin change interrupts. +Dive into the details of how this library works by reading the documentation in our [Arduino-SDI-12 wiki](https://github.com/StroudCenter/Arduino-SDI-12/wiki).s -## Getting Started -Read the [Arduino-SDI-12 wiki](https://github.com/StroudCenter/Arduino-SDI-12/wiki). +## Origins and Inherited Limitations + +This library was developed from the [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial) library that is a built-in [standard Arduino library](https://www.arduino.cc/en/Reference/Libraries). As such, it also shares many of the [limitations of SoftwareSerial](https://www.arduino.cc/en/Reference/SoftwareSerial). + +A primary limitation is that all [pin-change interrupts](https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/) are disabled during transmission, which can interfere with other processes and libraries that also use interrupts. This is particularly problematic for Arduino-SDI-12, because SDI-12 operates at a very slow baud rate (only 1200 baud). This translates to ~8.3 mS of "radio silence" from the processor for each character that goes in or out via SDI-12, which adds up to ~380-810ms per command! For that reason, avoid using the default "master" branch of this library to send and receive data via SDI-12 while also transmitting other serial data or while looking for other pin change interrupts. We have created [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) of this library (read below) to overcome such limitations when used in combination with alternate variants of SoftwareSerial. + +## Compatibility Considerations + +This library has been tested with an Arduino Uno (AtMega328p), EnviroDIY Mayfly (AtMega1284p), Adafruit Feather 32u4 (AtMega32u4, identical to Arduino Leonardo), and an Adafruit Feather M0 (SAMD21G18, identical to Arduino Zero). + +Not all data pins are available for use with this Arduino-SDI-12 library. Pin availability depends on the micro-controller. These pins will work on those processors: + +* **AtMega328p / Arduino Uno:** All pins +* **AtMega1284p / EnviroDIY Mayfly:** All pins +* **ATmega2560 / Arduino Mega or Mega 2560:** 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69) +* **AtMega32u4 / Arduino Leonardo or Adafruit Feather:** 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI) +* **SAMD21G18 / Arduino Zero:** All pins (except 4 on the zero) + +Note that not all of these pins are available with our [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches), below. + + +## Variants and Branches +As we've described, the default "master" branch of this library will conflict with SoftwareSerial and any other library that monopolizes all pin change interrupt vectors for all AVR boards. To allow simultaneous use of Arduino-SDI-12 and SoftwareSerial, we have created additional variants of these libraries that we maintain as separate branches of this repository. For background information, my be helpful to read our [Overview of Interrupts](https://github.com/EnviroDIY/Arduino-SDI-12/wiki/2b.-Overview-of-Interrupts) wiki page or this [Arduino Pin Change Interrupts article](https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/). + +#### EnviroDIY_SDI12 +EnviroDIY_SDI12 is the default master branch of this repository. It controls and monopolizes all pin change interrupt vectors, and can therefore have conflicts with any variant of SoftwareSerial and other libraries that use interrupts. + +#### EnviroDIY_SDI12_PCINT3 +EnviroDIY_SDI12_PCINT3 is in the Mayfly branch of this repository, and was historically was called "SDI12_mod". It's been cropped to only control interrupt vector 3, or PCINT3 (D), which on the Mayfly (or Sodaq Mbili) corresponds to Pins D0-D7. +It is designed to be compatible with EnviroDIY_SoftwareSerial_PCINT12 library, which which has been modified to only control interupt vectors 1 & 2, which on the Mayfly corresponds to pins PCINT1 (B) = Pins D08-D15; PCINT2 (C) = Pins D16-D23. +Note that different AtMega1284p boards have a different mapping from the physical PIN numbers to the listed digital PIN numbers that are printed on the board. For more infomation, see the [Pin/Port Bestiary wiki page for the Enable Interrupt library](https://github.com/GreyGnome/EnableInterrupt/wiki/Usage#PIN__PORT_BESTIARY). + +#### EnviroDIY_SDI12_ExtInts +EnviroDIY_SDI12_ExtInts is the ExtInt branch of this repository. It doesn't control any of the interrupts, but instead relies on an external interrupt management library (like [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt)) to assign the SDI-12 receive data function to the right pin. This is the least stable because there's some extra delay because the external library is involved. It's also the easiest to get working in combination with any other pin change interrupt based library. It can be paired with the EnviroDIY_SoftwareSerial_PCINT12 or the EnviroDIY_SoftwareSerial_ExtInts libraries. + +If you would like to use a different pin change interrupt library, uncomment the line ```#define SDI12_EXTERNAL_PCINT``` in SDI12.h and recompile the library. Then, in your own code call `SDI12::handleInterrupt()` as the interrupt for the SDI12 pin using the other interrupt library. Example j shows doing this in GreyGnome's [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) library. + ## Contribute Open an [issue](https://github.com/EnviroDIY/Arduino-SDI-12/issues) to suggest and discuss potential changes/additions. @@ -45,9 +79,12 @@ Documentation is licensed as [Creative Commons Attribution-ShareAlike 4.0](https ## Credits [EnviroDIY](http://envirodiy.org/)™ is presented by the Stroud Water Research Center, with contributions from a community of enthusiasts sharing do-it-yourself ideas for environmental science and monitoring. -[Kevin M. Smith](https://github.com/Kevin-M-Smith) is the primary developer of the Arduino-SDI-12 library, with input from [S. Hicks](https://github.com/s-hicks2) and many [other contributors](https://github.com/EnviroDIY/Arduino-SDI-12/graphs/contributors). +[Kevin M. Smith](https://github.com/Kevin-M-Smith) was the primary developer of the Arduino-SDI-12 library, with input from [S. Hicks](https://github.com/s-hicks2) and [Anthony Aufdenkampe](https://github.com/aufdenkampe). + +[Sara Damiano](https://github.com/SRGDamia1) is now the primary maintainer, with input from many [other contributors](https://github.com/EnviroDIY/Arduino-SDI-12/graphs/contributors). This project has benefited from the support from the following funders: * National Science Foundation, awards [EAR-0724971](http://www.nsf.gov/awardsearch/showAward?AWD_ID=0724971), [EAR-1331856](http://www.nsf.gov/awardsearch/showAward?AWD_ID=1331856), [ACI-1339834](http://www.nsf.gov/awardsearch/showAward?AWD_ID=1339834) +* William Penn Foundation, grant 158-15 * Stroud Water Research Center endowment From d8a7d67c2ed7cc0cdc6652b3c69bd977a4d70861 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Tue, 27 Mar 2018 23:13:24 -0400 Subject: [PATCH 06/26] Lots more notes, hopefully handling the parity bit correctly --- src/SDI12.cpp | 77 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index ef85b13..abcfc44 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -138,10 +138,10 @@ static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in // 1200 baud = 1200 bits/second ~ 833.333 µs/bit // 830 seems to be a reliable value given the overhead of the call. static const uint8_t rxWindowWidth = 0; // A fudge factor to make things work -static const uint8_t allBitsComplete = 0b11111111; +static const uint8_t WAITING_FOR_START_BIT = 0b11111111; static uint16_t prevBitMicros; // previous RX transition in micros -static uint8_t rxBitsCompleted; // 0: got start bit; >0: bits rcvd +static uint8_t rxState; // 0: got start bit; >0: bits rcvd static uint8_t rxMask; // bit mask for building received character static uint8_t rxValue; // character being built @@ -865,7 +865,7 @@ void SDI12::handleInterrupt(){ // 7.2 - Creates a blank slate of bits for an incoming character void SDI12::startChar() { - rxBitsCompleted = 0; // got a start bit + rxState = 0; // got a start bit rxMask = 0b00000001; // bit mask, lsb first rxValue = 0b00000000; // RX character to be, a blank slate @@ -874,32 +874,46 @@ void SDI12::startChar() // 7.3 - The actual interrupt service routine void SDI12::receiveISR() { - uint16_t thisBitMicros = micros(); // time of this data transition in micros (plus ISR latency) + uint16_t thisBitMicros = micros(); // time of this data transition in micros (plus ISR latency) uint8_t pinLevel = digitalRead(_dataPin); // current RX data level // Check if we're ready for a start bit, and if this could possibly be it // Otherwise, just ignore the interrupt and exit - if (rxBitsCompleted == allBitsComplete) { - if (pinLevel == LOW) return; // it just became low so not a start bit, exit - startChar(); + if (rxState == WAITING_FOR_START_BIT) { + if (pinLevel == LOW) return; // it just changed from high to low so not the end of a start bit, exit + startChar(); // create an empty character and a new mask with a 1 in the lowest place } - // if we aren't ready for a start bit, it means it has already come in and - // this new change is from a data, parity, or stop bit + // if the character is incomplete, and this is not a start bit, + // then this change is from a data, parity, or stop bit else { // check how many bit times have passed since the last change + // the rxWindowWidth is just a fudge factor uint16_t rxBits = (thisBitMicros - prevBitMicros + rxWindowWidth)/bitWidth_micros; - // calculate how many bits should be left, ignoring parity bit and stop bit - uint8_t bitsLeft = 8 - rxBitsCompleted; - // mark a new character started if more bits have been received than should be left + // calculate how many *data+parity* bits should be left + // We know the start bit is past and are ignoring the stop bit (which will be low) + // We have to treat the parity bit as a data bit because we don't know its state + uint8_t bitsLeft = 9 - rxState; + // note that a new character *may* have started if more bits have been + // received than should be left. + // This will also happen if the parity bit is 1 or the last bit(s) of the + // character and the parity bit are all 1's. bool nextCharStarted = (rxBits > bitsLeft); - // check how many bits should have been sent since the last change - // translation: if nextCharStarted bitsThisFrame = bitsLeft, else bitsThisFrame = rxBits - uint8_t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; - // Advance the receive state by that many bits - rxBitsCompleted += bitsThisFrame; + // check how many data+parity bits have been sent in this frame + // If the total number of bits in this frame is more than the number of data+parity + // bits remaining in the character, then the number of data+parity bits is equal + // to the number of bits remaining for the character and partiy. If the total + // number of bits in this frame is less than the number of data bits left + // for the character and parity, then the number of data+parity bits received + // in this frame is equal to the total number of bits received in this frame. + // translation: + // if nextCharStarted then bitsThisFrame = bitsLeft + // else bitsThisFrame = rxBits + uint8_t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; + // Tick up the rxState by that many bits + rxState += bitsThisFrame; // Set all the bits received between the last change and this change // If the current state is HIGH (and it just became so), then all bits between @@ -907,32 +921,35 @@ void SDI12::receiveISR() if (pinLevel == HIGH) { // back fill previous bits with 1's (inverse logic - LOW = 1) while (bitsThisFrame-- > 0) { - rxValue |= rxMask; - rxMask = rxMask << 1; + rxValue |= rxMask; // Add a 1 to the LSB/right-most place + rxMask = rxMask << 1; // Shift the 1 in the mask up by one position } - rxMask = rxMask << 1; + rxMask = rxMask << 1; // Set the last bit received as 1 } // If the current state is LOW (and it just became so), then this bit is LOW // but all bits between the last change and now must have been HIGH else { // pinLevel==LOW // previous bits were 0's so only this bit is a 1 (inverse logic - LOW = 1) - rxMask = rxMask << (bitsThisFrame-1); - rxValue |= rxMask; + rxMask = rxMask << (bitsThisFrame-1); // Shift the 1 in the mask up by the number of bits past + rxValue |= rxMask; // Add that shifted one to the character being created } - // After setting bits, if this is the 7th bit, parity bit, or stop bit - // then the character is complete. - if (rxBitsCompleted > 6) { + // If this was the 8th or more bit then the character is complete. + if (rxState > 7) { + rxValue = rxValue >> 1; // Shift the received value down to throw away the parity bit charToBuffer(rxValue); // Put the finished character into the buffer - if ((pinLevel == HIGH) || !nextCharStarted) { - rxBitsCompleted = allBitsComplete; + // if we just switched to low, or we haven't exceeded the number of bits forcing + // a new character, then this can't be the stop bit, but we do know the character + // itself is finished, so we can start looking for a new start bit. + if ((pinLevel == LOW) || !nextCharStarted) { + rxState = WAITING_FOR_START_BIT; // DISABLE STOP BIT TIMER } else { - // The last char ended with 1's, so this 0 is actually - // the start bit of the next character. - + // If we just switched to high, or we've past the number of bits to be + // into the next character, the last character must have ended with 1's + // and this new 0 is actually the start bit of the next character. startChar(); } } From b92cbfb3f755c08a13446aa21f2a18efd97f0b4a Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 28 Mar 2018 15:16:38 -0400 Subject: [PATCH 07/26] Just isn't working.. --- src/SDI12.cpp | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index abcfc44..a37375e 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -136,7 +136,6 @@ SDI12 *SDI12::_activeObject = NULL; // 0.3 pointer to active SDI12 object static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in microseconds // 1200 baud = 1200 bits/second ~ 833.333 µs/bit - // 830 seems to be a reliable value given the overhead of the call. static const uint8_t rxWindowWidth = 0; // A fudge factor to make things work static const uint8_t WAITING_FOR_START_BIT = 0b11111111; @@ -302,6 +301,7 @@ void SDI12::setState(SDI12_STATES state){ pinMode(_dataPin,INPUT); // Pin mode = input interrupts(); // Enable general interrupts setPinInterrupts(true); // Enable rx inerrupts on data pin + rxState = WAITING_FOR_START_BIT; break; } default: // DISABLED or ENABLED @@ -839,16 +839,6 @@ be a change from LOW to HIGH. + If this isn't a start bit, and a new character has been started, figure out where in the character we are at this change and fill out bits accordingly. -The loop runs from i=0x1 (hexadecimal notation for 00000001) to i<0x80 -(hexadecimal notation for 10000000). So the loop effectively uses the -masks following masks: 00000001 -00000010 -00000100 -00001000 -00010000 -00100000 -01000000 and their inverses. - Here we use an if / else structure that helps to balance the time it takes to either a HIGH vs a LOW, and helps maintain a constant timing. @@ -880,8 +870,12 @@ void SDI12::receiveISR() // Check if we're ready for a start bit, and if this could possibly be it // Otherwise, just ignore the interrupt and exit if (rxState == WAITING_FOR_START_BIT) { - if (pinLevel == LOW) return; // it just changed from high to low so not the end of a start bit, exit - startChar(); // create an empty character and a new mask with a 1 in the lowest place + // If it is low it's not a start bit, exit + // Inverse logic start bit = HIGH + if (pinLevel == LOW) return; + // If it is HIGH, this should be a start bit + // Thus set the rxStat to 0, create an empty character, and a new mask with a 1 in the lowest place + startChar(); } // if the character is incomplete, and this is not a start bit, @@ -924,7 +918,7 @@ void SDI12::receiveISR() rxValue |= rxMask; // Add a 1 to the LSB/right-most place rxMask = rxMask << 1; // Shift the 1 in the mask up by one position } - rxMask = rxMask << 1; // Set the last bit received as 1 + rxMask = rxMask << 1; // Shift the 1 in the mask up by one more position } // If the current state is LOW (and it just became so), then this bit is LOW // but all bits between the last change and now must have been HIGH @@ -934,27 +928,28 @@ void SDI12::receiveISR() rxValue |= rxMask; // Add that shifted one to the character being created } - // If this was the 8th or more bit then the character is complete. + // If this was the 8th or more bit then the character and parity are complete. if (rxState > 7) { - rxValue = rxValue >> 1; // Shift the received value down to throw away the parity bit + rxValue &= 0b01111111; // Throw away the parity bit charToBuffer(rxValue); // Put the finished character into the buffer - // if we just switched to low, or we haven't exceeded the number of bits forcing - // a new character, then this can't be the stop bit, but we do know the character - // itself is finished, so we can start looking for a new start bit. + + // if this is LOW, or we haven't exceeded the number of bits in a + // character (but have gotten all the data bits, then this should be a + // stop bit and we can start looking for a new start bit. if ((pinLevel == LOW) || !nextCharStarted) { rxState = WAITING_FOR_START_BIT; // DISABLE STOP BIT TIMER } else { - // If we just switched to high, or we've past the number of bits to be - // into the next character, the last character must have ended with 1's - // and this new 0 is actually the start bit of the next character. + // If we just switched to HIGH, or we've exceeded the total number of + // bits in a character, then the character must have ended with 1's/LOW, + // and this new 0/HIGH is actually the start bit of the next character. startChar(); } } - prevBitMicros = thisBitMicros; // remember time stamp of this change! } + prevBitMicros = thisBitMicros; // remember time stamp of this change! } // 7.4 - Put a new character in the buffer From b785214a5d28f14e5a4bee1d7c9a202ebbce5e11 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 10:30:45 -0400 Subject: [PATCH 08/26] Testing using 8-bit timer 2 --- src/SDI12.cpp | 36 ++++++++++++----- src/SDI12_boards.h | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 src/SDI12_boards.h diff --git a/src/SDI12.cpp b/src/SDI12.cpp index a37375e..c9fa281 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -129,6 +129,7 @@ SDI-12.org, official site of the SDI-12 Support Group. */ #include "SDI12.h" // 0.1 header file for this library +#include "SDI12_boards.h" #define SDI12_BUFFER_SIZE 64 // 0.2 max RX buffer size @@ -136,14 +137,28 @@ SDI12 *SDI12::_activeObject = NULL; // 0.3 pointer to active SDI12 object static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in microseconds // 1200 baud = 1200 bits/second ~ 833.333 µs/bit -static const uint8_t rxWindowWidth = 0; // A fudge factor to make things work + +static const uint8_t txBitWidth = TICKS_PER_BIT; +static const uint8_t rxWindowWidth = 5; // A fudge factor to make things work +static const uint8_t bitsPerTick_Q10 = BITS_PER_TICK_Q10; static const uint8_t WAITING_FOR_START_BIT = 0b11111111; -static uint16_t prevBitMicros; // previous RX transition in micros +static uint16_t prevBitTCNT; // previous RX transition in micros static uint8_t rxState; // 0: got start bit; >0: bits rcvd static uint8_t rxMask; // bit mask for building received character static uint8_t rxValue; // character being built +static uint16_t mul8x8to16(uint8_t x, uint8_t y) +{return x*y;} + +//.......................................... + +static uint16_t bitTimes( uint8_t dt ) +{ + return mul8x8to16( dt + rxWindowWidth, bitsPerTick_Q10 ) >> 10; + +} // bitTimes + /* =========== 1. Buffer Setup ============================================ @@ -300,7 +315,7 @@ void SDI12::setState(SDI12_STATES state){ digitalWrite(_dataPin,LOW); // Pin state = low pinMode(_dataPin,INPUT); // Pin mode = input interrupts(); // Enable general interrupts - setPinInterrupts(true); // Enable rx inerrupts on data pin + setPinInterrupts(true); // Enable Rx interrupts on data pin rxState = WAITING_FOR_START_BIT; break; } @@ -367,15 +382,17 @@ void SDI12::begin(){ // setState(HOLDING); setActive(); // SDI-12 protocol says sensors must respond within 15 milliseconds - // We'll bump that up to 100, just for good measure, but we don't want to + // We'll bump that up to 150, just for good measure, but we don't want to // wait the whole stream default of 1s for a response. - setTimeout(100); + setTimeout(150); // Because SDI-12 is mostly used for environmental sensors, we want to be able // to distinguish between the '0' that parseInt and parseFloat usually return // on timeouts and a real measured 0 value. So we force the timeout response // to be -9999, which is not a common value for most variables measured by // in-site environmental sensors. setTimeoutValue(-9999); + // Set up the prescaler as needed for timers + CONFIG_TIMER_PRESCALE(); } void SDI12::begin(uint8_t dataPin){ _dataPin = dataPin; @@ -498,7 +515,7 @@ void SDI12::writeChar(uint8_t out) // 4.3 - this function sends out the characters of the String cmd, one by one void SDI12::sendCommand(String &cmd) { - wakeSensors(); // wake up sensors + wakeSensors(); // set state to transmitting and send break/marking for (int unsigned i = 0; i < cmd.length(); i++){ writeChar(cmd[i]); // write each character } @@ -858,13 +875,12 @@ void SDI12::startChar() rxState = 0; // got a start bit rxMask = 0b00000001; // bit mask, lsb first rxValue = 0b00000000; // RX character to be, a blank slate - } // startChar // 7.3 - The actual interrupt service routine void SDI12::receiveISR() { - uint16_t thisBitMicros = micros(); // time of this data transition in micros (plus ISR latency) + uint8_t thisBitTCNT = TCNTX; // time of this data transition (plus ISR latency) uint8_t pinLevel = digitalRead(_dataPin); // current RX data level // Check if we're ready for a start bit, and if this could possibly be it @@ -884,7 +900,7 @@ void SDI12::receiveISR() // check how many bit times have passed since the last change // the rxWindowWidth is just a fudge factor - uint16_t rxBits = (thisBitMicros - prevBitMicros + rxWindowWidth)/bitWidth_micros; + uint16_t rxBits = bitTimes(thisBitTCNT - prevBitTCNT); // calculate how many *data+parity* bits should be left // We know the start bit is past and are ignoring the stop bit (which will be low) // We have to treat the parity bit as a data bit because we don't know its state @@ -949,7 +965,7 @@ void SDI12::receiveISR() } } } - prevBitMicros = thisBitMicros; // remember time stamp of this change! + prevBitTCNT = thisBitTCNT; // remember time stamp of this change! } // 7.4 - Put a new character in the buffer diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h new file mode 100644 index 0000000..db3a830 --- /dev/null +++ b/src/SDI12_boards.h @@ -0,0 +1,98 @@ +// Most 'standard' AVR boards +// +#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || \ + defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || \ + defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || \ + defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + + #define TCNTX TCNT2 // Using Timer 2 + + #if F_CPU == 16000000L + #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x07) + // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & OC2B disconnected + // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 + #define TICKS_PER_BIT 13 + // 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit + #define BITS_PER_TICK_Q10 79 + // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #elif F_CPU == 8000000L + #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x06) + // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected + // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22, & 20 on - prescaler set to CK/256 + #define TICKS_PER_BIT 26 + // 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit + #define BITS_PER_TICK_Q10 39 + // 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 + // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0b00000111) // Set the prescaler to 1024 + // #define TICKS_PER_BIT 6 + // // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' + // // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit + // #define BITS_PER_TICK_Q10 157 + // // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 + #endif + + +// ATtiny boards (ie, adafruit trinket) +// +#elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) + #define TCNTX TCNT1 \ // Using Timer 1 + + #if F_CPU == 16000000L + #define CONFIG_TIMER_PRESCALE() (TCCR1A = 0b00001011) // Set the prescaler to 1024 + #define TICKS_PER_BIT 13 + // 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit + #define BITS_PER_TICK_Q10 79 + // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #elif F_CPU == 8000000L + #define CONFIG_TIMER_PRESCALE() (TCCR1A = 0b00001010) // Set the prescaler to 512 + #define TICKS_PER_BIT 13 + // 8MHz / 512 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit + #define BITS_PER_TICK_Q10 79 + // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #endif + + +// Arduino Leonardo & Yun and other 32U4 boards +// +#elif defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_LEONARDO) || defined(__AVR_ATmega32U4__) + #define TCNTX TCNT4 // Using Timer 4 + + #if F_CPU == 16000000L + #define CONFIG_TIMER_PRESCALE() (TCCR4A = 0x00, TCCR4B = 0x0B, TCCR4C = 0x00, TCCR4D = 0x00, TCCR4E = 0x00) + // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected + // TCCR4B = 0x0B = 0b00001011 - Clock Select bits 43, 41, & 40 on - prescaler set to CK/1024 + // TCCR4C = 0x00 = "normal" operation - Normal port operation, OC4D0 disconnected + // TCCR4D = 0x00 = No fault protection + // TCCR4E = 0x00 = No register locks or overrides + #define TICKS_PER_BIT 13 + // 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit + #define BITS_PER_TICK_Q10 79 + // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #elif F_CPU == 8000000L + #define CONFIG_TIMER_PRESCALE() (TCCR4A = 0x00, TCCR4B = 0x0A, TCCR4C = 0x00, TCCR4D = 0x00, TCCR4E = 0x00) + // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected + // TCCR4B = 0x0A = 0b00001010 - Clock Select bits 43 & 41 on - prescaler set to CK/512 + // TCCR4C = 0x00 = "normal" operation - Normal port operation, OC4D0 disconnected + // TCCR4D = 0x00 = No fault protection + // TCCR4E = 0x00 = No register locks or overrides + #define TICKS_PER_BIT 13 + // 8MHz / 512 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit + #define BITS_PER_TICK_Q10 79 + // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #endif + + +// Arduino Zero other SAMD21 boards +// +#elif defined(ARDUINO_SAMD_ZERO) || defined(__SAMD21G18A__) + +// Unknown board +#else +#error "Please define your board timer and pins" +#endif From 91399e55fe52ef01329dfe6cd72f08544867caa7 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 11:08:15 -0400 Subject: [PATCH 09/26] Some debug prints --- examples/d_simple_logger/d_simple_logger.ino | 2 +- examples/e_simple_parsing/e_simple_parsing.ino | 2 +- src/SDI12.cpp | 13 ++++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/d_simple_logger/d_simple_logger.ino b/examples/d_simple_logger/d_simple_logger.ino index 5d7500f..f191383 100644 --- a/examples/d_simple_logger/d_simple_logger.ino +++ b/examples/d_simple_logger/d_simple_logger.ino @@ -61,7 +61,7 @@ #include -#define SERIAL_BAUD 57600 // The baud rate for the output serial port +#define SERIAL_BAUD 115200 // The baud rate for the output serial port #define DATA_PIN 7 // The pin of the SDI-12 data bus #define POWER_PIN 22 // The sensor power pin (or -1 if not switching power) diff --git a/examples/e_simple_parsing/e_simple_parsing.ino b/examples/e_simple_parsing/e_simple_parsing.ino index b7934f1..13281fc 100644 --- a/examples/e_simple_parsing/e_simple_parsing.ino +++ b/examples/e_simple_parsing/e_simple_parsing.ino @@ -71,7 +71,7 @@ Other notes: #include -#define SERIAL_BAUD 57600 // The baud rate for the output serial port +#define SERIAL_BAUD 115200 // The baud rate for the output serial port #define DATA_PIN 7 // The pin of the SDI-12 data bus #define POWER_PIN 22 // The sensor power pin (or -1 if not switching power) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index c9fa281..d9f2c5b 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -882,16 +882,25 @@ void SDI12::receiveISR() { uint8_t thisBitTCNT = TCNTX; // time of this data transition (plus ISR latency) uint8_t pinLevel = digitalRead(_dataPin); // current RX data level + // Serial.print(pinLevel); + // Serial.print('@'); + // Serial.print(TCNTX); + // Serial.print('='); // Check if we're ready for a start bit, and if this could possibly be it // Otherwise, just ignore the interrupt and exit if (rxState == WAITING_FOR_START_BIT) { // If it is low it's not a start bit, exit // Inverse logic start bit = HIGH - if (pinLevel == LOW) return; + if (pinLevel == LOW) + { + // Serial.println('X'); + return; + } // If it is HIGH, this should be a start bit // Thus set the rxStat to 0, create an empty character, and a new mask with a 1 in the lowest place startChar(); + // Serial.println('*'); } // if the character is incomplete, and this is not a start bit, @@ -901,6 +910,7 @@ void SDI12::receiveISR() // check how many bit times have passed since the last change // the rxWindowWidth is just a fudge factor uint16_t rxBits = bitTimes(thisBitTCNT - prevBitTCNT); + // Serial.println(rxBits); // calculate how many *data+parity* bits should be left // We know the start bit is past and are ignoring the stop bit (which will be low) // We have to treat the parity bit as a data bit because we don't know its state @@ -946,6 +956,7 @@ void SDI12::receiveISR() // If this was the 8th or more bit then the character and parity are complete. if (rxState > 7) { + // Serial.println(rxValue, BIN); rxValue &= 0b01111111; // Throw away the parity bit charToBuffer(rxValue); // Put the finished character into the buffer From 30eb3ccab075ecdd8459fcce988a7c6e053e3bc1 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 11:17:46 -0400 Subject: [PATCH 10/26] Seems to be working! --- src/SDI12.cpp | 2 +- src/SDI12_boards.h | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index d9f2c5b..32e9ff4 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -912,7 +912,7 @@ void SDI12::receiveISR() uint16_t rxBits = bitTimes(thisBitTCNT - prevBitTCNT); // Serial.println(rxBits); // calculate how many *data+parity* bits should be left - // We know the start bit is past and are ignoring the stop bit (which will be low) + // We know the start bit is past and are ignoring the stop bit (which will be LOW/1) // We have to treat the parity bit as a data bit because we don't know its state uint8_t bitsLeft = 9 - rxState; // note that a new character *may* have started if more bits have been diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index db3a830..5616173 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -19,18 +19,20 @@ #elif F_CPU == 8000000L #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x06) // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected - // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22, & 20 on - prescaler set to CK/256 + // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - prescaler set to CK/256 #define TICKS_PER_BIT 26 // 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' // (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit #define BITS_PER_TICK_Q10 39 // 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 - // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0b00000111) // Set the prescaler to 1024 + // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0x07) // Set the prescaler to 1024 + // // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected + // // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 // #define TICKS_PER_BIT 6 - // // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' - // // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit + // // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' + // // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit // #define BITS_PER_TICK_Q10 157 - // // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 + // // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 #endif From cc7f2c65842a29d8f623a77b4450d083550097ec Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 11:19:41 -0400 Subject: [PATCH 11/26] I'd rather use slower prescaler @ 8MHz --- src/SDI12_boards.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index 5616173..df8e15a 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -17,22 +17,22 @@ #define BITS_PER_TICK_Q10 79 // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 #elif F_CPU == 8000000L - #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x06) - // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected - // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - prescaler set to CK/256 - #define TICKS_PER_BIT 26 - // 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' - // (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit - #define BITS_PER_TICK_Q10 39 - // 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 - // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0x07) // Set the prescaler to 1024 + // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x06) // // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected - // // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 - // #define TICKS_PER_BIT 6 - // // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' - // // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit - // #define BITS_PER_TICK_Q10 157 - // // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 + // // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - prescaler set to CK/256 + // #define TICKS_PER_BIT 26 + // // 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' + // // (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit + // #define BITS_PER_TICK_Q10 39 + // // 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 + #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0x07) // Set the prescaler to 1024 + // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected + // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 + #define TICKS_PER_BIT 6 + // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit + #define BITS_PER_TICK_Q10 157 + // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 #endif From 42c6bbcbe8a9ecf3d8e760a29326f6cc6dfbea88 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 11:35:46 -0400 Subject: [PATCH 12/26] Tiny change in break/marking --- src/SDI12.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index 32e9ff4..c17d9d5 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -137,6 +137,10 @@ SDI12 *SDI12::_activeObject = NULL; // 0.3 pointer to active SDI12 object static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in microseconds // 1200 baud = 1200 bits/second ~ 833.333 µs/bit +static const uint16_t lineBreak_micros = (uint16_t) 12100; // The required "break" before sending commands + // break >= 12ms +static const uint16_t marking_micros = (uint16_t) 8330; // The required mark before a command or response + // marking >= 8.33ms static const uint8_t txBitWidth = TICKS_PER_BIT; static const uint8_t rxWindowWidth = 5; // A fudge factor to make things work @@ -485,9 +489,9 @@ recorder for another SDI-12 device void SDI12::wakeSensors(){ setState(TRANSMITTING); digitalWrite(_dataPin, HIGH); - delayMicroseconds(12100); // Required break of 12 milliseconds + delayMicroseconds(lineBreak_micros); // Required break of 12 milliseconds digitalWrite(_dataPin, LOW); - delayMicroseconds(8400); // Required marking of 8.33 milliseconds + delayMicroseconds(marking_micros); // Required marking of 8.33 milliseconds } // 4.2 - this function writes a character out on the data line @@ -548,7 +552,7 @@ void SDI12::sendResponse(String &resp) { setState(TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); - delayMicroseconds(8330); // 8.33 ms marking before response + delayMicroseconds(marking_micros); // 8.33 ms marking before response for (int unsigned i = 0; i < resp.length(); i++){ writeChar(resp[i]); // write each character } @@ -559,7 +563,7 @@ void SDI12::sendResponse(const char *resp) { setState(TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); - delayMicroseconds(8330); // 8.33 ms marking before response + delayMicroseconds(marking_micros); // 8.33 ms marking before response for (int unsigned i = 0; i < strlen(resp); i++){ writeChar(resp[i]); // write each character } @@ -570,7 +574,7 @@ void SDI12::sendResponse(FlashString resp) { setState(TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); - delayMicroseconds(8330); // 8.33 ms marking before response + delayMicroseconds(marking_micros); // 8.33 ms marking before response for (int unsigned i = 0; i < strlen_P((PGM_P)resp); i++){ writeChar((char)pgm_read_byte((const char *)resp + i)); // write each character } From 8e0a2d5571ec413dca34fbc38d9c038a0f008f02 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 11:37:47 -0400 Subject: [PATCH 13/26] update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64c4459..880437a 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ Temporary Items .gcc-flags.json lib/readme.txt platformio.ini + +examples/debug_print/ From 50926cccc43e6ee48d0274e95bfcf75d18b12dc7 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 16:54:20 -0400 Subject: [PATCH 14/26] Added support for M0 and tested a few others --- README.md | 14 ++++++++++---- src/SDI12.cpp | 34 ++++++++++++++++++++++++++++++++-- src/SDI12_boards.h | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 75c3fcb..a52a5fd 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,15 @@ Dive into the details of how this library works by reading the documentation in ## Origins and Inherited Limitations -This library was developed from the [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial) library that is a built-in [standard Arduino library](https://www.arduino.cc/en/Reference/Libraries). As such, it also shares many of the [limitations of SoftwareSerial](https://www.arduino.cc/en/Reference/SoftwareSerial). +This library was developed from the [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial) library that is a built-in [standard Arduino library](https://www.arduino.cc/en/Reference/Libraries). It was further modified to use a timer to improve read stability using the same interrupt logic as [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial). -A primary limitation is that all [pin-change interrupts](https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/) are disabled during transmission, which can interfere with other processes and libraries that also use interrupts. This is particularly problematic for Arduino-SDI-12, because SDI-12 operates at a very slow baud rate (only 1200 baud). This translates to ~8.3 mS of "radio silence" from the processor for each character that goes in or out via SDI-12, which adds up to ~380-810ms per command! For that reason, avoid using the default "master" branch of this library to send and receive data via SDI-12 while also transmitting other serial data or while looking for other pin change interrupts. We have created [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) of this library (read below) to overcome such limitations when used in combination with alternate variants of SoftwareSerial. +The most obvious "limitation" is that this library will conflict with all other libraries that make use of pin change interrupts. You will be unable to compile them together. Some other libraries using pin change interrupts include [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial), [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial), [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt/), [PinChangeInt](https://playground.arduino.cc/Main/PinChangeInt), [Servo](https://www.arduino.cc/en/Reference/Servo), and quite a number of other libraries. See the notes under [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) below for advice in using this library in combination with such libraries. + +Another non-trivial, but hidden limitation is that _all_ interrupts are disabled during transmission, which can interfere with other processes. That includes other pin-change interrupts, clock/timer interrupts, external interrupts, and every other type of processor interrupt. This is particularly problematic for Arduino-SDI-12, because SDI-12 operates at a very slow baud rate (only 1200 baud). This translates to ~8.3 mS of "radio silence" from the processor for each character that goes out via SDI-12, which adds up to ~380-810ms per command! Interrupts are enabled for the majority of the time while the processor is listening for responses. + +For most AVR boards, this library will also conflict with the [tone](https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/) function because of its utilization of timer 2. There will be no obvious compile error, but because SDI-12 and the tone library may use different clock-prescaler functions, the results for both might be rather unexpected. + +The Arduino Due, Teensy, and ESP8266/ESP32 boards are not supported at this time. If you are interested in adding support for those boards, feel free to send pull requests. ## Compatibility Considerations @@ -47,8 +53,8 @@ As we've described, the default "master" branch of this library will conflict wi EnviroDIY_SDI12 is the default master branch of this repository. It controls and monopolizes all pin change interrupt vectors, and can therefore have conflicts with any variant of SoftwareSerial and other libraries that use interrupts. #### EnviroDIY_SDI12_PCINT3 -EnviroDIY_SDI12_PCINT3 is in the Mayfly branch of this repository, and was historically was called "SDI12_mod". It's been cropped to only control interrupt vector 3, or PCINT3 (D), which on the Mayfly (or Sodaq Mbili) corresponds to Pins D0-D7. -It is designed to be compatible with EnviroDIY_SoftwareSerial_PCINT12 library, which which has been modified to only control interupt vectors 1 & 2, which on the Mayfly corresponds to pins PCINT1 (B) = Pins D08-D15; PCINT2 (C) = Pins D16-D23. +EnviroDIY_SDI12_PCINT3 is in the Mayfly branch of this repository, and was historically was called "SDI12_mod". It's been cropped to only control interrupt vector 3, or PCINT3 (D), which on the Mayfly (or Sodaq Mbili) corresponds to Pins D0-D7. +It is designed to be compatible with EnviroDIY_SoftwareSerial_PCINT12 library, which which has been modified to only control interupt vectors 1 & 2, which on the Mayfly corresponds to pins PCINT1 (B) = Pins D08-D15; PCINT2 (C) = Pins D16-D23. Note that different AtMega1284p boards have a different mapping from the physical PIN numbers to the listed digital PIN numbers that are printed on the board. For more infomation, see the [Pin/Port Bestiary wiki page for the Enable Interrupt library](https://github.com/GreyGnome/EnableInterrupt/wiki/Usage#PIN__PORT_BESTIARY). #### EnviroDIY_SDI12_ExtInts diff --git a/src/SDI12.cpp b/src/SDI12.cpp index c17d9d5..e895cd8 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -143,7 +143,7 @@ static const uint16_t marking_micros = (uint16_t) 8330; // The required mark be // marking >= 8.33ms static const uint8_t txBitWidth = TICKS_PER_BIT; -static const uint8_t rxWindowWidth = 5; // A fudge factor to make things work +static const uint8_t rxWindowWidth = RX_WINDOW_FUDGE; // A fudge factor to make things work static const uint8_t bitsPerTick_Q10 = BITS_PER_TICK_Q10; static const uint8_t WAITING_FOR_START_BIT = 0b11111111; @@ -396,7 +396,37 @@ void SDI12::begin(){ // in-site environmental sensors. setTimeoutValue(-9999); // Set up the prescaler as needed for timers - CONFIG_TIMER_PRESCALE(); + + #if defined(ARDUINO_SAMD_ZERO) || defined(__SAMD21G18A__) + // I would prefer to define this all as a macro, but for some reason it isn't working + // Select generic clock generator 4 (Arduino core uses 0-3) + // Most examples use this clock generator.. consider yourself warned! + // I would use a higher clock number, but some of the cores don't include them for some reason + REG_GCLK_GENDIV = GCLK_GENDIV_ID(4) | // Select Generic Clock Generator 4 + GCLK_GENDIV_DIV(3) ; // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + // Write the generic clock generator 5 configuration + REG_GCLK_GENCTRL = GCLK_GENCTRL_ID(4) | // Select GCLK4 + GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source + GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW + GCLK_GENCTRL_GENEN; // Enable the generic clock clontrol + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + // Feed GCLK4 to TC4 (also feeds to TC5, the two must have the same source) + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_GEN_GCLK4 | // Select Generic Clock Generator 4 + GCLK_CLKCTRL_CLKEN | // Enable the generic clock generator + GCLK_CLKCTRL_ID_TC4_TC5; // Feed the Generic Clock Generator 4 to TC4 and TC5 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024, 16MHz/1024 = 15.625kHz + TC_CTRLA_WAVEGEN_NFRQ | // Put the timer TC4 into normal frequency (NFRQ) mode + TC_CTRLA_MODE_COUNT8 | // Put the timer TC4 into 8-bit mode + TC_CTRLA_ENABLE; // Enable TC4 + while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization + #else + CONFIG_TIMER_PRESCALE();// Set up the generic clock (GCLK4) used to clock timers + #endif } void SDI12::begin(uint8_t dataPin){ _dataPin = dataPin; diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index df8e15a..3b19cdb 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -5,9 +5,11 @@ defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || \ defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + #define TIMER_IN_USE_STR "TCNT2" #define TCNTX TCNT2 // Using Timer 2 #if F_CPU == 16000000L + #define PRESCALE_IN_USE_STR "1024" #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x07) // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & OC2B disconnected // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 @@ -16,7 +18,9 @@ // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit #define BITS_PER_TICK_Q10 79 // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #define RX_WINDOW_FUDGE 2 #elif F_CPU == 8000000L + // #define PRESCALE_IN_USE_STR "256" // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x06) // // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected // // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - prescaler set to CK/256 @@ -25,6 +29,8 @@ // // (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit // #define BITS_PER_TICK_Q10 39 // // 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 + // #define RX_WINDOW_FUDGE 10 + #define PRESCALE_IN_USE_STR "1024" #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0x07) // Set the prescaler to 1024 // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 @@ -33,37 +39,47 @@ // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit #define BITS_PER_TICK_Q10 157 // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 + #define RX_WINDOW_FUDGE 5 #endif // ATtiny boards (ie, adafruit trinket) // #elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) - #define TCNTX TCNT1 \ // Using Timer 1 + + #define TIMER_IN_USE_STR "TCNT1" + #define TCNTX TCNT1 // Using Timer 1 #if F_CPU == 16000000L + #define PRESCALE_IN_USE_STR "1024" #define CONFIG_TIMER_PRESCALE() (TCCR1A = 0b00001011) // Set the prescaler to 1024 #define TICKS_PER_BIT 13 // 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit #define BITS_PER_TICK_Q10 79 // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #define RX_WINDOW_FUDGE 2 #elif F_CPU == 8000000L + #define PRESCALE_IN_USE_STR "512" #define CONFIG_TIMER_PRESCALE() (TCCR1A = 0b00001010) // Set the prescaler to 512 #define TICKS_PER_BIT 13 // 8MHz / 512 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit #define BITS_PER_TICK_Q10 79 // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #define RX_WINDOW_FUDGE 5 #endif // Arduino Leonardo & Yun and other 32U4 boards // #elif defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_LEONARDO) || defined(__AVR_ATmega32U4__) + + #define TIMER_IN_USE_STR "TCNT4" #define TCNTX TCNT4 // Using Timer 4 #if F_CPU == 16000000L + #define PRESCALE_IN_USE_STR "1024" #define CONFIG_TIMER_PRESCALE() (TCCR4A = 0x00, TCCR4B = 0x0B, TCCR4C = 0x00, TCCR4D = 0x00, TCCR4E = 0x00) // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected // TCCR4B = 0x0B = 0b00001011 - Clock Select bits 43, 41, & 40 on - prescaler set to CK/1024 @@ -75,7 +91,9 @@ // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit #define BITS_PER_TICK_Q10 79 // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #define RX_WINDOW_FUDGE 2 #elif F_CPU == 8000000L + #define PRESCALE_IN_USE_STR "512" #define CONFIG_TIMER_PRESCALE() (TCCR4A = 0x00, TCCR4B = 0x0A, TCCR4C = 0x00, TCCR4D = 0x00, TCCR4E = 0x00) // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected // TCCR4B = 0x0A = 0b00001010 - Clock Select bits 43 & 41 on - prescaler set to CK/512 @@ -87,6 +105,7 @@ // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit #define BITS_PER_TICK_Q10 79 // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #define RX_WINDOW_FUDGE 5 #endif @@ -94,6 +113,18 @@ // #elif defined(ARDUINO_SAMD_ZERO) || defined(__SAMD21G18A__) + #define TIMER_IN_USE_STR "GCLK4-TC4" + #define TCNTX REG_TC4_COUNT8_COUNT // Using Timer 4 + + #define PRESCALE_IN_USE_STR "3x1024" + #define TICKS_PER_BIT 13 + // 48MHz / 3 pre-prescaler = 16MHz + // 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit + #define BITS_PER_TICK_Q10 79 + // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + #define RX_WINDOW_FUDGE 2 + // Unknown board #else #error "Please define your board timer and pins" From 55d73f4f5f817387667b39aa70288ba4d98449c2 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 16:56:43 -0400 Subject: [PATCH 15/26] bump version --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index 0f003a0..5320ca4 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "Arduino-SDI-12", - "version": "1.0.6", + "version": "1.1.0", "keywords": "SDI-12, sdi12, communication, bus, sensor, Decagon", "description": "Arduino library for SDI-12 communications to a wide variety of environmental sensors.", "repository": diff --git a/library.properties b/library.properties index 2b45921..119cdbf 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino-SDI-12 -version=1.0.6 +version=1.1.0 author=Kevin M. Smith , Shannon Hicks maintainer=Sara Damiano sentence=Arduino library for SDI-12 communications to a wide variety of environmental sensors. From a075ac711761c30662e4b12f5adae4d51401a91b Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 16:59:25 -0400 Subject: [PATCH 16/26] Added other SAMD boards --- src/SDI12_boards.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index 3b19cdb..536c823 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -111,7 +111,7 @@ // Arduino Zero other SAMD21 boards // -#elif defined(ARDUINO_SAMD_ZERO) || defined(__SAMD21G18A__) +#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || defined(__SAMD21G18A__) || defined(__SAMD21J18A__) #define TIMER_IN_USE_STR "GCLK4-TC4" #define TCNTX REG_TC4_COUNT8_COUNT // Using Timer 4 From 814e05906f730c08989780bf1067e8eb4a7aa179 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 17:02:44 -0400 Subject: [PATCH 17/26] 1 more samd board --- src/SDI12_boards.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index 536c823..3106d1d 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -111,7 +111,8 @@ // Arduino Zero other SAMD21 boards // -#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || defined(__SAMD21G18A__) || defined(__SAMD21J18A__) +#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || \ + defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__ || defined(__SAMD21E18A__) #define TIMER_IN_USE_STR "GCLK4-TC4" #define TCNTX REG_TC4_COUNT8_COUNT // Using Timer 4 From 1ebc4bb7534f716e504c57707a88ded19150c7bf Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 17:05:16 -0400 Subject: [PATCH 18/26] Another SAMD bug --- library.json | 2 +- library.properties | 2 +- src/SDI12.cpp | 2 +- src/SDI12_boards.h | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/library.json b/library.json index 5320ca4..1d00777 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "Arduino-SDI-12", - "version": "1.1.0", + "version": "1.1.1", "keywords": "SDI-12, sdi12, communication, bus, sensor, Decagon", "description": "Arduino library for SDI-12 communications to a wide variety of environmental sensors.", "repository": diff --git a/library.properties b/library.properties index 119cdbf..0699e85 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino-SDI-12 -version=1.1.0 +version=1.1.1 author=Kevin M. Smith , Shannon Hicks maintainer=Sara Damiano sentence=Arduino library for SDI-12 communications to a wide variety of environmental sensors. diff --git a/src/SDI12.cpp b/src/SDI12.cpp index e895cd8..1bbe81a 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -397,7 +397,7 @@ void SDI12::begin(){ setTimeoutValue(-9999); // Set up the prescaler as needed for timers - #if defined(ARDUINO_SAMD_ZERO) || defined(__SAMD21G18A__) + #if defined(ARDUINO_ARCH_SAMD) // I would prefer to define this all as a macro, but for some reason it isn't working // Select generic clock generator 4 (Arduino core uses 0-3) // Most examples use this clock generator.. consider yourself warned! diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index 3106d1d..cc97b0c 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -118,6 +118,7 @@ #define TCNTX REG_TC4_COUNT8_COUNT // Using Timer 4 #define PRESCALE_IN_USE_STR "3x1024" + #define CONFIG_TIMER_PRESCALE() // This is done elsewhere #define TICKS_PER_BIT 13 // 48MHz / 3 pre-prescaler = 16MHz // 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' From a693a35b55aa96ea7d47b7a1f7e32b0b87a6a050 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 17:06:51 -0400 Subject: [PATCH 19/26] typo --- src/SDI12_boards.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index cc97b0c..7efd474 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -112,7 +112,7 @@ // Arduino Zero other SAMD21 boards // #elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || \ - defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__ || defined(__SAMD21E18A__) + defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__SAMD21E18A__) #define TIMER_IN_USE_STR "GCLK4-TC4" #define TCNTX REG_TC4_COUNT8_COUNT // Using Timer 4 From 3590f4e56e6c600e087e56245086e036bc6ec3ac Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 17:11:14 -0400 Subject: [PATCH 20/26] Another version bump --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index 1d00777..31845b8 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "Arduino-SDI-12", - "version": "1.1.1", + "version": "1.2.1", "keywords": "SDI-12, sdi12, communication, bus, sensor, Decagon", "description": "Arduino library for SDI-12 communications to a wide variety of environmental sensors.", "repository": diff --git a/library.properties b/library.properties index 0699e85..c784061 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino-SDI-12 -version=1.1.1 +version=1.2.1 author=Kevin M. Smith , Shannon Hicks maintainer=Sara Damiano sentence=Arduino library for SDI-12 communications to a wide variety of environmental sensors. From ea37265a2d065e50b5c44fa33164096d8e3755a8 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Wed, 4 Apr 2018 17:21:25 -0400 Subject: [PATCH 21/26] Fixed merge duplicate --- library.json | 2 +- library.properties | 2 +- src/SDI12.cpp | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/library.json b/library.json index 31845b8..e22d9d5 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "Arduino-SDI-12", - "version": "1.2.1", + "version": "1.2.3", "keywords": "SDI-12, sdi12, communication, bus, sensor, Decagon", "description": "Arduino library for SDI-12 communications to a wide variety of environmental sensors.", "repository": diff --git a/library.properties b/library.properties index c784061..9fd5695 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino-SDI-12 -version=1.2.1 +version=1.2.3 author=Kevin M. Smith , Shannon Hicks maintainer=Sara Damiano sentence=Arduino library for SDI-12 communications to a wide variety of environmental sensors. diff --git a/src/SDI12.cpp b/src/SDI12.cpp index 6bb5fcc..39780cc 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -431,10 +431,6 @@ void SDI12::begin(uint8_t dataPin){ _dataPin = dataPin; begin(); } -void SDI12::begin(uint8_t dataPin){ - _dataPin = dataPin; - begin(); -} // 3.4 End void SDI12::end() { setState(DISABLED); } From 9d9bb61e6b34a97bc9a10678b828ee3091d138ed Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Thu, 5 Apr 2018 11:18:47 -0400 Subject: [PATCH 22/26] Copied ReadMe --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a52a5fd..6437778 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Learn more, below, about this library's: Try running our [Example sketches](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples) with your Arduino board and SDI-12 sensor. -Dive into the details of how this library works by reading the documentation in our [Arduino-SDI-12 wiki](https://github.com/StroudCenter/Arduino-SDI-12/wiki).s +Dive into the details of how this library works by reading the documentation in our [Arduino-SDI-12 wiki](https://github.com/StroudCenter/Arduino-SDI-12/wiki). [Note: this is currently somewhat out of date. Sorry!] ## Origins and Inherited Limitations From 78e3351033cd0cf53f03a23ceedb7ea528b2518e Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Thu, 5 Apr 2018 13:57:44 -0400 Subject: [PATCH 23/26] Trying to reduce time universal interrupts are off --- src/SDI12.cpp | 67 ++++++++++++++++++++++++++++++++++++---------- src/SDI12_boards.h | 38 +++++++++++++------------- 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index 39780cc..258c0e5 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -145,7 +145,7 @@ static const uint16_t marking_micros = (uint16_t) 8330; // The required mark be static const uint8_t txBitWidth = TICKS_PER_BIT; static const uint8_t rxWindowWidth = RX_WINDOW_FUDGE; // A fudge factor to make things work static const uint8_t bitsPerTick_Q10 = BITS_PER_TICK_Q10; -static const uint8_t WAITING_FOR_START_BIT = 0b11111111; +static const uint8_t WAITING_FOR_START_BIT = 0xFF; // 0b11111111 static uint16_t prevBitTCNT; // previous RX transition in micros static uint8_t rxState; // 0: got start bit; >0: bits rcvd @@ -251,8 +251,7 @@ relinquish control of the data line when not transmitting. #include // optimized parity bit handling #else // Added MJB: parity fuction to replace the one specific for AVR from util/parity.h -// look for a better optimized code in -// http://www.avrfreaks.net/forum/easy-method-calculate-even-parity-16-bit +// http://graphics.stanford.edu/~seander/bithacks.html#ParityNaive uint8_t SDI12::parity_even_bit(uint8_t v) { uint8_t parity = 0; @@ -310,7 +309,7 @@ void SDI12::setState(SDI12_STATES state){ { pinMode(_dataPin,INPUT); // added to make output work after pinMode to OUTPUT (don't know why, but works) pinMode(_dataPin,OUTPUT); // Pin mode = output - noInterrupts(); // _ALL_ interrupts disabled + setPinInterrupts(false); // Interrupts disabled on data pin break; } case LISTENING: @@ -517,6 +516,9 @@ recorder for another SDI-12 device // 4.1 - this function wakes up the entire sensor bus void SDI12::wakeSensors(){ setState(TRANSMITTING); + // Universal interrupts can be on while the break and marking happen because + // timings for break and from the recorder are not critical. + // Interrupts on the pin are disabled for the entire transmitting state digitalWrite(_dataPin, HIGH); delayMicroseconds(lineBreak_micros); // Required break of 12 milliseconds digitalWrite(_dataPin, LOW); @@ -526,23 +528,60 @@ void SDI12::wakeSensors(){ // 4.2 - this function writes a character out on the data line void SDI12::writeChar(uint8_t out) { - out |= (parity_even_bit(out)<<7); // 4.2.1 - parity bit + uint8_t currentTxBitNum = 0; // first bit is start bit + uint8_t bitValue = 0; // start bit is low + uint8_t parityBit = parity_even_bit(out); // Calculate the parity bit + // Serial.write(out); + out |= (parityBit<<7); // Add parity bit to the outgoing character + // Serial.print('='); + // Serial.print(out, BIN); + + uint8_t lastHighBit = 1; // The last bit that could possibly be HIGH/1 (+1 for start bit) + uint8_t outToCnt = out; // The last bit that could possibly be HIGH/1 + 1 for start bit + while (outToCnt >>= 1) { // Calculate the MSB position. + lastHighBit++; + } + // Serial.print('('); + // Serial.print(lastHighBit); + // Serial.println(')'); - digitalWrite(_dataPin, HIGH); // 4.2.2 - start bit - delayMicroseconds(bitWidth_micros); + uint8_t prevSREG = SREG; // Save the old interrupt register + noInterrupts(); // _ALL_ interrupts disabled so timing can't be shifted - for (byte mask = 0x01; mask; mask<<=1){ // 4.2.3 - send payload - if(out & mask){ + while (currentTxBitNum++ < lastHighBit+1) { // repeat for start bit until last possible HIGH/1 bit (+1 for start bit) + if (bitValue){ digitalWrite(_dataPin, LOW); + // Serial.print(currentTxBitNum); + // Serial.print("-"); + // Serial.print(bitValue); + // Serial.print("(L)"); + // Serial.print("@"); + // Serial.println(micros()); } else{ digitalWrite(_dataPin, HIGH); + // Serial.print(currentTxBitNum); + // Serial.print("-"); + // Serial.print(bitValue); + // Serial.print("(H)"); + // Serial.print("@"); + // Serial.println(micros()); } delayMicroseconds(bitWidth_micros); + bitValue = out & 0x01; // get next bit in the character to send + out = out >> 1; // shift character to expose the following bit } - digitalWrite(_dataPin, LOW); // 4.2.4 - stop bit - delayMicroseconds(bitWidth_micros); + SREG = prevSREG; // Re-enable universal interrupts as soon as critical timing is past + + digitalWrite(_dataPin, LOW); // Stop bit and all remaining LOW/0 bits + // Serial.print(currentTxBitNum); + // Serial.print("-1(L)@"); + // Serial.println(micros()); + delayMicroseconds(bitWidth_micros * (11-lastHighBit)); + // Serial.print("X"); + // Serial.print("@"); + // Serial.println(micros()); } // 4.3 - this function sends out the characters of the String cmd, one by one @@ -906,8 +945,8 @@ void SDI12::handleInterrupt(){ void SDI12::startChar() { rxState = 0; // got a start bit - rxMask = 0b00000001; // bit mask, lsb first - rxValue = 0b00000000; // RX character to be, a blank slate + rxMask = 0x01; // 0b00000001, bit mask, lsb first + rxValue = 0x00; // 0b00000000, RX character to be, a blank slate } // startChar // 7.3 - The actual interrupt service routine @@ -990,7 +1029,7 @@ void SDI12::receiveISR() // If this was the 8th or more bit then the character and parity are complete. if (rxState > 7) { // Serial.println(rxValue, BIN); - rxValue &= 0b01111111; // Throw away the parity bit + rxValue &= 0x7F; // 0b01111111, Throw away the parity bit charToBuffer(rxValue); // Put the finished character into the buffer diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index 7efd474..a3e95a7 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -20,26 +20,26 @@ // 1/(13.0208 ticks/bit) * 2^10 = 78.6432 #define RX_WINDOW_FUDGE 2 #elif F_CPU == 8000000L - // #define PRESCALE_IN_USE_STR "256" - // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x06) - // // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected - // // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - prescaler set to CK/256 - // #define TICKS_PER_BIT 26 - // // 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' - // // (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit - // #define BITS_PER_TICK_Q10 39 - // // 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 - // #define RX_WINDOW_FUDGE 10 - #define PRESCALE_IN_USE_STR "1024" - #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0x07) // Set the prescaler to 1024 + #define PRESCALE_IN_USE_STR "256" + #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0x00, TCCR2B = 0x06) // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected - // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 - #define TICKS_PER_BIT 6 - // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' - // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit - #define BITS_PER_TICK_Q10 157 - // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 - #define RX_WINDOW_FUDGE 5 + // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - prescaler set to CK/256 + #define TICKS_PER_BIT 26 + // 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' + // (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit + #define BITS_PER_TICK_Q10 39 + // 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 + #define RX_WINDOW_FUDGE 10 + // #define PRESCALE_IN_USE_STR "1024" + // #define CONFIG_TIMER_PRESCALE() (TCCR2A = 0, TCCR2B = 0x07) // Set the prescaler to 1024 + // // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC4A & OC4B disconnected + // // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - prescaler set to CK/1024 + // #define TICKS_PER_BIT 6 + // // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' + // // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit + // #define BITS_PER_TICK_Q10 157 + // // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 + // #define RX_WINDOW_FUDGE 5 #endif From 690f4aac96cb9ef71d82fdafed777410ef6879c8 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Thu, 5 Apr 2018 17:16:24 -0400 Subject: [PATCH 24/26] Works! --- src/SDI12.cpp | 100 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/src/SDI12.cpp b/src/SDI12.cpp index 258c0e5..14abd8a 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -526,62 +526,94 @@ void SDI12::wakeSensors(){ } // 4.2 - this function writes a character out on the data line -void SDI12::writeChar(uint8_t out) +void SDI12::writeChar(uint8_t outChar) { uint8_t currentTxBitNum = 0; // first bit is start bit - uint8_t bitValue = 0; // start bit is low - uint8_t parityBit = parity_even_bit(out); // Calculate the parity bit - // Serial.write(out); - out |= (parityBit<<7); // Add parity bit to the outgoing character - // Serial.print('='); - // Serial.print(out, BIN); + uint8_t bitValue = 1; // start bit is HIGH (inverse parity...) + + noInterrupts(); // _ALL_ interrupts disabled so timing can't be shifted - uint8_t lastHighBit = 1; // The last bit that could possibly be HIGH/1 (+1 for start bit) - uint8_t outToCnt = out; // The last bit that could possibly be HIGH/1 + 1 for start bit - while (outToCnt >>= 1) { // Calculate the MSB position. - lastHighBit++; + uint8_t t0 = TCNTX; // start time + digitalWrite(_dataPin, HIGH); // immediately get going on the start bit + // this gives us 833µs to calculate parity and position of last high bit + currentTxBitNum++; + + uint8_t parityBit = parity_even_bit(outChar); // Calculate the parity bit + // Serial.write(outChar); + outChar |= (parityBit<<7); // Add parity bit to the outgoing character + // Serial.print("=1."); // stop + // Serial.print(bitRead(outChar, 7)); + // Serial.print(bitRead(outChar, 6)); + // Serial.print(bitRead(outChar, 5)); + // Serial.print(bitRead(outChar, 4)); + // Serial.print(bitRead(outChar, 3)); + // Serial.print(bitRead(outChar, 2)); + // Serial.print(bitRead(outChar, 1)); + // Serial.print(bitRead(outChar, 0)); + // Serial.print(".0"); //start + + // Calculate the position of the last bit that is a 0/HIGH (ie, HIGH, not marking) + // That bit will be the last time-critical bit. All bits after that can be + // sent with interrupts enabled. + + uint8_t lastHighBit = 9; // The position of the last bit that is a 0 (ie, HIGH, not marking) + uint8_t msbMask = 0x80; // A mask with all bits at 1 + while (msbMask & outChar) { + lastHighBit--; + msbMask >>= 1; } - // Serial.print('('); - // Serial.print(lastHighBit); - // Serial.println(')'); - uint8_t prevSREG = SREG; // Save the old interrupt register - noInterrupts(); // _ALL_ interrupts disabled so timing can't be shifted + // Serial.print(currentTxBitNum); + // Serial.print("-"); + // Serial.print(bitValue); + // Serial.print("@"); + // Serial.println(t0); + + // Hold the line for the rest of the start bit duration + while ((uint8_t)(TCNTX - t0) < txBitWidth) {} + t0 = TCNTX; // advance start time - while (currentTxBitNum++ < lastHighBit+1) { // repeat for start bit until last possible HIGH/1 bit (+1 for start bit) + // repeat for all data bits until the last bit different from marking + while (currentTxBitNum++ < lastHighBit) { + bitValue = outChar & 0x01; // get next bit in the character to send if (bitValue){ - digitalWrite(_dataPin, LOW); + digitalWrite(_dataPin, LOW); // set the pin state to LOW for 1's // Serial.print(currentTxBitNum); // Serial.print("-"); // Serial.print(bitValue); - // Serial.print("(L)"); // Serial.print("@"); - // Serial.println(micros()); + // Serial.println(t0); } else{ - digitalWrite(_dataPin, HIGH); + digitalWrite(_dataPin, HIGH); // set the pin state to HIGH for 0's // Serial.print(currentTxBitNum); // Serial.print("-"); // Serial.print(bitValue); - // Serial.print("(H)"); // Serial.print("@"); - // Serial.println(micros()); + // Serial.println(t0); } - delayMicroseconds(bitWidth_micros); - bitValue = out & 0x01; // get next bit in the character to send - out = out >> 1; // shift character to expose the following bit + // Hold the line for this bit duration + while ((uint8_t)(TCNTX - t0) < txBitWidth) {} + t0 = TCNTX; // advance start time + outChar = outChar >> 1; // shift character to expose the following bit } - SREG = prevSREG; // Re-enable universal interrupts as soon as critical timing is past + // Set the line low for the all remaining 1's and the stop bit + digitalWrite(_dataPin, LOW); + + interrupts(); // Re-enable universal interrupts as soon as critical timing is past - digitalWrite(_dataPin, LOW); // Stop bit and all remaining LOW/0 bits // Serial.print(currentTxBitNum); - // Serial.print("-1(L)@"); - // Serial.println(micros()); - delayMicroseconds(bitWidth_micros * (11-lastHighBit)); - // Serial.print("X"); - // Serial.print("@"); - // Serial.println(micros()); + // Serial.print("-1@"); + // Serial.println(t0); + + // Hold the line low until the end of the 10th bit + uint8_t bitTimeRemaining = txBitWidth*(10-lastHighBit); + while ((uint8_t)(TCNTX - t0) < bitTimeRemaining) {} + t0 = TCNTX; // advance just for debugging + // Serial.print("X@"); + // Serial.println(t0); + } // 4.3 - this function sends out the characters of the String cmd, one by one From 96d10f4a39b6a2f8b3467c875fd7c1162a5befc6 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Fri, 6 Apr 2018 11:25:27 -0400 Subject: [PATCH 25/26] All SDI12 objects now share a single buffer Also rearranged code some just for my own ease of reading --- library.json | 2 +- library.properties | 2 +- src/SDI12.cpp | 933 ++++++++++++++++++++------------------------- src/SDI12.h | 32 +- 4 files changed, 433 insertions(+), 536 deletions(-) diff --git a/library.json b/library.json index e22d9d5..6f9210c 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "Arduino-SDI-12", - "version": "1.2.3", + "version": "1.3.0", "keywords": "SDI-12, sdi12, communication, bus, sensor, Decagon", "description": "Arduino library for SDI-12 communications to a wide variety of environmental sensors.", "repository": diff --git a/library.properties b/library.properties index 9fd5695..64c7243 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino-SDI-12 -version=1.2.3 +version=1.3.0 author=Kevin M. Smith , Shannon Hicks maintainer=Sara Damiano sentence=Arduino library for SDI-12 communications to a wide variety of environmental sensors. diff --git a/src/SDI12.cpp b/src/SDI12.cpp index 14abd8a..c599cbb 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -112,28 +112,22 @@ SDI-12.org, official site of the SDI-12 Support Group. ==================== Code Organization ====================== 0. Includes, Defines, & Variable Declarations -1. Buffer Setup -2. Data Line States, Overview of Interrupts +1. Buffer Setup. +2. Reading from the SDI-12 buffer 3. Constructor, Destructor, SDI12.begin(), and SDI12.end() -4. Waking up, and talking to, the sensors. -5. Reading from the SDI-12 object. available(), peek(), read(), flush(), clearBuffer() -6. Using more than one SDI-12 object, isActive() and setActive(). +4. Using more than one SDI-12 object, isActive() and setActive(). +5. Setting proper data Line States +6. Waking up, and talking to, the sensors 7. Interrupt Service Routine (getting the data into the buffer) +*/ -=========== 0. Includes, Defines, & Variable Declarations ============= - -0.1 - Include the header file for this library. -0.2 - defines the size of the buffer -0.3 - a static pointer to the active object. See section 6. - +/*=========== 0. Includes, Defines, & Variable Declarations ============= */ -#include "SDI12.h" // 0.1 header file for this library +#include "SDI12.h" // Header file for this library #include "SDI12_boards.h" -#define SDI12_BUFFER_SIZE 64 // 0.2 max RX buffer size - -SDI12 *SDI12::_activeObject = NULL; // 0.3 pointer to active SDI12 object +SDI12 *SDI12::_activeObject = NULL; // Pointer to active SDI12 object static const uint16_t bitWidth_micros = (uint16_t) 833; // The size of a bit in microseconds // 1200 baud = 1200 bits/second ~ 833.333 µs/bit @@ -152,17 +146,17 @@ static uint8_t rxState; // 0: got start bit; >0: bits rcvd static uint8_t rxMask; // bit mask for building received character static uint8_t rxValue; // character being built +// static method for getting a 16-bit value from 2 8-bit values static uint16_t mul8x8to16(uint8_t x, uint8_t y) {return x*y;} -//.......................................... - +// static method for calculating the number of bit-times that have elapsed static uint16_t bitTimes( uint8_t dt ) { return mul8x8to16( dt + rxWindowWidth, bitsPerTick_Q10 ) >> 10; - } // bitTimes + /* =========== 1. Buffer Setup ============================================ The buffer is used to store characters from the SDI-12 data line. @@ -175,24 +169,375 @@ running multiple instances. For more information on circular buffers: http://en.wikipedia.org/wiki/Circular_buffer -1.1 - Define a maximum buffer size (in number of characters). Increasing +1.1 - Initialize a single buffer for all SDI12 objects. Increasing the buffer size will use more RAM. If you exceed 256 characters, be sure to change the data type of the index to support the larger range of addresses. +1.2 - Index to buffer head. (unsigned 8-bit integer, can map from 0-255) +1.3 - Index to buffer tail. (unsigned 8-bit integer, can map from 0-255) + +*/ + +uint8_t SDI12::_rxBuffer[SDI12_BUFFER_SIZE]; // 1.1 - buff for incoming +volatile uint8_t SDI12::_rxBufferTail = 0; // 1.2 - index of buff head +volatile uint8_t SDI12::_rxBufferHead = 0; // 1.3 - index of buff tail + +/* =========== 2. Reading from the SDI-12 buffer ========================== + +2.1 - available() is a public function that returns the number of +characters available in the buffer. + +To understand how: +_rxBufferTail + SDI12_BUFFER_SIZE - _rxBufferHead) % SDI12_BUFFER_SIZE; +accomplishes this task, we will use a few examples. + +To start take the buffer below that has SDI12_BUFFER_SIZE = 10. The +message "abc" has been wrapped around (circular buffer). + +_rxBufferTail = 1 // points to the '-' after c +_rxBufferHead = 8 // points to 'a' + +[ c ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] [ a ] [ b ] + +The number of available characters is (1 + 10 - 8) % 10 = 3 + +The '%' or modulo operator finds the remainder of division of one number +by another. In integer arithmetic 3 / 10 = 0, but has a remainder of 3. +We can only get the remainder by using the the modulo '%'. 3 % 10 = 3. +This next case demonstrates more clearly why the modulo is used. + +_rxBufferTail = 4 // points to the '-' after c +_rxBufferHead = 1 // points to 'a' + +[ a ] [ b ] [ c ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] + +The number of available characters is (4 + 10 - 1) % 10 = 3 + +If we did not use the modulo we would get either ( 4 + 10 - 1 ) = 13 +characters or ( 4 + 10 - 1 ) / 10 = 1 character. Obviously neither is +correct. + +If there has been a buffer overflow, available() will return -1. + +2.2 - peek() is a public function that allows the user to look at the +character that is at the head of the buffer. Unlike read() it does not +consume the character (i.e. the index addressed by _rxBufferHead is not +changed). peek() returns -1 if there are no characters to show. + +2.3 - clearBuffer() is a public function that clears the buffers contents by +setting the index for both head and tail back to zero. + +2.4 - read() returns the character at the current head in the buffer +after incrementing the index of the buffer head. This action 'consumes' +the character, meaning it can not be read from the buffer again. If you +would rather see the character, but leave the index to head intact, you +should use peek(); + +2.5 - peekNextDigit(), parseInt(), and parseFloat() are functions in the Stream +class. Although they are not virtual and cannot be "overridden," recreting +them here "hides" the stream default versions to allow for a custom timeout +return value. The default value for the Stream class is to return 0. This makes +distinguishing timeouts from true zero readings impossible. Therefore the +default value has been set to -9999 in the being function. The value returned by +a timeout (TIMEOUT) is a public variable and can be changed dynamically +within a program by calling: + mySDI12.TIMEOUT = (int) newValue +or using the setTimeoutValue(int) function. + +*/ + +// 2.1 - reveals the number of characters available in the buffer +int SDI12::available() +{ + if(_bufferOverflow) return -1; + return (_rxBufferTail + SDI12_BUFFER_SIZE - _rxBufferHead) % SDI12_BUFFER_SIZE; +} + +// 2.2 - reveals the next character in the buffer without consuming +int SDI12::peek() +{ + if (_rxBufferHead == _rxBufferTail) return -1; // Empty buffer? If yes, -1 + return _rxBuffer[_rxBufferHead]; // Otherwise, read from "head" +} + +// 2.3 - a public function that clears the buffer contents and +// resets the status of the buffer overflow. +void SDI12::clearBuffer() +{ + _rxBufferHead = _rxBufferTail = 0; + _bufferOverflow = false; +} + +// 2.4 - reads in the next character from the buffer (and moves the index ahead) +int SDI12::read() +{ + _bufferOverflow = false; // Reading makes room in the buffer + if (_rxBufferHead == _rxBufferTail) return -1; // Empty buffer? If yes, -1 + uint8_t nextChar = _rxBuffer[_rxBufferHead]; // Otherwise, grab char at head + _rxBufferHead = (_rxBufferHead + 1) % SDI12_BUFFER_SIZE; // increment head + return nextChar; // return the char +} + +// 2.5 - these functions hide the stream equivalents to return a custom timeout value +int SDI12::peekNextDigit(LookaheadMode lookahead, bool detectDecimal) +{ + int c; + while (1) { + c = timedPeek(); + + if( c < 0 || + c == '-' || + (c >= '0' && c <= '9') || + (detectDecimal && c == '.')) return c; + + switch( lookahead ){ + case SKIP_NONE: return -1; // Fail code. + case SKIP_WHITESPACE: + switch( c ){ + case ' ': + case '\t': + case '\r': + case '\n': break; + default: return -1; // Fail code. + } + case SKIP_ALL: + break; + } + read(); // discard non-numeric + } +} + +long SDI12::parseInt(LookaheadMode lookahead, char ignore) +{ + bool isNegative = false; + long value = 0; + int c; + + c = peekNextDigit(lookahead, false); + // ignore non numeric leading characters + if(c < 0) + return TIMEOUT; // TIMEOUT returned if timeout + // THIS IS THE ONLY DIFFERENCE BETWEEN THIS FUNCTION AND THE STREAM DEFAULT! + + do{ + if(c == ignore) + ; // ignore this character + else if(c == '-') + isNegative = true; + else if(c >= '0' && c <= '9') // is c a digit? + value = value * 10 + c - '0'; + read(); // consume the character we got with peek + c = timedPeek(); + } + while( (c >= '0' && c <= '9') || c == ignore ); + + if(isNegative) + value = -value; + return value; +} + +// the same as parseInt but returns a floating point value +float SDI12::parseFloat(LookaheadMode lookahead, char ignore) +{ + bool isNegative = false; + bool isFraction = false; + long value = 0; + int c; + float fraction = 1.0; + + c = peekNextDigit(lookahead, true); + // ignore non numeric leading characters + if(c < 0) + return TIMEOUT; // TIMEOUT returned if timeout + // THIS IS THE ONLY DIFFERENCE BETWEEN THIS FUNCTION AND THE STREAM DEFAULT! + + do{ + if(c == ignore) + ; // ignore + else if(c == '-') + isNegative = true; + else if (c == '.') + isFraction = true; + else if(c >= '0' && c <= '9') { // is c a digit? + value = value * 10 + c - '0'; + if(isFraction) + fraction *= 0.1; + } + read(); // consume the character we got with peek + c = timedPeek(); + } + while( (c >= '0' && c <= '9') || (c == '.' && !isFraction) || c == ignore ); + + if(isNegative) + value = -value; + if(isFraction) + return value * fraction; + else + return value; +} + +/* ======= 3. Constructor, Destructor, SDI12.begin(), and SDI12.end() ======= + +3.1 - The constructor requires a single parameter: the pin to be used +for the data line. When the constructor is called it resets the buffer +overflow status to FALSE and assigns the pin number "dataPin" to the +private variable "_dataPin". + +3.2 - When the destructor is called, it's main task is to disable any +interrupts that had been previously assigned to the pin, so that the pin +will behave as expected when used for other purposes. This is achieved +by putting the SDI-12 object in the DISABLED state. + +3.3 - This is called to begin the functionality of the SDI-12 object. It +has no parameters as the SDI-12 protocol is fully specified (e.g. the +baud rate is set). It sets the object as the active object (if multiple +SDI-12 instances are being used simultaneously). + +3.4 - This can be called to temporarily cease all functionality of the +SDI-12 object. It is not as harsh as destroying the object with the +destructor, as it will maintain the memory buffer. + +3.5 - These set a custom value to return if a parse int or parse float function +times out. By default this value is -9999. + +*/ + +// 3.1 Constructor +SDI12::SDI12(){ + _bufferOverflow = false; +} +SDI12::SDI12(uint8_t dataPin){ + _bufferOverflow = false; + _dataPin = dataPin; +} + +// 3.2 Destructor +SDI12::~SDI12(){ setState(DISABLED); } + +// 3.3 Begin +void SDI12::begin(){ + // setState(HOLDING); + setActive(); + // SDI-12 protocol says sensors must respond within 15 milliseconds + // We'll bump that up to 150, just for good measure, but we don't want to + // wait the whole stream default of 1s for a response. + setTimeout(150); + // Because SDI-12 is mostly used for environmental sensors, we want to be able + // to distinguish between the '0' that parseInt and parseFloat usually return + // on timeouts and a real measured 0 value. So we force the timeout response + // to be -9999, which is not a common value for most variables measured by + // in-site environmental sensors. + setTimeoutValue(-9999); + // Set up the prescaler as needed for timers + + #if defined(ARDUINO_ARCH_SAMD) + // I would prefer to define this all as a macro, but for some reason it isn't working + // Select generic clock generator 4 (Arduino core uses 0-3) + // Most examples use this clock generator.. consider yourself warned! + // I would use a higher clock number, but some of the cores don't include them for some reason + REG_GCLK_GENDIV = GCLK_GENDIV_ID(4) | // Select Generic Clock Generator 4 + GCLK_GENDIV_DIV(3) ; // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + // Write the generic clock generator 5 configuration + REG_GCLK_GENCTRL = GCLK_GENCTRL_ID(4) | // Select GCLK4 + GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source + GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW + GCLK_GENCTRL_GENEN; // Enable the generic clock clontrol + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + // Feed GCLK4 to TC4 (also feeds to TC5, the two must have the same source) + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_GEN_GCLK4 | // Select Generic Clock Generator 4 + GCLK_CLKCTRL_CLKEN | // Enable the generic clock generator + GCLK_CLKCTRL_ID_TC4_TC5; // Feed the Generic Clock Generator 4 to TC4 and TC5 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024, 16MHz/1024 = 15.625kHz + TC_CTRLA_WAVEGEN_NFRQ | // Put the timer TC4 into normal frequency (NFRQ) mode + TC_CTRLA_MODE_COUNT8 | // Put the timer TC4 into 8-bit mode + TC_CTRLA_ENABLE; // Enable TC4 + while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization + #else + CONFIG_TIMER_PRESCALE();// Set up the generic clock (GCLK4) used to clock timers + #endif +} +void SDI12::begin(uint8_t dataPin){ + _dataPin = dataPin; + begin(); +} + +// 3.4 End +void SDI12::end() { setState(DISABLED); } -1.2 - Create a character array of the specified size. 1.3 - Index to -buffer head. (unsigned 8-bit integer, can map from 0-255) 1.4 - Index to -buffer tail. (unsigned 8-bit integer, can map from 0-255) +// 3.5 Set the timeout return +void SDI12::setTimeoutValue(int value) { TIMEOUT = value; } + +// 3.6 Return the data pin for the SDI-12 instance +uint8_t SDI12::getDataPin() { return _dataPin; } + + +/* ============= 4. Using more than one SDI-12 object. =================== + +This library is allows for multiple instances of itself running on the same or +different pins. SDI-12 can support up to 62 sensors on a single pin/bus, +so it is notnecessary to use an instance for each sensor. + +Because we are using pin change interrupts there can only be one active +object at a time (since this is the only reliable way to determine which +pin the interrupt occurred on). The active object is the only object +that will respond properly to interrupts. However promoting another +instance to Active status does not automatically remove the interrupts +on the other pin. For proper behavior it is recommended to use this +pattern: + + mySDI12.forceHold(); + myOtherSDI12.setActive(); + +Other notes: Promoting an object into the Active state will set it as +HOLDING. See 4.1 for more information. + +Calling mySDI12.begin() will assert mySDI12 as the new active object, +until another instance calls myOtherSDI12.begin() or +myOtherSDI12.setActive(). + +Calling mySDI12.end() does NOT hand-off active status to another SDI-12 +instance. + +You can check on the active object by calling mySDI12.isActive(), which +will return a boolean value TRUE if active or FALSE if inactive. + +4.1 - a method for setting the current object as the active object. +returns TRUE if the object was not formerly the active object and now +is. returns + +Promoting an inactive to the active instance will start it in the +HOLDING state and return TRUE. + +Otherwise, if the object is currently the active instance, it will +remain unchanged and return FALSE. + +4.2 - a method for checking if the object is the active object. Returns +true if the object is currently the active object, false otherwise. */ -// See section 0 above. // 1.1 - max buffer size -char _rxBuffer[SDI12_BUFFER_SIZE]; // 1.2 - buff for incoming -uint8_t _rxBufferHead = 0; // 1.3 - index of buff head -uint8_t _rxBufferTail = 0; // 1.4 - index of buff tail +// 4.1 - a method for setting the current object as the active object +bool SDI12::setActive() +{ + if (_activeObject != this) + { + setState(HOLDING); + _activeObject = this; + return true; + } + return false; +} + +// 4.2 - a method for checking if this object is the active object +bool SDI12::isActive() { return this == _activeObject; } -/* =========== 2. Data Line States =============================== +/* =========== 5. Data Line States =============================== The Arduino is responsible for managing communication with the sensors. Since all the data transfer happens on the same line, the state of the @@ -213,7 +558,7 @@ predictability, we set the pin to a LOW level high impedance state State Interrupts Pin Mode Pin Level HOLDING Pin Disable OUTPUT LOW -TRANSMITTING All Disable OUTPUT VARYING +TRANSMITTING All/Pin Disable OUTPUT VARYING LISTENING All Enable INPUT LOW DISABLED Pin Disable INPUT LOW @@ -227,25 +572,25 @@ HOLDING --> TRANSMITTING --> LISTENING --> done reading, forceHold(); HOLDING -------------------------| Function Descriptions |------------------------- -2.1 - Sets up parity and interrupts for different processor types - that is, +5.1 - Sets up parity and interrupts for different processor types - that is, imports the interrupts and parity for the AVR processors where they exist. -2.2 - A private helper function to turn pin interupts on or off +5.2 - A private helper function to turn pin interupts on or off -2.3 - Sets the proper state. This is a private function, and only used +5.3 - Sets the proper state. This is a private function, and only used internally. The grid above defines the settings applied in changing to each state. -2.4 - A public function which forces the line into a "holding" state. +5.4 - A public function which forces the line into a "holding" state. This is generally unneeded, but for deployments where interference is an issue, it should be used after all expected bytes have been returned from the sensor. -2.5 - A public function which forces the line into a "listening" state. +5.5 - A public function which forces the line into a "listening" state. This may be needed for implementing a slave-side device, which should relinquish control of the data line when not transmitting. */ -// 2.1 - Processor specific parity and interrupts +// 5.1 - Processor specific parity and interrupts #if defined __AVR__ #include // interrupt handling #include // optimized parity bit handling @@ -264,7 +609,7 @@ uint8_t SDI12::parity_even_bit(uint8_t v) } #endif -// 2.2 - a helper function to switch pin interrupts on or off +// 5.2 - a helper function to switch pin interrupts on or off void SDI12::setPinInterrupts(bool enable) { #ifndef SDI12_EXTERNAL_PCINT @@ -293,7 +638,7 @@ void SDI12::setPinInterrupts(bool enable) #endif } -// 2.3 - sets the state of the SDI-12 object. +// 5.3 - sets the state of the SDI-12 object. void SDI12::setState(SDI12_STATES state){ switch (state) { @@ -331,127 +676,26 @@ void SDI12::setState(SDI12_STATES state){ } } -// 2.4 - forces a HOLDING state. +// 5.4 - forces a HOLDING state. void SDI12::forceHold(){ setState(HOLDING); } -// 2.5 - forces a LISTENING state. +// 5.5 - forces a LISTENING state. void SDI12::forceListen(){ setState(LISTENING); } -/* ======= 3. Constructor, Destructor, SDI12.begin(), and SDI12.end() ======= -3.1 - The constructor requires a single parameter: the pin to be used -for the data line. When the constructor is called it resets the buffer -overflow status to FALSE and assigns the pin number "dataPin" to the -private variable "_dataPin". - -3.2 - When the destructor is called, it's main task is to disable any -interrupts that had been previously assigned to the pin, so that the pin -will behave as expected when used for other purposes. This is achieved -by putting the SDI-12 object in the DISABLED state. - -3.3 - This is called to begin the functionality of the SDI-12 object. It -has no parameters as the SDI-12 protocol is fully specified (e.g. the -baud rate is set). It sets the object as the active object (if multiple -SDI-12 instances are being used simultaneously). - -3.4 - This can be called to temporarily cease all functionality of the -SDI-12 object. It is not as harsh as destroying the object with the -destructor, as it will maintain the memory buffer. - -3.5 - This sets a custom value to return if a parse int or parse float function -times out. By default this value is -9999. - -*/ - -// 3.1 Constructor -SDI12::SDI12(){ - _bufferOverflow = false; -} -SDI12::SDI12(uint8_t dataPin){ - _bufferOverflow = false; - _dataPin = dataPin; -} - -// 3.2 Destructor -SDI12::~SDI12(){ setState(DISABLED); } - -// 3.3 Begin -void SDI12::begin(){ - // setState(HOLDING); - setActive(); - // SDI-12 protocol says sensors must respond within 15 milliseconds - // We'll bump that up to 150, just for good measure, but we don't want to - // wait the whole stream default of 1s for a response. - setTimeout(150); - // Because SDI-12 is mostly used for environmental sensors, we want to be able - // to distinguish between the '0' that parseInt and parseFloat usually return - // on timeouts and a real measured 0 value. So we force the timeout response - // to be -9999, which is not a common value for most variables measured by - // in-site environmental sensors. - setTimeoutValue(-9999); - // Set up the prescaler as needed for timers - - #if defined(ARDUINO_ARCH_SAMD) - // I would prefer to define this all as a macro, but for some reason it isn't working - // Select generic clock generator 4 (Arduino core uses 0-3) - // Most examples use this clock generator.. consider yourself warned! - // I would use a higher clock number, but some of the cores don't include them for some reason - REG_GCLK_GENDIV = GCLK_GENDIV_ID(4) | // Select Generic Clock Generator 4 - GCLK_GENDIV_DIV(3) ; // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz - while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization +/* ============= 6. Waking up, and talking to, the sensors. =================== - // Write the generic clock generator 5 configuration - REG_GCLK_GENCTRL = GCLK_GENCTRL_ID(4) | // Select GCLK4 - GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source - GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW - GCLK_GENCTRL_GENEN; // Enable the generic clock clontrol - while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization - - // Feed GCLK4 to TC4 (also feeds to TC5, the two must have the same source) - REG_GCLK_CLKCTRL = GCLK_CLKCTRL_GEN_GCLK4 | // Select Generic Clock Generator 4 - GCLK_CLKCTRL_CLKEN | // Enable the generic clock generator - GCLK_CLKCTRL_ID_TC4_TC5; // Feed the Generic Clock Generator 4 to TC4 and TC5 - while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization - - REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024, 16MHz/1024 = 15.625kHz - TC_CTRLA_WAVEGEN_NFRQ | // Put the timer TC4 into normal frequency (NFRQ) mode - TC_CTRLA_MODE_COUNT8 | // Put the timer TC4 into 8-bit mode - TC_CTRLA_ENABLE; // Enable TC4 - while (TC4->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization - #else - CONFIG_TIMER_PRESCALE();// Set up the generic clock (GCLK4) used to clock timers - #endif -} -void SDI12::begin(uint8_t dataPin){ - _dataPin = dataPin; - begin(); -} - -// 3.4 End -void SDI12::end() { setState(DISABLED); } - -// 3.5 Set the timeout return -void SDI12::setTimeoutValue(int value) { TIMEOUT = value; } - -// 3.6 Return the data pin for the SDI-12 instance -uint8_t SDI12::getDataPin() { return _dataPin; } - - -/* ============= 4. Waking up, and talking to, the sensors. =================== - -4.1 - wakeSensors() literally wakes up all the sensors on the bus. The +6.1 - wakeSensors() literally wakes up all the sensors on the bus. The SDI-12 protocol requires a pulse of HIGH voltage for at least 12 milliseconds followed immediately by a pulse of LOW voltage for at least -8.3 milliseconds. The values here are close to those values, but provide -100 extra microseconds of wiggle room. Setting the SDI-12 object into -the TRANSMITTING allows us to assert control of the line without -triggering any interrupts. +8.3 milliseconds. Setting the SDI-12 object into the TRANSMITTING allows us to +assert control of the line without triggering any interrupts. -4.2 - This function writes a character out to the data line. SDI-12 +6.2 - This function writes a character out to the data line. SDI-12 specifies the general transmission format of a single character as: 10 bits per data frame @@ -460,61 +704,26 @@ specifies the general transmission format of a single character as: 1 even parity bit 1 stop bit -We also recall that we are using inverse logic, so HIGH represents 0, -and LOW represents a 1. If you are unclear on any of these terms, I -would recommend that you look them up before proceeding. They will be -better explained elsewhere. - -The transmission takes several steps. -The variable name for the outgoing character is "out". - -+ 4.2.1 - Determine the proper even parity bit (will an additional 1 or 0 -make the final number of 1's even?) - -First we grab the bit using an optimized macro from parity.h - parity_even_bit(out) - -Then we bit shift it into the proper place, which is the most -significant bit position, since the characters we are using are only -7-bits. - (parity_even_bit(out)<<7); - -Then we use the '|=' operator to set the bit if necessary. - -+ 4.2.2 - Send the start bit. The start bit is always a '0', so we simply -write the dataPin HIGH for bitWidth_micros microseconds. - -+ 4.2.3 - Send the payload (the 7 character bits and the parity bit) least -significant bit first. This is accomplished bitwise AND operations on a -moving mask (00000001) --> (00000010) --> (00000100)... and so on. This -functionality makes use of the '<<=' operator which stores the result of -the bit-shift back into the left hand side. - -If the result of (out & mask) determines whether a 1 or 0 should be sent. -Again, here inverse logic may lead to easy confusion. - -if(out & mask){ - digitalWrite(_dataPin, LOW); - } - else{ - digitalWrite(_dataPin, HIGH); - } - -+ 4.2.4 - Send the stop bit. The stop bit is always a '1', so we simply -write the dataPin LOW for bitWidth_micros microseconds. +We also recall that we are using inverse logic, so HIGH represents 0, +and LOW represents a 1. If you are unclear on any of these terms, I +would recommend that you look them up before proceeding. They will be +better explained elsewhere. -4.3 - sendCommand(String cmd) is a publicly accessible function that +The transmission takes several steps. +The variable name for the outgoing character is "outChar". + +6.3 - sendCommand(String cmd) is a publicly accessible function that wakes sensors and sends out a String byte by byte the command line. -4.4 - sendResponse(String resp) is a publicly accessible function that +6.4 - sendResponse(String resp) is a publicly accessible function that sends out an 8.33 ms marking and a String byte by byte the command line. This is needed if the Arduino is acting as an SDI-12 device itself, not as a recorder for another SDI-12 device */ -// 4.1 - this function wakes up the entire sensor bus -void SDI12::wakeSensors(){ +// 6.1 - this function wakes up the entire sensor bus +void SDI12::wakeSensors() { setState(TRANSMITTING); // Universal interrupts can be on while the break and marking happen because // timings for break and from the recorder are not critical. @@ -525,9 +734,8 @@ void SDI12::wakeSensors(){ delayMicroseconds(marking_micros); // Required marking of 8.33 milliseconds } -// 4.2 - this function writes a character out on the data line -void SDI12::writeChar(uint8_t outChar) -{ +// 6.2 - this function writes a character out on the data line +void SDI12::writeChar(uint8_t outChar) { uint8_t currentTxBitNum = 0; // first bit is start bit uint8_t bitValue = 1; // start bit is HIGH (inverse parity...) @@ -539,18 +747,7 @@ void SDI12::writeChar(uint8_t outChar) currentTxBitNum++; uint8_t parityBit = parity_even_bit(outChar); // Calculate the parity bit - // Serial.write(outChar); outChar |= (parityBit<<7); // Add parity bit to the outgoing character - // Serial.print("=1."); // stop - // Serial.print(bitRead(outChar, 7)); - // Serial.print(bitRead(outChar, 6)); - // Serial.print(bitRead(outChar, 5)); - // Serial.print(bitRead(outChar, 4)); - // Serial.print(bitRead(outChar, 3)); - // Serial.print(bitRead(outChar, 2)); - // Serial.print(bitRead(outChar, 1)); - // Serial.print(bitRead(outChar, 0)); - // Serial.print(".0"); //start // Calculate the position of the last bit that is a 0/HIGH (ie, HIGH, not marking) // That bit will be the last time-critical bit. All bits after that can be @@ -563,12 +760,6 @@ void SDI12::writeChar(uint8_t outChar) msbMask >>= 1; } - // Serial.print(currentTxBitNum); - // Serial.print("-"); - // Serial.print(bitValue); - // Serial.print("@"); - // Serial.println(t0); - // Hold the line for the rest of the start bit duration while ((uint8_t)(TCNTX - t0) < txBitWidth) {} t0 = TCNTX; // advance start time @@ -578,19 +769,9 @@ void SDI12::writeChar(uint8_t outChar) bitValue = outChar & 0x01; // get next bit in the character to send if (bitValue){ digitalWrite(_dataPin, LOW); // set the pin state to LOW for 1's - // Serial.print(currentTxBitNum); - // Serial.print("-"); - // Serial.print(bitValue); - // Serial.print("@"); - // Serial.println(t0); } else{ digitalWrite(_dataPin, HIGH); // set the pin state to HIGH for 0's - // Serial.print(currentTxBitNum); - // Serial.print("-"); - // Serial.print(bitValue); - // Serial.print("@"); - // Serial.println(t0); } // Hold the line for this bit duration while ((uint8_t)(TCNTX - t0) < txBitWidth) {} @@ -603,22 +784,24 @@ void SDI12::writeChar(uint8_t outChar) interrupts(); // Re-enable universal interrupts as soon as critical timing is past - // Serial.print(currentTxBitNum); - // Serial.print("-1@"); - // Serial.println(t0); - // Hold the line low until the end of the 10th bit uint8_t bitTimeRemaining = txBitWidth*(10-lastHighBit); while ((uint8_t)(TCNTX - t0) < bitTimeRemaining) {} - t0 = TCNTX; // advance just for debugging - // Serial.print("X@"); - // Serial.println(t0); } -// 4.3 - this function sends out the characters of the String cmd, one by one -void SDI12::sendCommand(String &cmd) -{ +// The typical write functionality for a stream object +// This allows you to use the stream print functions to send commands out on +// the SDI-12, line, but it will not wake the sensors in advance of the command. +size_t SDI12::write(uint8_t byte) { + setState(TRANSMITTING); + writeChar(byte); // write the character/byte + setState(LISTENING); // listen for reply + return 1; // 1 character sent +} + +// 6.3 - this function sends out the characters of the String cmd, one by one +void SDI12::sendCommand(String &cmd) { wakeSensors(); // set state to transmitting and send break/marking for (int unsigned i = 0; i < cmd.length(); i++){ writeChar(cmd[i]); // write each character @@ -626,8 +809,7 @@ void SDI12::sendCommand(String &cmd) setState(LISTENING); // listen for reply } -void SDI12::sendCommand(const char *cmd) -{ +void SDI12::sendCommand(const char *cmd) { wakeSensors(); // wake up sensors for (int unsigned i = 0; i < strlen(cmd); i++){ writeChar(cmd[i]); // write each character @@ -635,8 +817,7 @@ void SDI12::sendCommand(const char *cmd) setState(LISTENING); // listen for reply } -void SDI12::sendCommand(FlashString cmd) -{ +void SDI12::sendCommand(FlashString cmd) { wakeSensors(); // wake up sensors for (int unsigned i = 0; i < strlen_P((PGM_P)cmd); i++){ writeChar((char)pgm_read_byte((const char *)cmd + i)); // write each character @@ -644,12 +825,11 @@ void SDI12::sendCommand(FlashString cmd) setState(LISTENING); // listen for reply } -// 4.4 - this function sets up for a response to a separate data recorder by +// 6.4 - this function sets up for a response to a separate data recorder by // sending out a marking and then sending out the characters of resp // one by one (for slave-side use, that is, when the Arduino itself is // acting as an SDI-12 device rather than a recorder). -void SDI12::sendResponse(String &resp) -{ +void SDI12::sendResponse(String &resp) { setState(TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); delayMicroseconds(marking_micros); // 8.33 ms marking before response @@ -659,8 +839,7 @@ void SDI12::sendResponse(String &resp) setState(LISTENING); // return to listening state } -void SDI12::sendResponse(const char *resp) -{ +void SDI12::sendResponse(const char *resp) { setState(TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); delayMicroseconds(marking_micros); // 8.33 ms marking before response @@ -670,8 +849,7 @@ void SDI12::sendResponse(const char *resp) setState(LISTENING); // return to listening state } -void SDI12::sendResponse(FlashString resp) -{ +void SDI12::sendResponse(FlashString resp) { setState(TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); delayMicroseconds(marking_micros); // 8.33 ms marking before response @@ -681,264 +859,6 @@ void SDI12::sendResponse(FlashString resp) setState(LISTENING); // return to listening state } -/* ============= 5. Reading from the SDI-12 object. =================== - -5.1 - available() is a public function that returns the number of -characters available in the buffer. - -To understand how: -_rxBufferTail + SDI12_BUFFER_SIZE - _rxBufferHead) % SDI12_BUFFER_SIZE; -accomplishes this task, we will use a few examples. - -To start take the buffer below that has SDI12_BUFFER_SIZE = 10. The -message "abc" has been wrapped around (circular buffer). - -_rxBufferTail = 1 // points to the '-' after c -_rxBufferHead = 8 // points to 'a' - -[ c ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] [ a ] [ b ] - -The number of available characters is (1 + 10 - 8) % 10 = 3 - -The '%' or modulo operator finds the remainder of division of one number -by another. In integer arithmetic 3 / 10 = 0, but has a remainder of 3. -We can only get the remainder by using the the modulo '%'. 3 % 10 = 3. -This next case demonstrates more clearly why the modulo is used. - -_rxBufferTail = 4 // points to the '-' after c -_rxBufferHead = 1 // points to 'a' - -[ a ] [ b ] [ c ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] [ - ] - -The number of available characters is (4 + 10 - 1) % 10 = 3 - -If we did not use the modulo we would get either ( 4 + 10 - 1 ) = 13 -characters or ( 4 + 10 - 1 ) / 10 = 1 character. Obviously neither is -correct. - -If there has been a buffer overflow, available() will return -1. - -5.2 - peek() is a public function that allows the user to look at the -character that is at the head of the buffer. Unlike read() it does not -consume the character (i.e. the index addressed by _rxBufferHead is not -changed). peek() returns -1 if there are no characters to show. - -5.3 - clearBuffer() is a public function that clears the buffers contents by -setting the index for both head and tail back to zero. - -5.4 - read() returns the character at the current head in the buffer -after incrementing the index of the buffer head. This action 'consumes' -the character, meaning it can not be read from the buffer again. If you -would rather see the character, but leave the index to head intact, you -should use peek(); - -5.5 - peekNextDigit(), parseInt(), and parseFloat() are functions in the Stream -class. Although they are not virtual and cannot be "overridden," recreting -them here "hides" the stream default versions to allow for a custom timeout -return value. The default value for the Stream class is to return 0. This makes -distinguishing timeouts from true zero readings impossible. Therefore the -default value has been set to -9999 in the being function. The value returned by -a timeout (TIMEOUT) is a public variable and can be changed dynamically -within a program by calling: - mySDI12.TIMEOUT = (int) newValue -or using the setTimeoutValue(int) function. - -*/ - -// 5.1 - reveals the number of characters available in the buffer -int SDI12::available() -{ - if(_bufferOverflow) return -1; - return (_rxBufferTail + SDI12_BUFFER_SIZE - _rxBufferHead) % SDI12_BUFFER_SIZE; -} - -// 5.2 - reveals the next character in the buffer without consuming -int SDI12::peek() -{ - if (_rxBufferHead == _rxBufferTail) return -1; // Empty buffer? If yes, -1 - return _rxBuffer[_rxBufferHead]; // Otherwise, read from "head" -} - -// 5.3 - a public function that clears the buffer contents and -// resets the status of the buffer overflow. -void SDI12::clearBuffer() -{ - _rxBufferHead = _rxBufferTail = 0; - _bufferOverflow = false; -} - -// 5.4 - reads in the next character from the buffer (and moves the index ahead) -int SDI12::read() -{ - _bufferOverflow = false; // Reading makes room in the buffer - if (_rxBufferHead == _rxBufferTail) return -1; // Empty buffer? If yes, -1 - uint8_t nextChar = _rxBuffer[_rxBufferHead]; // Otherwise, grab char at head - _rxBufferHead = (_rxBufferHead + 1) % SDI12_BUFFER_SIZE; // increment head - return nextChar; // return the char -} - -// 5.5 - these functions hide the stream equivalents to return a custom timeout value -int SDI12::peekNextDigit(LookaheadMode lookahead, bool detectDecimal) -{ - int c; - while (1) { - c = timedPeek(); - - if( c < 0 || - c == '-' || - (c >= '0' && c <= '9') || - (detectDecimal && c == '.')) return c; - - switch( lookahead ){ - case SKIP_NONE: return -1; // Fail code. - case SKIP_WHITESPACE: - switch( c ){ - case ' ': - case '\t': - case '\r': - case '\n': break; - default: return -1; // Fail code. - } - case SKIP_ALL: - break; - } - read(); // discard non-numeric - } -} - -long SDI12::parseInt(LookaheadMode lookahead, char ignore) -{ - bool isNegative = false; - long value = 0; - int c; - - c = peekNextDigit(lookahead, false); - // ignore non numeric leading characters - if(c < 0) - return TIMEOUT; // TIMEOUT returned if timeout - // THIS IS THE ONLY DIFFERENCE BETWEEN THIS FUNCTION AND THE STREAM DEFAULT! - - do{ - if(c == ignore) - ; // ignore this character - else if(c == '-') - isNegative = true; - else if(c >= '0' && c <= '9') // is c a digit? - value = value * 10 + c - '0'; - read(); // consume the character we got with peek - c = timedPeek(); - } - while( (c >= '0' && c <= '9') || c == ignore ); - - if(isNegative) - value = -value; - return value; -} - -// as parseInt but returns a floating point value -float SDI12::parseFloat(LookaheadMode lookahead, char ignore) -{ - bool isNegative = false; - bool isFraction = false; - long value = 0; - int c; - float fraction = 1.0; - - c = peekNextDigit(lookahead, true); - // ignore non numeric leading characters - if(c < 0) - return TIMEOUT; // TIMEOUT returned if timeout - // THIS IS THE ONLY DIFFERENCE BETWEEN THIS FUNCTION AND THE STREAM DEFAULT! - - do{ - if(c == ignore) - ; // ignore - else if(c == '-') - isNegative = true; - else if (c == '.') - isFraction = true; - else if(c >= '0' && c <= '9') { // is c a digit? - value = value * 10 + c - '0'; - if(isFraction) - fraction *= 0.1; - } - read(); // consume the character we got with peek - c = timedPeek(); - } - while( (c >= '0' && c <= '9') || (c == '.' && !isFraction) || c == ignore ); - - if(isNegative) - value = -value; - if(isFraction) - return value * fraction; - else - return value; -} - -/* ============= 6. Using more than one SDI-12 object. =================== - -This library is allows for multiple instances of itself running on -different pins, however, you should use care. The instances DO NOT share -a buffer, as they do in the multi-instance case for SoftwareSerial, so -it will consume more RAM than you might expect. - -SDI-12 can support up to 62 sensors on a single pin/bus, so it is not -necessary to use an instance for each sensor. - -Because we are using pin change interrupts there can only be one active -object at a time (since this is the only reliable way to determine which -pin the interrupt occurred on). The active object is the only object -that will respond properly to interrupts. However promoting another -instance to Active status does not automatically remove the interrupts -on the other pin. For proper behavior it is recommended to use this -pattern: - - mySDI12.forceHold(); - myOtherSDI12.setActive(); - -Other notes: Promoting an object into the Active state will set it as -HOLDING. See 6.1 for more information. - -Calling mySDI12.begin() will assert mySDI12 as the new active object, -until another instance calls myOtherSDI12.begin() or -myOtherSDI12.setActive(). - -Calling mySDI12.end() does NOT hand-off active status to another SDI-12 -instance. - -You can check on the active object by calling mySDI12.isActive(), which -will return a boolean value TRUE if active or FALSE if inactive. - -6.1 - a method for setting the current object as the active object. -returns TRUE if the object was not formerly the active object and now -is. returns - -Promoting an inactive to the active instance will start it in the -HOLDING state and return TRUE. - -Otherwise, if the object is currently the active instance, it will -remain unchanged and return FALSE. - -6.2 - a method for checking if the object is the active object. Returns -true if the object is currently the active object, false otherwise. - -*/ - -// 6.1 - a method for setting the current object as the active object -bool SDI12::setActive() -{ - if (_activeObject != this) - { - setState(HOLDING); - _activeObject = this; - return true; - } - return false; -} - -// 6.2 - a method for checking if this object is the active object -bool SDI12::isActive() { return this == _activeObject; } - /* ============== 7. Interrupt Service Routine =================== @@ -986,25 +906,18 @@ void SDI12::receiveISR() { uint8_t thisBitTCNT = TCNTX; // time of this data transition (plus ISR latency) uint8_t pinLevel = digitalRead(_dataPin); // current RX data level - // Serial.print(pinLevel); - // Serial.print('@'); - // Serial.print(TCNTX); - // Serial.print('='); // Check if we're ready for a start bit, and if this could possibly be it // Otherwise, just ignore the interrupt and exit if (rxState == WAITING_FOR_START_BIT) { // If it is low it's not a start bit, exit // Inverse logic start bit = HIGH - if (pinLevel == LOW) - { - // Serial.println('X'); + if (pinLevel == LOW) { return; } // If it is HIGH, this should be a start bit // Thus set the rxStat to 0, create an empty character, and a new mask with a 1 in the lowest place startChar(); - // Serial.println('*'); } // if the character is incomplete, and this is not a start bit, @@ -1060,7 +973,6 @@ void SDI12::receiveISR() // If this was the 8th or more bit then the character and parity are complete. if (rxState > 7) { - // Serial.println(rxValue, BIN); rxValue &= 0x7F; // 0b01111111, Throw away the parity bit charToBuffer(rxValue); // Put the finished character into the buffer @@ -1069,9 +981,7 @@ void SDI12::receiveISR() // character (but have gotten all the data bits, then this should be a // stop bit and we can start looking for a new start bit. if ((pinLevel == LOW) || !nextCharStarted) { - rxState = WAITING_FOR_START_BIT; - // DISABLE STOP BIT TIMER - + rxState = WAITING_FOR_START_BIT; // DISABLE STOP BIT TIMER } else { // If we just switched to HIGH, or we've exceeded the total number of // bits in a character, then the character must have ended with 1's/LOW, @@ -1079,6 +989,7 @@ void SDI12::receiveISR() startChar(); } } + } prevBitTCNT = thisBitTCNT; // remember time stamp of this change! } diff --git a/src/SDI12.h b/src/SDI12.h index c5c5967..cd906cc 100644 --- a/src/SDI12.h +++ b/src/SDI12.h @@ -39,25 +39,6 @@ Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - -================== Notes on Various Arduino-Type Processors ==================== - -This library requires the use of pin change interrupts (PCINT). - -Not all Arduino boards have the same pin capabilities. -The known compatibile pins for common variants are shown below. - -Arduino Uno: All pins. -Arduino Mega or Mega 2560: -10, 11, 12, 13, 14, 15, 50, 51, 52, 53, A8 (62), -A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69). - -Arduino Leonardo: -8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI) - -Arduino Zero: -Any pin except 4 */ #ifndef SDI12_h @@ -71,6 +52,7 @@ Any pin except 4 typedef const __FlashStringHelper *FlashString; #define NO_IGNORE_CHAR '\x01' // a char not found in a valid ASCII numeric field +#define SDI12_BUFFER_SIZE 64 // max Rx buffer size class SDI12 : public Stream { @@ -91,19 +73,24 @@ class SDI12 : public Stream } SDI12_STATES; static SDI12 *_activeObject; // static pointer to active SDI12 instance + void setPinInterrupts(bool enable); // Turns pin interrupts on or off void setState(SDI12_STATES state); // sets the state of the SDI12 objects void wakeSensors(); // used to wake up the SDI12 bus void writeChar(uint8_t out); // used to send a char out on the data line - void startChar(); // creates a blank slate for a new character + void startChar(); // creates a blank slate for a new incoming character void receiveISR(); // the actual function responding to changes in rx line state - void charToBuffer(uint8_t c); // puts a finished character into the SDI12 instance buffer + void charToBuffer(uint8_t c); // puts a finished character into the SDI12 buffer #ifndef __AVR__ static uint8_t parity_even_bit(uint8_t v); #endif uint8_t _dataPin; // reference to the data pin + + static uint8_t _rxBuffer[SDI12_BUFFER_SIZE]; // A single buffer for ALL SDI-12 objects + static volatile uint8_t _rxBufferTail; + static volatile uint8_t _rxBufferHead; bool _bufferOverflow; // buffer overflow status public: @@ -131,8 +118,7 @@ class SDI12 : public Stream int read(); // returns next byte in the buffer (consumes) void clearBuffer(); // clears the buffer void flush(){}; // Waits for sending to finish - because no TX buffering, does nothing - virtual size_t write(uint8_t byte){return 1;} // dummy function required to inherit from Stream - + virtual size_t write(uint8_t byte); // standard stream function // hide the Stream equivalents to allow custom value to be returned on timeout long parseInt(LookaheadMode lookahead = SKIP_ALL, char ignore = NO_IGNORE_CHAR); From 8228702b384e39a7041e19ebaa5763b12a1929b1 Mon Sep 17 00:00:00 2001 From: SRGDamia1 Date: Fri, 6 Apr 2018 12:14:20 -0400 Subject: [PATCH 26/26] Small changes to examples, update ReadMe --- README.md | 18 +++++++++--------- examples/d_simple_logger/d_simple_logger.ino | 4 ++-- examples/e_simple_parsing/e_simple_parsing.ino | 4 ++-- .../j_external_pcint_library.ino | 4 ++-- library.json | 2 +- library.properties | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6437778..cd9fc59 100644 --- a/README.md +++ b/README.md @@ -21,19 +21,19 @@ Dive into the details of how this library works by reading the documentation in ## Origins and Inherited Limitations -This library was developed from the [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial) library that is a built-in [standard Arduino library](https://www.arduino.cc/en/Reference/Libraries). It was further modified to use a timer to improve read stability using the same interrupt logic as [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial). +This library was developed from the [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial) library that is a built-in [standard Arduino library](https://www.arduino.cc/en/Reference/Libraries). It was further modified to use a timer to improve read stability and decrease the amount of time universal interrupts are disabled using logic from [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial). The most obvious "limitation" is that this library will conflict with all other libraries that make use of pin change interrupts. You will be unable to compile them together. Some other libraries using pin change interrupts include [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial), [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial), [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt/), [PinChangeInt](https://playground.arduino.cc/Main/PinChangeInt), [Servo](https://www.arduino.cc/en/Reference/Servo), and quite a number of other libraries. See the notes under [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) below for advice in using this library in combination with such libraries. -Another non-trivial, but hidden limitation is that _all_ interrupts are disabled during transmission, which can interfere with other processes. That includes other pin-change interrupts, clock/timer interrupts, external interrupts, and every other type of processor interrupt. This is particularly problematic for Arduino-SDI-12, because SDI-12 operates at a very slow baud rate (only 1200 baud). This translates to ~8.3 mS of "radio silence" from the processor for each character that goes out via SDI-12, which adds up to ~380-810ms per command! Interrupts are enabled for the majority of the time while the processor is listening for responses. +Another non-trivial, but hidden limitation is that _all_ interrupts are disabled during most of the transmission of each character, which can interfere with other processes. That includes other pin-change interrupts, clock/timer interrupts, external interrupts, and every other type of processor interrupt. This is particularly problematic for Arduino-SDI-12, because SDI-12 operates at a very slow baud rate (only 1200 baud). This translates to ~8.3 mS of "radio silence" from the processor for each character that goes out via SDI-12, which adds up to ~380-810ms per command! Interrupts are enabled for the majority of the time while the processor is listening for responses. -For most AVR boards, this library will also conflict with the [tone](https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/) function because of its utilization of timer 2. There will be no obvious compile error, but because SDI-12 and the tone library may use different clock-prescaler functions, the results for both might be rather unexpected. - -The Arduino Due, Teensy, and ESP8266/ESP32 boards are not supported at this time. If you are interested in adding support for those boards, feel free to send pull requests. +For most AVR boards, this library will also conflict with the [tone](https://www.arduino.cc/reference/en/language/functions/advanced-io/tone/) function because of its utilization of timer 2. There will be no obvious compile error, but because SDI-12 and the tone library may use different clock-prescaler functions, the results for both might be rather unexpected. All 8MHz AVR boards will also have unresolvable prescaler conflicts with [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial). ## Compatibility Considerations -This library has been tested with an Arduino Uno (AtMega328p), EnviroDIY Mayfly (AtMega1284p), Adafruit Feather 32u4 (AtMega32u4, identical to Arduino Leonardo), and an Adafruit Feather M0 (SAMD21G18, identical to Arduino Zero). +This library has been tested with an Arduino Uno (AtMega328p), EnviroDIY Mayfly (AtMega1284p), Adafruit Feather 32u4 (AtMega32u4, identical to Arduino Leonardo), and an Adafruit Feather M0 (SAMD21G18, identical to Arduino Zero). It should also work on an Arduino Mega (AtMega2560), Gemma/AtTiny board, and most other AVR processors with running on the Arduino framework. + +The Arduino Due, Arduino 101, Teensy, and ESP8266/ESP32 boards are not supported at this time. If you are interested in adding support for those boards, please send pull requests. Not all data pins are available for use with this Arduino-SDI-12 library. Pin availability depends on the micro-controller. These pins will work on those processors: @@ -54,11 +54,11 @@ EnviroDIY_SDI12 is the default master branch of this repository. It controls and #### EnviroDIY_SDI12_PCINT3 EnviroDIY_SDI12_PCINT3 is in the Mayfly branch of this repository, and was historically was called "SDI12_mod". It's been cropped to only control interrupt vector 3, or PCINT3 (D), which on the Mayfly (or Sodaq Mbili) corresponds to Pins D0-D7. -It is designed to be compatible with EnviroDIY_SoftwareSerial_PCINT12 library, which which has been modified to only control interupt vectors 1 & 2, which on the Mayfly corresponds to pins PCINT1 (B) = Pins D08-D15; PCINT2 (C) = Pins D16-D23. -Note that different AtMega1284p boards have a different mapping from the physical PIN numbers to the listed digital PIN numbers that are printed on the board. For more infomation, see the [Pin/Port Bestiary wiki page for the Enable Interrupt library](https://github.com/GreyGnome/EnableInterrupt/wiki/Usage#PIN__PORT_BESTIARY). +It is designed to be compatible with [EnviroDIY_SoftwareSerial_PCINT12](https://github.com/EnviroDIY/SoftwareSerial_PCINT12) library (which controls interrupt vectors PCINT1 (B) & PCINT2 (C) / Mayfly pins D08-D15 & D16-D23) and [EnviroDIY PcInt PCINT0](https://github.com/EnviroDIY/PcInt_PCINT0) (which controls interrupt vectors PCINT0 (A) / Mayfly pins D24-D31/A0-A7). +Note that different AtMega1284p boards have a different mapping from the physical PIN numbers to the listed digital PIN numbers that are printed on the board. One of the most helpful lists of pins and interrupts vectors is in the the [Pin/Port Bestiary wiki page for the Enable Interrupt library](https://github.com/GreyGnome/EnableInterrupt/wiki/Usage#PIN__PORT_BESTIARY). #### EnviroDIY_SDI12_ExtInts -EnviroDIY_SDI12_ExtInts is the ExtInt branch of this repository. It doesn't control any of the interrupts, but instead relies on an external interrupt management library (like [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt)) to assign the SDI-12 receive data function to the right pin. This is the least stable because there's some extra delay because the external library is involved. It's also the easiest to get working in combination with any other pin change interrupt based library. It can be paired with the EnviroDIY_SoftwareSerial_PCINT12 or the EnviroDIY_SoftwareSerial_ExtInts libraries. +EnviroDIY_SDI12_ExtInts is the ExtInt branch of this repository. It doesn't control any of the interrupts, but instead relies on an external interrupt management library (like [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt)) to assign the SDI-12 receive data function to the right pin. This is the least stable because there's some extra delay because the external library is involved, but the use of timers in the SDI-12 library greatly increases it's stability. It's also the easiest to get working in combination with any other pin change interrupt based library. It can be paired with the [EnviroDIY_SoftwareSerial_ExtInts](https://github.com/EnviroDIY/SoftwareSerial_ExternalInts) libraries (which is, by the way, extremely unstable). If you would like to use a different pin change interrupt library, uncomment the line ```#define SDI12_EXTERNAL_PCINT``` in SDI12.h and recompile the library. Then, in your own code call `SDI12::handleInterrupt()` as the interrupt for the SDI12 pin using the other interrupt library. Example j shows doing this in GreyGnome's [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) library. diff --git a/examples/d_simple_logger/d_simple_logger.ino b/examples/d_simple_logger/d_simple_logger.ino index f191383..2078287 100644 --- a/examples/d_simple_logger/d_simple_logger.ino +++ b/examples/d_simple_logger/d_simple_logger.ino @@ -279,8 +279,8 @@ void setup(){ for(byte i = 0; i < 62; i++){ if(isTaken(i)){ found = true; - Serial.print("Found address: "); - Serial.println(i); + Serial.print("First address found: "); + Serial.println(decToChar(i)); break; } } diff --git a/examples/e_simple_parsing/e_simple_parsing.ino b/examples/e_simple_parsing/e_simple_parsing.ino index 13281fc..85f8659 100644 --- a/examples/e_simple_parsing/e_simple_parsing.ino +++ b/examples/e_simple_parsing/e_simple_parsing.ino @@ -330,8 +330,8 @@ void setup(){ for(byte i = 0; i < 62; i++){ if(isTaken(i)){ found = true; - Serial.print("Found address: "); - Serial.println(i); + Serial.print("First address found: "); + Serial.println(decToChar(i)); break; } } diff --git a/examples/j_external_pcint_library/j_external_pcint_library.ino b/examples/j_external_pcint_library/j_external_pcint_library.ino index e81eefe..dcc90cf 100644 --- a/examples/j_external_pcint_library/j_external_pcint_library.ino +++ b/examples/j_external_pcint_library/j_external_pcint_library.ino @@ -244,8 +244,8 @@ void setup(){ for(byte i = 0; i < 62; i++){ if(isTaken(i)){ found = true; - Serial.print("Found address: "); - Serial.println(i); + Serial.print("First address found: "); + Serial.println(decToChar(i)); break; } } diff --git a/library.json b/library.json index 6f9210c..60eb46c 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "Arduino-SDI-12", - "version": "1.3.0", + "version": "1.3.1", "keywords": "SDI-12, sdi12, communication, bus, sensor, Decagon", "description": "Arduino library for SDI-12 communications to a wide variety of environmental sensors.", "repository": diff --git a/library.properties b/library.properties index 64c7243..1d0b906 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino-SDI-12 -version=1.3.0 +version=1.3.1 author=Kevin M. Smith , Shannon Hicks maintainer=Sara Damiano sentence=Arduino library for SDI-12 communications to a wide variety of environmental sensors.