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/ diff --git a/README.md b/README.md index 75c3fcb..cd9fc59 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,24 @@ 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 -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 and decrease the amount of time universal interrupts are disabled using logic from [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 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. 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: @@ -47,12 +53,12 @@ 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. -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_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](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 5d7500f..2078287 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) @@ -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 b7934f1..85f8659 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) @@ -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 1e29cc1..60eb46c 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "Arduino-SDI-12", - "version": "1.1.2", + "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 596e2dc..1d0b906 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino-SDI-12 -version=1.1.2 +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. diff --git a/src/SDI12.cpp b/src/SDI12.cpp index 46fa7ce..c599cbb 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -112,31 +112,50 @@ 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. - */ -#include "SDI12.h" // 0.1 header file for this library +/*=========== 0. Includes, Defines, & Variable Declarations ============= +*/ -#define SDI12_BUFFER_SIZE 64 // 0.2 max RX buffer size +#include "SDI12.h" // Header file for this library +#include "SDI12_boards.h" -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 - // 830 seems to be a reliable value given the overhead of the call. +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 = 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 = 0xFF; // 0b11111111 + +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 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 ============================================ @@ -150,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); } + +// 3.5 Set the timeout return +void SDI12::setTimeoutValue(int value) { TIMEOUT = value; } -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.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 @@ -188,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 @@ -202,32 +572,31 @@ 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 #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; @@ -240,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 @@ -269,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) { @@ -285,7 +654,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: @@ -293,7 +662,8 @@ 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; } default: // DISABLED or ENABLED @@ -306,509 +676,188 @@ 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 100, just for good measure, but we don't want to - // wait the whole stream default of 1s for a response. - setTimeout(100); - // 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); -} -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 -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. - -4.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 - 1 start bit - 7 data bits (least significant bit first) - 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. - -4.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 -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(){ - setState(TRANSMITTING); - digitalWrite(_dataPin, HIGH); - delayMicroseconds(12100); // Required break of 12 milliseconds - digitalWrite(_dataPin, LOW); - delayMicroseconds(8400); // Required marking of 8.33 milliseconds -} - -// 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 - - digitalWrite(_dataPin, HIGH); // 4.2.2 - start bit - delayMicroseconds(bitWidth_micros); - - for (byte mask = 0x01; mask; mask<<=1){ // 4.2.3 - send payload - if(out & mask){ - digitalWrite(_dataPin, LOW); - } - else{ - digitalWrite(_dataPin, HIGH); - } - delayMicroseconds(bitWidth_micros); - } - - digitalWrite(_dataPin, LOW); // 4.2.4 - stop bit - delayMicroseconds(bitWidth_micros); -} - -// 4.3 - this function sends out the characters of the String cmd, one by one -void SDI12::sendCommand(String &cmd) -{ - wakeSensors(); // wake up sensors - for (int unsigned i = 0; i < cmd.length(); i++){ - writeChar(cmd[i]); // write each character - } - setState(LISTENING); // listen for reply -} - -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 - } - setState(LISTENING); // listen for reply -} - -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 - } - setState(LISTENING); // listen for reply -} - -// 4.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) -{ - setState(TRANSMITTING); // Get ready to send data to the recorder - digitalWrite(_dataPin, LOW); - delayMicroseconds(8330); // 8.33 ms marking before response - for (int unsigned i = 0; i < resp.length(); i++){ - writeChar(resp[i]); // write each character - } - setState(LISTENING); // return to listening state -} - -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 - for (int unsigned i = 0; i < strlen(resp); i++){ - writeChar(resp[i]); // write each character - } - setState(LISTENING); // return to listening state -} - -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 - for (int unsigned i = 0; i < strlen_P((PGM_P)resp); i++){ - writeChar((char)pgm_read_byte((const char *)resp + i)); // write each character - } - 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. +/* ============= 6. Waking up, and talking to, the sensors. =================== -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. +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. Setting the SDI-12 object into the TRANSMITTING allows us to +assert control of the line without triggering any interrupts. -5.3 - clearBuffer() is a public function that clears the buffers contents by -setting the index for both head and tail back to zero. +6.2 - This function writes a character out to the data line. SDI-12 +specifies the general transmission format of a single character as: -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(); + 10 bits per data frame + 1 start bit + 7 data bits (least significant bit first) + 1 even parity bit + 1 stop bit -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. +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 "outChar". -// 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; -} +6.3 - sendCommand(String cmd) is a publicly accessible function that +wakes sensors and sends out a String byte by byte the command line. -// 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" -} +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 -// 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 +// 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. + // 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); + delayMicroseconds(marking_micros); // Required marking of 8.33 milliseconds } -// 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(); +// 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...) - if( c < 0 || - c == '-' || - (c >= '0' && c <= '9') || - (detectDecimal && c == '.')) return c; + noInterrupts(); // _ALL_ interrupts disabled so timing can't be shifted - 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 - } -} + 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++; -long SDI12::parseInt(LookaheadMode lookahead, char ignore) -{ - bool isNegative = false; - long value = 0; - int c; + uint8_t parityBit = parity_even_bit(outChar); // Calculate the parity bit + outChar |= (parityBit<<7); // Add parity bit to the outgoing character - 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! + // 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. - 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(); + 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; } - 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! + // Hold the line for the rest of the start bit duration + while ((uint8_t)(TCNTX - t0) < txBitWidth) {} + t0 = TCNTX; // advance start time - 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; + // 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); // set the pin state to LOW for 1's } - read(); // consume the character we got with peek - c = timedPeek(); + else{ + digitalWrite(_dataPin, HIGH); // set the pin state to HIGH for 0's + } + // 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 } - 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. + // Set the line low for the all remaining 1's and the stop bit + digitalWrite(_dataPin, LOW); -Calling mySDI12.begin() will assert mySDI12 as the new active object, -until another instance calls myOtherSDI12.begin() or -myOtherSDI12.setActive(). + interrupts(); // Re-enable universal interrupts as soon as critical timing is past -Calling mySDI12.end() does NOT hand-off active status to another SDI-12 -instance. + // Hold the line low until the end of the 10th bit + uint8_t bitTimeRemaining = txBitWidth*(10-lastHighBit); + while ((uint8_t)(TCNTX - t0) < bitTimeRemaining) {} -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 +// 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 +} -Promoting an inactive to the active instance will start it in the -HOLDING state and return TRUE. +// 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 + } + setState(LISTENING); // listen for reply +} -Otherwise, if the object is currently the active instance, it will -remain unchanged and return FALSE. +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 + } + setState(LISTENING); // listen for reply +} -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. +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 + } + setState(LISTENING); // listen for reply +} -*/ +// 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) { + setState(TRANSMITTING); // Get ready to send data to the recorder + digitalWrite(_dataPin, LOW); + delayMicroseconds(marking_micros); // 8.33 ms marking before response + for (int unsigned i = 0; i < resp.length(); i++){ + writeChar(resp[i]); // write each character + } + setState(LISTENING); // return to listening state +} -// 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; +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 + for (int unsigned i = 0; i < strlen(resp); i++){ + writeChar(resp[i]); // write each character } - return false; + setState(LISTENING); // return to listening state } -// 6.2 - a method for checking if this object is the active object -bool SDI12::isActive() { return this == _activeObject; } +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 + for (int unsigned i = 0; i < strlen_P((PGM_P)resp); i++){ + writeChar((char)pgm_read_byte((const char *)resp + i)); // write each character + } + setState(LISTENING); // return to listening state +} /* ============== 7. Interrupt Service Routine =================== @@ -817,89 +866,149 @@ We have received an interrupt signal, what should we do? 7.1 - Passes off responsibility for the interrupt to the active object. -7.2 - This function quickly reads a new character from the data line in -to the buffer. It takes place over a series of key steps. +7.2 - Creates a blank slate of bits for an incoming character -+ 7.2.1 - Check for the start bit. If it is not there, interrupt may be -from interference or an interrupt we are not interested in, so return. - -+ 7.2.2 - Make space in memory for the new character "newChar". +7.3 - This function checks which direction the change of the interrupt was and +then uses that to populate the bits of the character. -+ 7.2.3 - Wait half of a bit width to help center on the next bit. - -+ 7.2.4 - For each of the 8 bits in the payload, read whether or not the -line state is HIGH or LOW. We use a moving mask here, as was previously -demonstrated in the writeByte() function. ++ First check if we're expecting a start bit and if this change is in the +right direction for the start bit. If it is not, interrupt may be +from interference or an interrupt we are not interested in, so return. +Because the SDI-12 protocol specifies inverse logic, the end of a start bit will +be a change from LOW to HIGH. -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. ++ 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. 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. -+ 7.2.5 - Skip the parity bit. There is no error checking. +7.4 - Puts a new character into the active SDI-12 buffer -+ 7.2.6 - Skip the stop bit. - -+ 7.2.7 - Check for an overflow. We do this by checking if advancing the -tail would make it have the same index as the head (in a circular -fashion). - -+ 7.2.8 - Save the byte into the buffer if there has not been an -overflow, and then advance the tail index. - -7.3 - Check if the various interrupt vectors are defined. If they are +7.5 - Check if the various interrupt vectors are defined. If they are the ISR is instructed to call _handleInterrupt() when they trigger. */ // 7.1 - Passes off responsibility for the interrupt to the active object. void SDI12::handleInterrupt(){ - if (_activeObject) _activeObject->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? - { - setPinInterrupts(false); // Disable further interrupts during reception + rxState = 0; // got a start bit + rxMask = 0x01; // 0b00000001, bit mask, lsb first + rxValue = 0x00; // 0b00000000, RX character to be, a blank slate +} // startChar - uint8_t newChar = 0; // 7.2.2 - Make room for char. +// 7.3 - The actual interrupt service routine +void SDI12::receiveISR() +{ + 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 + // 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 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(); + } - delayMicroseconds(bitWidth_micros/2); // 7.2.3 - Wait 1/2 of a bit to get settled - // It would be better if we had a measure of the real latency and could properly center on the 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 = 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/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 + // 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 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 + // the last change and now must have been LOW. + if (pinLevel == HIGH) { + // back fill previous bits with 1's (inverse logic - LOW = 1) + while (bitsThisFrame-- > 0) { + 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; // 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 + else { // pinLevel==LOW + // previous bits were 0's so only this bit is a 1 (inverse logic - LOW = 1) + 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 + } - for (uint8_t i=0x1; i<0x80; i <<= 1) // 7.2.4 - read the 7 data bits - { - delayMicroseconds(bitWidth_micros); - uint8_t noti = ~i; - if (!digitalRead(_dataPin)) - newChar |= i; - else - newChar &= noti; + // If this was the 8th or more bit then the character and parity are complete. + if (rxState > 7) { + rxValue &= 0x7F; // 0b01111111, Throw away the parity bit + charToBuffer(rxValue); // Put the finished character into the buffer + + + // 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 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(); + } } - delayMicroseconds(bitWidth_micros); // 7.2.5 - Skip the parity bit. - delayMicroseconds(bitWidth_micros); // 7.2.6 - Skip the stop bit. - setPinInterrupts(true); // Re-enable interrupts + } + prevBitTCNT = thisBitTCNT; // remember time stamp of this change! +} - // 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; - } +// 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 717d60d..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,17 +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 receiveChar(); // used by the ISR to grab a char from data line + 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 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: @@ -129,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); diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h new file mode 100644 index 0000000..a3e95a7 --- /dev/null +++ b/src/SDI12_boards.h @@ -0,0 +1,133 @@ +// 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 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 + #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 "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 + // // 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 + + +// ATtiny boards (ie, adafruit trinket) +// +#elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) + + #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 + // 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 + #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 + // 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 + #define RX_WINDOW_FUDGE 5 + #endif + + +// Arduino Zero other SAMD21 boards +// +#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || \ + defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__SAMD21E18A__) + + #define TIMER_IN_USE_STR "GCLK4-TC4" + #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' + // (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" +#endif