├── VERSION ├── docs ├── .prettierignore ├── SDI-12Text.png ├── SDI-12Text-Cropped.png ├── SDI-12_version-1_4-Dec-1-2017.pdf ├── SDI-12_version1_3 January 28, 2016.pdf ├── markdown_prefilter.py ├── mcss-Doxyfile ├── fixXmlExampleSections.py ├── mcss-conf.py ├── documentExamples.py ├── OverviewOfInterrupts.md ├── DoxygenLayout.xml └── CreatingACharacter.md ├── src ├── ReadMe.md ├── src.dox ├── SDI12_boards.cpp └── SDI12_boards.h ├── .github ├── FUNDING.yml └── workflows │ ├── changelog_reminder.yaml │ ├── verify_library_json.yaml │ ├── prepare_release.yaml │ ├── build_examples.yaml │ └── build_documentation.yaml ├── tools ├── powerOn │ └── powerOn.ino ├── SDI12_spy │ └── SDI12_spy.ino └── TestWarmUp │ └── TestWarmUp.ino ├── keywords.txt ├── library.properties ├── examples ├── a_wild_card │ ├── ReadMe.md │ └── a_wild_card.ino ├── g_terminal_window │ ├── ReadMe.md │ └── g_terminal_window.ino ├── f_basic_data_request │ ├── ReadMe.md │ └── f_basic_data_request.ino ├── j_external_pcint_library │ ├── ReadMe.md │ └── j_external_pcint_library.ino ├── k_concurrent_logger │ ├── ReadMe.md │ └── k_concurrent_logger.ino ├── c_check_all_addresses │ ├── ReadMe.md │ └── c_check_all_addresses.ino ├── h_SDI-12_slave_implementation │ ├── ReadMe.md │ └── h_SDI-12_slave_implementation.ino ├── i_SDI-12_interface │ ├── ReadMe.md │ └── i_SDI-12_interface.ino ├── d_simple_logger │ ├── ReadMe.md │ └── d_simple_logger.ino ├── e_continuous_measurement │ ├── ReadMe.md │ └── e_continuous_measurement.ino ├── b_address_change │ ├── ReadMe.md │ └── b_address_change.ino └── ReadMe.md ├── library.json ├── LICENSE.md ├── .clang-format ├── ChangeLog.md ├── continuous_integration └── .travis.yml_archive └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | v2.1.4 -------------------------------------------------------------------------------- /docs/.prettierignore: -------------------------------------------------------------------------------- 1 | doxygen/header.html -------------------------------------------------------------------------------- /src/ReadMe.md: -------------------------------------------------------------------------------- 1 | These are the library source files. 2 | -------------------------------------------------------------------------------- /docs/SDI-12Text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhujiedong/Arduino-SDI-12/master/docs/SDI-12Text.png -------------------------------------------------------------------------------- /src/src.dox: -------------------------------------------------------------------------------- 1 | /** 2 | * @dir src 3 | * 4 | * @brief Contains the source code for the SDI-12 library. 5 | */ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: ['https://stroudcenter.org/donate/'] 3 | -------------------------------------------------------------------------------- /docs/SDI-12Text-Cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhujiedong/Arduino-SDI-12/master/docs/SDI-12Text-Cropped.png -------------------------------------------------------------------------------- /docs/SDI-12_version-1_4-Dec-1-2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhujiedong/Arduino-SDI-12/master/docs/SDI-12_version-1_4-Dec-1-2017.pdf -------------------------------------------------------------------------------- /docs/SDI-12_version1_3 January 28, 2016.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhujiedong/Arduino-SDI-12/master/docs/SDI-12_version1_3 January 28, 2016.pdf -------------------------------------------------------------------------------- /tools/powerOn/powerOn.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int8_t powerPin = 22; 4 | 5 | void setup() { 6 | pinMode(powerPin, OUTPUT); 7 | digitalWrite(powerPin, HIGH); 8 | pinMode(A5, OUTPUT); 9 | digitalWrite(A5, HIGH); 10 | pinMode(10, OUTPUT); 11 | digitalWrite(10, HIGH); 12 | } 13 | 14 | void loop() {} 15 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | 2 | # Syntax Coloring Map for SDI12 when 3 | # using the Arduino IDE. 4 | 5 | ### Classes (KEYWORD1) 6 | 7 | SDI12 KEYWORD1 8 | 9 | ### Methods and Functions (KEYWORD2) 10 | 11 | begin KEYWORD2 12 | end KEYWORD2 13 | forceHold KEYWORD2 14 | forceListen KEYWORD2 15 | sendCommand KEYWORD2 16 | sendResponse KEYWORD2 17 | available KEYWORD2 18 | peek KEYWORD2 19 | read KEYWORD2 20 | clearBuffer KEYWORD2 21 | flush KEYWORD2 22 | setActive KEYWORD2 23 | isActive KEYWORD2 24 | handleInterrupt KEYWORD2 25 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SDI-12 2 | version=2.1.4 3 | author=Kevin M. Smith , Shannon Hicks 4 | maintainer=Sara Damiano 5 | sentence=An Arduino library for SDI-12 communication with a wide variety of environmental sensors. 6 | paragraph=This library provides a general software solution, without requiring any additional hardware. 7 | category=Communication 8 | url=https://github.com/EnviroDIY/Arduino-SDI-12 9 | architectures=avr,sam,samd,espressif 10 | includes=SDI12.h 11 | -------------------------------------------------------------------------------- /examples/a_wild_card/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_a_page Example A: Using the Wildcard - Getting Single Sensor Information ) 2 | # Example A: Using the Wildcard - Getting Single Sensor Information 3 | 4 | This is a simple demonstration of the SDI-12 library for Arduino. 5 | It requests information about a single attached sensor, including its address and manufacturer info, and prints it to the serial port 6 | 7 | [//]: # ( @section a_wild_card_pio PlatformIO Configuration ) 8 | 9 | [//]: # ( @include{lineno} a_wild_card/platformio.ini ) 10 | 11 | [//]: # ( @section a_wild_card_code The Complete Example ) 12 | -------------------------------------------------------------------------------- /examples/g_terminal_window/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_g_page Example G: Using the Arduino as a Command Terminal for SDI-12 Sensors ) 2 | # Example G: Using the Arduino as a Command Terminal for SDI-12 Sensors 3 | 4 | This is a simple demonstration of the SDI-12 library for Arduino. 5 | 6 | It's purpose is to allow a user to interact with an SDI-12 sensor directly, issuing commands through a serial terminal window. 7 | 8 | [//]: # ( @section g_terminal_window_pio PlatformIO Configuration ) 9 | 10 | [//]: # ( @include{lineno} g_terminal_window/platformio.ini ) 11 | 12 | [//]: # ( @section g_terminal_window_code The Complete Example ) 13 | -------------------------------------------------------------------------------- /examples/f_basic_data_request/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_f_page Example F: Basic Data Request to a Single Sensor ) 2 | # Example F: Basic Data Request to a Single Sensor 3 | 4 | This is a simple demonstration of the SDI-12 library for Arduino. 5 | 6 | This is a very basic (stripped down) example where the user initiates a measurement and receives the results to a terminal window without typing numerous commands into the terminal. 7 | 8 | [//]: # ( @section f_basic_data_request_pio PlatformIO Configuration ) 9 | 10 | [//]: # ( @include{lineno} f_basic_data_request/platformio.ini ) 11 | 12 | [//]: # ( @section f_basic_data_request_code The Complete Example ) 13 | -------------------------------------------------------------------------------- /.github/workflows/changelog_reminder.yaml: -------------------------------------------------------------------------------- 1 | on: pull_request 2 | name: Changelog Reminder 3 | jobs: 4 | remind: 5 | name: Changelog Reminder 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | with: 10 | persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token 11 | 12 | - name: Changelog Reminder 13 | uses: peterjgrainger/action-changelog-reminder@v1.3.0 14 | with: 15 | changelog_regex: '/ChangeLog\/.*\/*.md' 16 | customPrMessage: 'Please add your changes to the change log!' 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.SARA_PUSH_TOKEN }} 19 | -------------------------------------------------------------------------------- /examples/j_external_pcint_library/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_j_page Example J: Using External Interrupts ) 2 | # Example J: Using External Interrupts 3 | 4 | This is identical to example D, except that instead of using internal definitions of pin change interrupt vectors, it depends on another library to define them for it. 5 | 6 | To use this example, you must remove the comment braces around `#define SDI12_EXTERNAL_PCINT` in the library and re-compile it. 7 | 8 | [//]: # ( @section j_external_pcint_library_pio PlatformIO Configuration ) 9 | 10 | [//]: # ( @include{lineno} j_external_pcint_library/platformio.ini ) 11 | 12 | [//]: # ( @section j_external_pcint_library_code The Complete Example ) 13 | -------------------------------------------------------------------------------- /examples/k_concurrent_logger/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_k_page Example K: Concurrent Measurements ) 2 | # Example K: Concurrent Measurements 3 | 4 | This is very similar to example D - finding all attached sensors and logging data from them. 5 | Unlike example D, however, which waits for each sensor to complete a measurement, this asks all sensors to take measurements concurrently and then waits until each is finished to query for results. 6 | This can be much faster than waiting for each sensor when you have multiple sensor attached. 7 | 8 | [//]: # ( @section k_concurrent_logger_pio PlatformIO Configuration ) 9 | 10 | [//]: # ( @include{lineno} k_concurrent_logger/platformio.ini ) 11 | 12 | [//]: # ( @section k_concurrent_logger_code The Complete Example ) 13 | -------------------------------------------------------------------------------- /examples/c_check_all_addresses/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_c_page Example C: Check all Addresses for Active Sensors and Print Status ) 2 | # Example C: Check all Addresses for Active Sensors and Print Status 3 | 4 | This is a simple demonstration of the SDI-12 library for Arduino. 5 | 6 | It discovers the address of all sensors active on any pin on your board. 7 | 8 | Each sensor should have a unique address already - if not, multiple sensors may respond simultaenously to the same request and the output will not be readable by the Arduino. 9 | 10 | To address a sensor, please see Example B: b_address_change.ino 11 | 12 | [//]: # ( @section c_check_all_addresses_pio PlatformIO Configuration ) 13 | 14 | [//]: # ( @include{lineno} c_check_all_addresses/platformio.ini ) 15 | 16 | [//]: # ( @section c_check_all_addresses_code The Complete Example ) 17 | -------------------------------------------------------------------------------- /examples/h_SDI-12_slave_implementation/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_h_page Example H: Using SDI-12 in Slave Mode ) 2 | # Example H: Using SDI-12 in Slave Mode 3 | 4 | Example sketch demonstrating how to implement an Arduino as a slave on an SDI-12 bus. This may be used, for example, as a middleman between an I2C sensor and an SDI-12 datalogger. 5 | 6 | Note that an SDI-12 slave must respond to M! or C! with the number of values it will report and the max time until these values will be available. This example uses 9 values available in 21 s, but references to these numbers and the output array size and datatype should be changed for your specific application. 7 | 8 | [//]: # ( @section h_SDI-12_slave_implementation_pio PlatformIO Configuration ) 9 | 10 | [//]: # ( @include{lineno} h_SDI-12_slave_implementation/platformio.ini ) 11 | 12 | [//]: # ( @section h_SDI-12_slave_implementation_code The Complete Example ) 13 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SDI-12", 3 | "version": "2.1.4", 4 | "keywords": "SDI-12, sdi12, communication, bus, sensor, Decagon", 5 | "description": "An Arduino library for SDI-12 communication with a wide variety of environmental sensors.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/EnviroDIY/Arduino-SDI-12.git" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Kevin M. Smith", 13 | "email": "Kevin@elite-education.org" 14 | }, 15 | { 16 | "name": "Shannon Hicks", 17 | "email": "shicks@stroudcenter.org" 18 | }, 19 | { 20 | "name": "Sara Damiano", 21 | "email": "sdamiano@stroudcenter.org", 22 | "maintainer": true 23 | } 24 | ], 25 | "license": "BSD-3-Clause", 26 | "frameworks": "arduino", 27 | "platforms": [ 28 | "atmelavr", 29 | "atmelsam" 30 | ], 31 | "export": { 32 | "exclude": ["doc/*"] 33 | }, 34 | "examples": ["examples/*/*.ino"] 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/verify_library_json.yaml: -------------------------------------------------------------------------------- 1 | name: Verify JSON structure for library manifest 2 | 3 | # Triggers the workflow on push or pull request events 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | if: "!contains(github.event.head_commit.message, 'ci skip')" 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v1.4.4 16 | 17 | - name: Cache Node.js modules 18 | uses: actions/cache@v2.1.4 19 | with: 20 | # npm cache files are stored in `~/.npm` on Linux/macOS 21 | path: ~/.npm 22 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} 23 | restore-keys: | 24 | ${{ runner.OS }}-node- 25 | ${{ runner.OS }}- 26 | 27 | - name: install jsonlint 28 | run: npm install -g jsonlint 29 | 30 | - name: run jsonlint 31 | run: jsonlint -q library.json 32 | -------------------------------------------------------------------------------- /examples/i_SDI-12_interface/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_i_page Example I: SDI-12 PC Interface ) 2 | # Example I: SDI-12 PC Interface 3 | 4 | Code for an Arduino-based USB dongle translates serial comm from PC to SDI-12 (electrical and timing) 5 | 1. Allows user to communicate to SDI-12 devices from a serial terminal emulator (e.g. PuTTY). 6 | 2. Able to spy on an SDI-12 bus for troubleshooting comm between datalogger and sensors. 7 | 3. Can also be used as a hardware middleman for interfacing software to an SDI-12 sensor. For example, implementing an SDI-12 datalogger in Python on a PC. Use verbatim mode with feedback off in this case. 8 | 9 | Note: "translation" means timing and electrical interface. It does not ensure SDI-12 compliance of commands sent via it. 10 | 11 | [//]: # ( @section i_SDI-12_interface_pio PlatformIO Configuration ) 12 | 13 | [//]: # ( @include{lineno} i_SDI-12_interface/platformio.ini ) 14 | 15 | [//]: # ( @section i_SDI-12_interface_code The Complete Example ) 16 | -------------------------------------------------------------------------------- /examples/d_simple_logger/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_d_page Example D: Check all Addresses for Active Sensors and Log Data ) 2 | ## Example D: Check all Addresses for Active Sensors and Log Data 3 | 4 | This is a simple demonstration of the SDI-12 library for Arduino. 5 | 6 | It discovers the address of all sensors active on a single bus and takes measurements from them. 7 | 8 | Every SDI-12 device is different in the time it takes to take a measurement, and the amount of data it returns. This sketch will not serve every sensor type, but it will likely be helpful in getting you started. 9 | 10 | Each sensor should have a unique address already - if not, multiple sensors may respond simultaneously to the same request and the output will not be readable by the Arduino. 11 | 12 | To address a sensor, please see Example B: b_address_change.ino 13 | 14 | [//]: # ( @section d_simple_logger_pio PlatformIO Configuration ) 15 | 16 | [//]: # ( @include{lineno} d_simple_logger/platformio.ini ) 17 | 18 | [//]: # ( @section d_simple_logger_code The Complete Example ) 19 | -------------------------------------------------------------------------------- /examples/e_continuous_measurement/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_e_page Example E: Check all Addresses for Active Sensors and Start Continuous Measurements ) 2 | ## Example E: Check all Addresses for Active Sensors and Start Continuous Measurements 3 | 4 | This is a simple demonstration of the SDI-12 library for Arduino. 5 | 6 | It discovers the address of all sensors active on a single bus and takes continuous measurements from them. 7 | 8 | Every SDI-12 device is different in the time it takes to take a measurement, and the amount of data it returns. This sketch will not serve every sensor type, but it will likely be helpful in getting you started. 9 | 10 | Each sensor should have a unique address already - if not, multiple sensors may respond simultaneously to the same request and the output will not be readable by the Arduino. 11 | 12 | To address a sensor, please see Example B: b_address_change.ino 13 | 14 | [//]: # ( @section e_continuous_measurement_pio PlatformIO Configuration ) 15 | 16 | [//]: # ( @include{lineno} e_continuous_measurement/platformio.ini ) 17 | 18 | [//]: # ( @section e_continuous_measurement_code The Complete Example ) 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2013, Stroud Water Research Center (SWRC) and the EnviroDIY Development Team. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 16 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 | -------------------------------------------------------------------------------- /docs/markdown_prefilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput, re 3 | 4 | print_me = True 5 | skip_me = False 6 | i = 1 7 | # for line in fileinput.input(openhook=fileinput.hook_encoded("utf-8", "surrogateescape")): 8 | for line in fileinput.input(): 9 | # print(i, print_me, skip_me, line) 10 | 11 | # Remove markdown comment tags from doxygen commands within the markdown 12 | if print_me and not skip_me: 13 | print(re.sub(r'\[//\]: # \( @(\w+?.*) \)', r'@\1', line), end="") 14 | 15 | # using skip_me to skip single lines, so unset it after reading a line 16 | if skip_me: 17 | skip_me = False; 18 | 19 | # a page, section, subsection, or subsubsection commands followed 20 | # immediately with by a markdown header leads to that section appearing 21 | # twice in the doxygen html table of contents. 22 | # I'm putting the section markers right above the header and then will skip the header. 23 | if re.match(r'\[//\]: # \( @mainpage', line) is not None: 24 | skip_me = True; 25 | if re.match(r'\[//\]: # \( @page', line) is not None: 26 | skip_me = True; 27 | if re.match(r'\[//\]: # \( @.*section', line) is not None: 28 | skip_me = True; 29 | if re.match(r'\[//\]: # \( @paragraph', line) is not None: 30 | skip_me = True; 31 | 32 | # I'm using these comments to fence off content that is only intended for 33 | # github mardown rendering 34 | if "[//]: # ( Start GitHub Only )" in line: 35 | print_me = False 36 | 37 | if "[//]: # ( End GitHub Only )" in line: 38 | print_me = True 39 | 40 | i += 1 41 | -------------------------------------------------------------------------------- /docs/mcss-Doxyfile: -------------------------------------------------------------------------------- 1 | @INCLUDE = Doxyfile 2 | GENERATE_HTML = NO 3 | GENERATE_XML = YES 4 | XML_PROGRAMLISTING = NO 5 | HTML_OUTPUT = ../Arduino-SDI-12Doxygen/m.css 6 | SHOW_INCLUDE_FILES = YES 7 | WARN_LOGFILE = mcssDoxygenOutput.log 8 | PROJECT_REPOSITORY = https://github.com/EnviroDIY/Arduino-SDI-12 9 | ALIASES += \ 10 | "m_div{1}=@xmlonly@endxmlonly" \ 11 | "m_enddiv=@xmlonly@endxmlonly" \ 12 | "m_span{1}=@xmlonly@endxmlonly" \ 13 | "m_endspan=@xmlonly@endxmlonly" \ 14 | "m_class{1}=@xmlonly@endxmlonly" \ 15 | "m_footernavigation=@xmlonly@endxmlonly" \ 16 | "m_examplenavigation{2}=@xmlonly@endxmlonly" \ 17 | "m_keywords{1}=@xmlonly@endxmlonly" \ 18 | "m_keyword{3}=@xmlonly@endxmlonly" \ 19 | "m_enum_values_as_keywords=@xmlonly@endxmlonly" \ 20 | "m_since{2}=@since @m_class{m-label m-success m-flat} @ref changelog-\1-\2 \"since v\1.\2\"" \ 21 | "m_deprecated_since{2}=@since deprecated in v\1.\2 @deprecated" -------------------------------------------------------------------------------- /examples/a_wild_card/a_wild_card.ino: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @file a_wild_card.ino 4 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 5 | * and the EnviroDIY Development Team 6 | * This example is published under the BSD-3 license. 7 | * @author Kevin M.Smith 8 | * @date August 2013 9 | * 10 | * @brief Example A: Using the Wildcard - Getting Single Sensor Information 11 | * 12 | * This is a simple demonstration of the SDI-12 library for Arduino. 13 | * 14 | * It requests information about the attached sensor, including its address and 15 | * manufacturer info. 16 | */ 17 | 18 | #include 19 | 20 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 21 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 22 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 23 | 24 | /** Define the SDI-12 bus */ 25 | SDI12 mySDI12(DATA_PIN); 26 | 27 | /** 28 | '?' is a wildcard character which asks any and all sensors to respond 29 | 'I' indicates that the command wants information about the sensor 30 | '!' finishes the command 31 | */ 32 | String myCommand = "?I!"; 33 | 34 | void setup() { 35 | Serial.begin(SERIAL_BAUD); 36 | while (!Serial) 37 | ; 38 | 39 | Serial.println("Opening SDI-12 bus..."); 40 | mySDI12.begin(); 41 | delay(500); // allow things to settle 42 | 43 | // Power the sensors; 44 | if (POWER_PIN > 0) { 45 | Serial.println("Powering up sensors..."); 46 | pinMode(POWER_PIN, OUTPUT); 47 | digitalWrite(POWER_PIN, HIGH); 48 | delay(200); 49 | } 50 | } 51 | 52 | void loop() { 53 | mySDI12.sendCommand(myCommand); 54 | delay(300); // wait a while for a response 55 | while (mySDI12.available()) { // write the response to the screen 56 | Serial.write(mySDI12.read()); 57 | } 58 | delay(3000); // print again in three seconds 59 | } 60 | -------------------------------------------------------------------------------- /examples/b_address_change/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page example_b_page Example B: Changing the Address of your SDI-12 Sensor ) 2 | # Example B: Changing the Address of your SDI-12 Sensor 3 | 4 | Communication with an SDI-12 sensor depends on its 1-character alphanumeric address (1-9, A-Z, a-z). A sensor can also be programmed with an address of 0, but that address cannot always be used to get measurements from the sensor. This sketch enables you to find and change the address of your sensor. 5 | 6 | First, physically connect your SDI-12 sensor to your device. Some helpful hits for connecting it can be found here: https://envirodiy.org/topic/logging-mayfly-with-decagon-sdi-12-sensor/#post-2129. 7 | 8 | Once your sensor is physically connected to your board, download this library and open this sketch. 9 | 10 | Scroll to line 54 of the sketch (`#define DATA_PIN 7`). Change the `7` to the pin number that your sensor is attached to. 11 | 12 | Set the pin to provide power to your sensor in line 55 (`#define POWER_PIN 22`). If your sensor is continuously powered, set the power pin to -1. 13 | 14 | Upload the sketch to your board. After the upload finishes, open up the serial port monitor at a baud rate of 115200 on line 53. 15 | 16 | In the serial monitor you will see it begin scanning through all possible SDI-12 addresses. Once it has found an occupied address, it will stop and ask you to enter a new address. Send your desired address to the serial port. On the screen you should see "Readdressing Sensor." followed by "Success. Rescanning for verification." The scan will begin again, stopping at your new address. If you are now happy with the address you've selected, smile and close the serial port monitor. 17 | 18 | If you are using a Meter Group Hydros 21 CTD sensor, change the channel to 1 in the serial monitor where prompted. 19 | 20 | [//]: # ( @section b_address_change_pio PlatformIO Configuration ) 21 | 22 | [//]: # ( @include{lineno} b_address_change/platformio.ini ) 23 | 24 | [//]: # ( @section b_address_change_code The Complete Example ) 25 | -------------------------------------------------------------------------------- /tools/SDI12_spy/SDI12_spy.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file h_SDI-12_slave_implementation.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @date 2016 7 | * @author D. Wasielewski 8 | * 9 | * @brief Example H: Using SDI-12 in Slave Mode 10 | * 11 | * Example sketch demonstrating how to implement an arduino as a slave on an SDI-12 bus. 12 | * This may be used, for example, as a middleman between an I2C sensor and an SDI-12 13 | * datalogger. 14 | * 15 | * Note that an SDI-12 slave must respond to M! or C! with the number of values it will 16 | * report and the max time until these values will be available. This example uses 9 17 | * values available in 21 s, but references to these numbers and the output array size 18 | * and datatype should be changed for your specific application. 19 | * 20 | * D. Wasielewski, 2016 21 | * Builds upon work started by: 22 | * https://github.com/jrzondagh/AgriApps-SDI-12-Arduino-Sensor 23 | * https://github.com/Jorge-Mendes/Agro-Shield/tree/master/SDI-12ArduinoSensor 24 | * 25 | * Suggested improvements: 26 | * - Get away from memory-hungry arduino String objects in favor of char buffers 27 | * - Make an int variable for the "number of values to report" instead of the 28 | * hard-coded 9s interspersed throughout the code 29 | */ 30 | 31 | #include 32 | 33 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 34 | 35 | // Create object by which to communicate with the SDI-12 bus on SDIPIN 36 | SDI12 slaveSDI12(DATA_PIN); 37 | 38 | void setup() { 39 | Serial.begin(115200); 40 | slaveSDI12.begin(); 41 | delay(500); 42 | slaveSDI12.forceListen(); // sets SDIPIN as input to prepare for incoming message 43 | Serial.println("Starting SDI-12 Spy"); 44 | } 45 | 46 | void loop() { 47 | while (slaveSDI12.available()) { 48 | int readChar = slaveSDI12.read(); 49 | Serial.write(readChar); 50 | // if (readChar == '\n') { 51 | // slaveSDI12.forceListen(); 52 | // } else { 53 | // delay(10);// 1 character ~ 7.5ms 54 | // } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/fixXmlExampleSections.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | import re 4 | import os 5 | import glob 6 | import xml.etree.ElementTree as ET 7 | 8 | fileDir = os.path.dirname(os.path.realpath("__file__")) 9 | # print("Program Directory: {}".format(fileDir)) 10 | relative_dir = "../../Arduino-SDI-12Doxygen/xml/" 11 | abs_file_path = os.path.join(fileDir, relative_dir) 12 | abs_file_path = os.path.abspath(os.path.realpath(abs_file_path)) 13 | # print("XML Directory: {}".format(fileDir)) 14 | 15 | all_files = [ 16 | f 17 | for f in os.listdir(abs_file_path) 18 | if os.path.isfile(os.path.join(abs_file_path, f)) and f.endswith("8ino-example.xml") 19 | ] 20 | 21 | for filename in all_files: 22 | print(filename) 23 | 24 | tree = ET.parse(os.path.join(abs_file_path, filename)) 25 | root = tree.getroot() 26 | 27 | needs_to_be_fixed = False 28 | for definition in root.iter("compounddef"): 29 | # print(definition.attrib) 30 | compound_id = definition.attrib["id"] 31 | # print(compound_id) 32 | # print("---") 33 | 34 | for i in range(6): 35 | for section in definition.iter("sect" + str(i)): 36 | # print(section.attrib) 37 | section_id = section.attrib["id"] 38 | if not section_id.startswith(compound_id): 39 | # print("problem!") 40 | needs_to_be_fixed = True 41 | dox_loc = section_id.find(".dox_") 42 | section_suffix = section_id[dox_loc + 6 :] 43 | # print(section_suffix) 44 | corrected_id = compound_id + "_" + section_suffix 45 | # print(corrected_id) 46 | section.attrib["id"] = corrected_id 47 | 48 | if needs_to_be_fixed: 49 | tree.write(os.path.join(abs_file_path, filename + "_fixed")) 50 | os.rename( 51 | os.path.join(abs_file_path, filename), 52 | os.path.join(abs_file_path, filename + "_original"), 53 | ) 54 | os.rename( 55 | os.path.join(abs_file_path, filename + "_fixed"), 56 | os.path.join(abs_file_path, filename), 57 | ) 58 | # print() 59 | -------------------------------------------------------------------------------- /.github/workflows/prepare_release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | # Sequence of patterns matched against refs/tags 6 | paths: 7 | - 'VERSION' # Push events when the VERSION file changes 8 | workflow_dispatch: 9 | 10 | name: Prepare a new release 11 | 12 | env: 13 | PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} 14 | 15 | jobs: 16 | release: 17 | name: Prepare a new release 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Set variables 25 | run: | 26 | echo "::debug::Get the current version number" 27 | VER=$(cat VERSION) 28 | echo "VERSION=$VER" >> $GITHUB_ENV 29 | 30 | - name: Restore or Cache pip 31 | uses: actions/cache@v2.1.4 32 | with: 33 | path: ~/.cache/pip 34 | # if requirements.txt hasn't changed, then it will be a "cache hit" and pip will be restored 35 | # if requirements.txt HAS changed, it will be a "cache miss" and a new cache of pip will be created if the job completes successfully 36 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 37 | restore-keys: ${{ runner.os }}-pip- 38 | 39 | - name: Set up Python 40 | uses: actions/setup-python@v2 41 | 42 | # This should be pulled from cache, if there's not a new version 43 | - name: Install PlatformIO 44 | run: | 45 | python -m pip install --upgrade pip 46 | pip install --upgrade platformio 47 | 48 | - name: Get notes 49 | id: generate_notes 50 | uses: anmarkoulis/commitizen-changelog-reader@master 51 | with: 52 | # NOTE: Need to add the refs/tags to work with the generate notes action 53 | tag_name: ${{ format('refs/tags/{0}', env.VERSION) }} 54 | changelog: ChangeLog.md 55 | 56 | # Create a new release 57 | - name: Create Release 58 | id: create_release 59 | uses: actions/create-release@v1 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | with: 63 | tag_name: ${{ env.VERSION }} 64 | release_name: ${{ env.VERSION }} 65 | draft: false 66 | prerelease: false 67 | body: ${{join(fromJson(steps.generate_notes.outputs.notes).notes, '')}} 68 | 69 | # Publish the new release to the pio package manager 70 | - name: Publish release to PIO 71 | id: publish-pio 72 | run: pio package publish 73 | -------------------------------------------------------------------------------- /examples/g_terminal_window/g_terminal_window.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file g_terminal_window.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Kevin M.Smith 7 | * @date August 2013 8 | * @author Ruben Kertesz or @rinnamon on twitter 9 | * @date 2016 10 | * 11 | * @brief Example G: Using the Arduino as a Command Terminal for SDI-12 Sensors 12 | * 13 | * This is a simple demonstration of the SDI-12 library for Arduino. It's purpose is to 14 | * allow a user to interact with an SDI-12 sensor directly, issuing commands through a 15 | * serial terminal window. 16 | * 17 | * Edited by Ruben Kertesz for ISCO Nile 502 2/10/2016 18 | */ 19 | 20 | #include 21 | 22 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 23 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 24 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 25 | 26 | /** Define the SDI-12 bus */ 27 | SDI12 mySDI12(DATA_PIN); 28 | 29 | char inByte = 0; 30 | String sdiResponse = ""; 31 | String myCommand = ""; 32 | 33 | void setup() { 34 | Serial.begin(SERIAL_BAUD); 35 | while (!Serial) 36 | ; 37 | 38 | Serial.println("Opening SDI-12 bus..."); 39 | mySDI12.begin(); 40 | delay(500); // allow things to settle 41 | 42 | // Power the sensors; 43 | if (POWER_PIN > 0) { 44 | Serial.println("Powering up sensors..."); 45 | pinMode(POWER_PIN, OUTPUT); 46 | digitalWrite(POWER_PIN, HIGH); 47 | delay(200); 48 | } 49 | } 50 | 51 | void loop() { 52 | if (Serial.available()) { 53 | inByte = Serial.read(); 54 | if ((inByte != '\n') && 55 | (inByte != '\r')) { // read all values entered in terminal window before enter 56 | myCommand += inByte; 57 | delay(10); // 1 character ~ 7.5ms 58 | } 59 | } 60 | 61 | if (inByte == '\r') { // once we press enter, send string to SDI sensor/probe 62 | inByte = 0; 63 | Serial.println(myCommand); 64 | mySDI12.sendCommand(myCommand); 65 | delay(30); // wait a while for a response 66 | 67 | while (mySDI12.available()) { // build a string of the response 68 | char c = mySDI12.read(); 69 | if ((c != '\n') && (c != '\r')) { 70 | sdiResponse += c; 71 | delay(10); // 1 character ~ 7.5ms 72 | } 73 | } 74 | if (sdiResponse.length() >= 1) 75 | Serial.println(sdiResponse); // write the response to the screen 76 | 77 | mySDI12.clearBuffer(); // clear the line 78 | myCommand = ""; 79 | sdiResponse = ""; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/mcss-conf.py: -------------------------------------------------------------------------------- 1 | DOXYFILE = "mcss-Doxyfile" 2 | THEME_COLOR = "#cb4b16" 3 | # FAVICON = "https://3qzcxr28gq9vutx8scdn91zq-wpengine.netdna-ssl.com/wp-content/uploads/2016/05/cropped-EnviroDIY_LogoMaster_TrueSquare_V5_TwoTree_Trans_notext-192x192.png" 4 | FAVICON = "SDI-12Text-Cropped.png" 5 | LINKS_NAVBAR1 = [ 6 | ( 7 | "Functions", 8 | "class_s_d_i12", 9 | [ 10 | ('Constructor, Destructor, Begins, and Setters', ), 11 | ('Waking Up and Talking To Sensors', ), 12 | ('Reading from the SDI-12 Buffer', ), 13 | ('Data Line States', ), 14 | ('Using more than one SDI-12 Object', ), 15 | ('Interrupt Service Routine', ), 16 | ], 17 | ), 18 | ( 19 | "Examples", 20 | "examples_page", 21 | [ 22 | ('Getting Sensor Information', ), 23 | ('Address Change', ), 24 | ('Checking all Addresses', ), 25 | ('Logging Data', ), 26 | ('Parsing Data', ), 27 | ('Simple Data Request', ), 28 | ('Terminal Emulator 1', ), 29 | ('Slave Implementation',), 30 | ('Terminal Emulator 2', ), 31 | ('External Interrupts', ), 32 | ('Concurrent Measurements', ), 33 | ], 34 | ), 35 | ("Classes", "annotated", [],), 36 | # ("Files", "files", []), 37 | ( 38 | "Notes", 39 | "pages", 40 | [ 41 | ("The SDI-12 Specification", "specifications"), 42 | ("Overview of Pin Change Interrupts", "interrupts_page"), 43 | ("Stepping through the Rx ISR", "rx_page"), 44 | ], 45 | ), 46 | ] 47 | LINKS_NAVBAR2 = [ 48 | ] 49 | VERSION_LABELS = True 50 | CLASS_INDEX_EXPAND_LEVELS = 2 51 | 52 | STYLESHEETS = [ 53 | "css/m-EnviroDIY+documentation.compiled.css", 54 | ] 55 | -------------------------------------------------------------------------------- /examples/f_basic_data_request/f_basic_data_request.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file f_basic_data_request.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Ruben Kertesz or @rinnamon on twitter 7 | * @date 2/10/2016 8 | * 9 | * @brief Example F: Basic Data Request to a Single Sensor 10 | * 11 | * This is a very basic (stripped down) example where the user initiates a measurement 12 | * and receives the results to a terminal window without typing numerous commands into 13 | * the terminal. 14 | * 15 | * Edited by Ruben Kertesz for ISCO Nile 502 2/10/2016 16 | */ 17 | 18 | #include 19 | 20 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 21 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 22 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 23 | #define SENSOR_ADDRESS 1 24 | 25 | /** Define the SDI-12 bus */ 26 | SDI12 mySDI12(DATA_PIN); 27 | 28 | String sdiResponse = ""; 29 | String myCommand = ""; 30 | 31 | void setup() { 32 | Serial.begin(SERIAL_BAUD); 33 | while (!Serial) 34 | ; 35 | 36 | Serial.println("Opening SDI-12 bus..."); 37 | mySDI12.begin(); 38 | delay(500); // allow things to settle 39 | 40 | // Power the sensors; 41 | if (POWER_PIN > 0) { 42 | Serial.println("Powering up sensors..."); 43 | pinMode(POWER_PIN, OUTPUT); 44 | digitalWrite(POWER_PIN, HIGH); 45 | delay(200); 46 | } 47 | } 48 | 49 | void loop() { 50 | do { // wait for a response from the serial terminal to do anything 51 | delay(30); 52 | } while (!Serial.available()); 53 | char nogo = 54 | Serial.read(); // simply hit enter in the terminal window or press send and the 55 | // characters get discarded but now the rest of the loop continues 56 | 57 | // first command to take a measurement 58 | myCommand = String(SENSOR_ADDRESS) + "M!"; 59 | Serial.println(myCommand); // echo command to terminal 60 | 61 | mySDI12.sendCommand(myCommand); 62 | delay(30); // wait a while for a response 63 | 64 | while (mySDI12.available()) { // build response string 65 | char c = mySDI12.read(); 66 | if ((c != '\n') && (c != '\r')) { 67 | sdiResponse += c; 68 | delay(10); // 1 character ~ 7.5ms 69 | } 70 | } 71 | if (sdiResponse.length() > 1) 72 | Serial.println(sdiResponse); // write the response to the screen 73 | mySDI12.clearBuffer(); 74 | 75 | 76 | delay(1000); // delay between taking reading and requesting data 77 | sdiResponse = ""; // clear the response string 78 | 79 | 80 | // next command to request data from last measurement 81 | myCommand = String(SENSOR_ADDRESS) + "D0!"; 82 | Serial.println(myCommand); // echo command to terminal 83 | 84 | mySDI12.sendCommand(myCommand); 85 | delay(30); // wait a while for a response 86 | 87 | while (mySDI12.available()) { // build string from response 88 | char c = mySDI12.read(); 89 | if ((c != '\n') && (c != '\r')) { 90 | sdiResponse += c; 91 | delay(10); // 1 character ~ 7.5ms 92 | } 93 | } 94 | if (sdiResponse.length() > 1) 95 | Serial.println(sdiResponse); // write the response to the screen 96 | mySDI12.clearBuffer(); 97 | 98 | // now go back to top and wait until user hits enter on terminal window 99 | } 100 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: true 8 | AlignConsecutiveDeclarations: true 9 | AlignEscapedNewlines: Left 10 | AlignOperands: false 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: true 16 | AllowShortCaseLabelsOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: WithoutElse 20 | AllowShortLoopsOnASingleLine: true 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: Yes 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BreakBeforeBinaryOperators: None 28 | BreakBeforeBraces: Attach 29 | BreakBeforeInheritanceComma: false 30 | BreakInheritanceList: BeforeColon 31 | BreakBeforeTernaryOperators: true 32 | BreakConstructorInitializersBeforeComma: false 33 | BreakConstructorInitializers: BeforeColon 34 | BreakAfterJavaFieldAnnotations: false 35 | BreakStringLiterals: true 36 | ColumnLimit: 88 37 | CommentPragmas: "^ IWYU pragma:" 38 | CompactNamespaces: false 39 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 40 | ConstructorInitializerIndentWidth: 2 41 | ContinuationIndentWidth: 2 42 | Cpp11BracedListStyle: true 43 | DerivePointerAlignment: false 44 | DisableFormat: false 45 | ExperimentalAutoDetectBinPacking: false 46 | FixNamespaceComments: true 47 | ForEachMacros: 48 | - foreach 49 | - Q_FOREACH 50 | - BOOST_FOREACH 51 | IncludeBlocks: Preserve 52 | IncludeCategories: 53 | - Regex: "TinyGsmClient.h" 54 | Priority: -1 55 | - Regex: "VariableBase.h" 56 | Priority: -1 57 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 58 | Priority: 2 59 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 60 | Priority: 3 61 | - Regex: ".*" 62 | Priority: 1 63 | IncludeIsMainRegex: "([-_](test|unittest))?$" 64 | IndentCaseLabels: true 65 | IndentPPDirectives: None 66 | IndentWidth: 2 67 | IndentWrappedFunctionNames: false 68 | JavaScriptQuotes: Leave 69 | JavaScriptWrapImports: true 70 | KeepEmptyLinesAtTheStartOfBlocks: false 71 | MacroBlockBegin: "" 72 | MacroBlockEnd: "" 73 | MaxEmptyLinesToKeep: 2 74 | NamespaceIndentation: None 75 | # ObjCBinPackProtocolList: Auto 76 | ObjCBlockIndentWidth: 2 77 | ObjCSpaceAfterProperty: false 78 | ObjCSpaceBeforeProtocolList: true 79 | PenaltyBreakAssignment: 25 80 | PenaltyBreakBeforeFirstCallParameter: 19 81 | PenaltyBreakComment: 300 82 | PenaltyBreakFirstLessLess: 120 83 | PenaltyBreakString: 1000 84 | PenaltyBreakTemplateDeclaration: 10 85 | PenaltyExcessCharacter: 600 86 | PenaltyReturnTypeOnItsOwnLine: 50 87 | PointerAlignment: Left 88 | PointerBindsToType: true 89 | ReflowComments: true 90 | SortIncludes: false 91 | SortUsingDeclarations: true 92 | SpaceAfterCStyleCast: false 93 | SpaceAfterLogicalNot: false 94 | SpaceAfterTemplateKeyword: true 95 | SpaceBeforeAssignmentOperators: true 96 | SpaceBeforeCpp11BracedList: false 97 | SpaceBeforeCtorInitializerColon: true 98 | SpaceBeforeInheritanceColon: true 99 | SpaceBeforeParens: ControlStatements 100 | SpaceBeforeRangeBasedForLoopColon: true 101 | SpaceInEmptyParentheses: false 102 | SpacesBeforeTrailingComments: 2 103 | SpacesInCStyleCastParentheses: false 104 | SpacesInParentheses: false 105 | SpacesInSquareBrackets: false 106 | Standard: Cpp11 107 | TabWidth: 2 108 | UseTab: Never 109 | --- 110 | -------------------------------------------------------------------------------- /.github/workflows/build_examples.yaml: -------------------------------------------------------------------------------- 1 | name: Build Examples 2 | 3 | # Triggers the workflow on push or pull request events 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | if: "!contains(github.event.head_commit.message, 'ci skip')" 10 | 11 | strategy: 12 | matrix: 13 | example: 14 | [ 15 | examples/a_wild_card/, 16 | examples/b_address_change/, 17 | examples/c_check_all_addresses/, 18 | examples/d_simple_logger/, 19 | examples/e_continuous_measurement/, 20 | examples/f_basic_data_request/, 21 | examples/g_terminal_window/, 22 | examples/h_SDI-12_slave_implementation/, 23 | examples/i_SDI-12_interface/, 24 | examples/j_external_pcint_library/, 25 | examples/k_concurrent_logger/, 26 | ] 27 | 28 | steps: 29 | - uses: actions/checkout@v2 30 | 31 | - name: Set variables 32 | run: | 33 | if [[ -z "${GITHUB_HEAD_REF}" ]]; then 34 | echo "::debug::Push to commit ${GITHUB_SHA}" 35 | echo "LIBRARY_INSTALL_SOURCE=https://github.com/${GITHUB_REPOSITORY}.git#${GITHUB_SHA}" >> $GITHUB_ENV 36 | else 37 | echo "::debug::Pull Request from the ${GITHUB_HEAD_REF} branch" 38 | echo "LIBRARY_INSTALL_SOURCE=https://github.com/${GITHUB_REPOSITORY}.git#${GITHUB_HEAD_REF}" >> $GITHUB_ENV 39 | fi 40 | 41 | - name: Restore or Cache pip 42 | uses: actions/cache@v2.1.4 43 | with: 44 | path: ~/.cache/pip 45 | # if requirements.txt hasn't changed, then it will be a "cache hit" and pip will be restored 46 | # if requirements.txt HAS changed, it will be a "cache miss" and a new cache of pip will be created if the job completes successfully 47 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 48 | restore-keys: ${{ runner.os }}-pip- 49 | 50 | - name: Restore or Cache PlatformIO and Libraries 51 | uses: actions/cache@v2.1.4 52 | with: 53 | path: ~/.platformio 54 | # if nothing in the lock files has changed, then it will be a "cache hit" and pip will be restored 55 | # otherwise, it will be a "cache miss" and a new cache of libraries will be created if the job completes successfully 56 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 57 | 58 | - name: Set up Python 59 | uses: actions/setup-python@v2 60 | 61 | # This should be pulled from cache, if there's not a new version 62 | - name: Install PlatformIO 63 | run: | 64 | python -m pip install --upgrade pip 65 | pip install --upgrade platformio 66 | 67 | - name: Run PlatformIO 68 | if: matrix.example != 'examples/j_external_pcint_library/' 69 | env: 70 | PLATFORMIO_CI_SRC: ${{ matrix.example }} 71 | run: | 72 | echo "${{ env.LIBRARY_INSTALL_SOURCE }}" 73 | pio lib --global install ${{ env.LIBRARY_INSTALL_SOURCE }} 74 | pio lib --global install EnableInterrupt 75 | platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 --board=huzzah --board=featheresp32 76 | pio lib --global uninstall SDI-12 77 | 78 | - name: Run PlatformIO 79 | if: matrix.example == 'examples/j_external_pcint_library/' 80 | env: 81 | PLATFORMIO_CI_SRC: ${{ matrix.example }} 82 | PLATFORMIO_BUILD_FLAGS: -DSDI12_EXTERNAL_PCINT 83 | run: | 84 | echo "${{ env.LIBRARY_INSTALL_SOURCE }}" 85 | pio lib --global install ${{ env.LIBRARY_INSTALL_SOURCE }} 86 | pio lib --global install EnableInterrupt 87 | platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 88 | pio lib --global uninstall SDI-12 -------------------------------------------------------------------------------- /examples/c_check_all_addresses/c_check_all_addresses.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file c_check_all_addresses.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Kevin M.Smith 7 | * @date August 2013 8 | * 9 | * @brief Example C: Check all Addresses for Active Sensors and Print Status 10 | * 11 | * This is a simple demonstration of the SDI-12 library for Arduino. 12 | * 13 | * It discovers the address of all sensors active and attached to the board. 14 | * THIS CAN BE *REALLY* SLOW TO RUN!!! 15 | * 16 | * Each sensor should have a unique address already - if not, multiple sensors may 17 | * respond simultaenously to the same request and the output will not be readable 18 | * by the Arduino. 19 | * 20 | * To address a sensor, please see Example B: b_address_change.ino 21 | */ 22 | 23 | #include 24 | 25 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 26 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 27 | #define FirstPin 5 /*! change to lowest pin number on your board */ 28 | #define LastPin 24 /*! change to highest pin number on your board */ 29 | 30 | 31 | /** 32 | * @brief gets identification information from a sensor, and prints it to the serial 33 | * port expects 34 | * 35 | * @param sdi the SDI-12 instance 36 | * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z' 37 | */ 38 | void printInfo(SDI12 sdi, char i) { 39 | String command = ""; 40 | command += (char)i; 41 | command += "I!"; 42 | sdi.sendCommand(command); 43 | sdi.clearBuffer(); 44 | delay(30); 45 | 46 | Serial.print(" --"); 47 | Serial.print(i); 48 | Serial.print("-- "); 49 | 50 | while (sdi.available()) { 51 | Serial.write(sdi.read()); 52 | delay(10); // 1 character ~ 7.5ms 53 | } 54 | } 55 | 56 | 57 | // this checks for activity at a particular address 58 | // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' 59 | boolean checkActive(SDI12 sdi, char i) { 60 | String myCommand = ""; 61 | myCommand = ""; 62 | myCommand += (char)i; // sends basic 'acknowledge' command [address][!] 63 | myCommand += "!"; 64 | 65 | for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts 66 | sdi.sendCommand(myCommand); 67 | sdi.clearBuffer(); 68 | delay(30); 69 | if (sdi.available()) { // If we here anything, assume we have an active sensor 70 | return true; 71 | } 72 | } 73 | sdi.clearBuffer(); 74 | return false; 75 | } 76 | 77 | void scanAddressSpace(SDI12 sdi) { 78 | // scan address space 0-9 79 | for (char i = '0'; i <= '9'; i++) 80 | if (checkActive(sdi, i)) { printInfo(sdi, i); } 81 | // scan address space a-z 82 | for (char i = 'a'; i <= 'z'; i++) 83 | if (checkActive(sdi, i)) { printInfo(sdi, i); } 84 | // scan address space A-Z 85 | for (char i = 'A'; i <= 'Z'; i++) 86 | if (checkActive(sdi, i)) { printInfo(sdi, i); }; 87 | } 88 | 89 | void setup() { 90 | Serial.begin(SERIAL_BAUD); 91 | Serial.println("//\n// Start Search for SDI-12 Devices \n// -----------------------"); 92 | 93 | // Power the sensors; 94 | if (POWER_PIN > 0) { 95 | Serial.println("Powering up sensors..."); 96 | pinMode(POWER_PIN, OUTPUT); 97 | digitalWrite(POWER_PIN, HIGH); 98 | delay(200); 99 | } 100 | 101 | for (uint8_t pin = FirstPin; pin <= LastPin; pin++) { 102 | if (pin != POWER_PIN) { 103 | pinMode(pin, INPUT); 104 | SDI12 mySDI12(pin); 105 | mySDI12.begin(); 106 | Serial.print("Checking pin "); 107 | Serial.print(pin); 108 | Serial.println("..."); 109 | scanAddressSpace(mySDI12); 110 | mySDI12.end(); 111 | } 112 | } 113 | 114 | Serial.println("\n//\n// End Search for SDI-12 Devices \n// ---------------------"); 115 | 116 | // Cut power 117 | digitalWrite(POWER_PIN, LOW); 118 | } 119 | 120 | void loop() {} 121 | -------------------------------------------------------------------------------- /.github/workflows/build_documentation.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Publish Documentation 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - master 9 | # Also trigger on page_build, as well as release created events 10 | page_build: 11 | release: 12 | types: # This configuration does not affect the page_build event above 13 | - created 14 | 15 | env: 16 | DOXYGEN_VERSION: Release_1_9_1 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | if: "!contains(github.event.head_commit.message, 'ci skip')" 22 | 23 | steps: 24 | # check out the Arduino-SDI-12 repo 25 | - uses: actions/checkout@v2 26 | with: 27 | path: code_docs/Arduino-SDI-12 28 | 29 | - name: Restore or Cache pip 30 | uses: actions/cache@v2.1.4 31 | id: cache_pip 32 | with: 33 | path: ~/.cache/pip 34 | # if requirements.txt hasn't changed, then it will be a "cache hit" and pip will be restored 35 | # if requirements.txt HAS changed, it will be a "cache miss" and a new cache of pip will be created if the job completes successfully 36 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 37 | restore-keys: ${{ runner.os }}-pip- 38 | 39 | - name: Restore or Cache PlatformIO and Libraries 40 | uses: actions/cache@v2.1.4 41 | id: cache_pio 42 | with: 43 | path: ~/.platformio 44 | # if nothing in the lock files has changed, then it will be a "cache hit" 45 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 46 | 47 | - name: Set up Python 48 | uses: actions/setup-python@v2 49 | 50 | # This should be pulled from cache, if there's not a new version 51 | - name: Install PlatformIO 52 | run: | 53 | python -m pip install --upgrade pip 54 | pip install --upgrade platformio 55 | 56 | # Install *all* the dependencies! 57 | # We're including the dependencies just so the includes can follow in the doxygen pre-processor 58 | - name: Install the dependencies at global level 59 | run: | 60 | echo "::debug::Installing greygnome/EnableInterrupt" 61 | pio lib -g install greygnome/EnableInterrupt 62 | 63 | - name: Update Libraries from Cache 64 | run: pio lib -g update 65 | 66 | - name: Install GraphViz (dot) 67 | run: sudo apt-get -y install graphviz 68 | 69 | - name: Restore or Cache Doxygen 70 | id: cache_doxygen 71 | uses: actions/cache@v2.1.4 72 | with: 73 | path: doxygen-src 74 | key: ${{ runner.os }}-doxygen-${{ env.DOXYGEN_VERSION }} 75 | 76 | - name: Clone and build doxygen 77 | if: steps.cache_doxygen.outputs.cache-hit != 'true' 78 | env: 79 | TRAVIS_BUILD_DIR: ${{ github.workspace }} 80 | run: | 81 | cd ${{ github.workspace }}/code_docs/Arduino-SDI-12/ 82 | chmod +x continuous_integration/build-install-doxygen.sh 83 | sh continuous_integration/build-install-doxygen.sh 84 | 85 | # This should be pulled from cache, if there's not a new version 86 | - name: Install Pygments and other m.css requirements 87 | run: pip3 install jinja2 Pygments beautifulsoup4 88 | 89 | # check out my fork of m.css, for processing Doxygen output 90 | - name: Checkout m.css 91 | uses: actions/checkout@v2 92 | with: 93 | # Repository name with owner. For example, actions/checkout 94 | repository: SRGDamia1/m.css 95 | path: code_docs/m.css 96 | 97 | - name: Generate all the documentation 98 | env: 99 | TRAVIS_BUILD_DIR: ${{ github.workspace }} 100 | run: | 101 | cd ${{ github.workspace }}/code_docs/Arduino-SDI-12/ 102 | chmod +x continuous_integration/generate-documentation.sh 103 | sh continuous_integration/generate-documentation.sh 104 | 105 | - name: Deploy to github pages 106 | uses: peaceiris/actions-gh-pages@v3 107 | with: 108 | github_token: ${{ secrets.GITHUB_TOKEN }} 109 | publish_dir: ${{ github.workspace }}/code_docs/Arduino-SDI-12Doxygen/m.css 110 | -------------------------------------------------------------------------------- /examples/b_address_change/b_address_change.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file b_address_change.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Kevin M.Smith 7 | * @date August 2013 8 | * 9 | * @brief Example B: Changing the Address of your SDI-12 Sensor 10 | * 11 | * This is a simple demonstration of the SDI-12 library for arduino. 12 | * It discovers the address of the attached sensor and allows you to change it. 13 | */ 14 | 15 | #include 16 | 17 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 18 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 19 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 20 | 21 | /** Define the SDI-12 bus */ 22 | SDI12 mySDI12(DATA_PIN); 23 | 24 | String myCommand = ""; // empty to start 25 | char oldAddress = '!'; // invalid address as placeholder 26 | 27 | 28 | // this checks for activity at a particular address 29 | // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' 30 | boolean checkActive(byte i) { // this checks for activity at a particular address 31 | Serial.print("Checking address "); 32 | Serial.print((char)i); 33 | Serial.print("..."); 34 | myCommand = ""; 35 | myCommand += (char)i; // sends basic 'acknowledge' command [address][!] 36 | myCommand += "!"; 37 | 38 | for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts 39 | mySDI12.sendCommand(myCommand); 40 | delay(30); 41 | if (mySDI12.available()) { // If we here anything, assume we have an active sensor 42 | Serial.println("Occupied"); 43 | mySDI12.clearBuffer(); 44 | return true; 45 | } else { 46 | Serial.println("Vacant"); // otherwise it is vacant. 47 | mySDI12.clearBuffer(); 48 | } 49 | } 50 | return false; 51 | } 52 | 53 | 54 | void setup() { 55 | Serial.begin(SERIAL_BAUD); 56 | while (!Serial) 57 | ; 58 | 59 | Serial.println("Opening SDI-12 bus..."); 60 | mySDI12.begin(); 61 | delay(500); // allow things to settle 62 | 63 | // Power the sensors; 64 | if (POWER_PIN > 0) { 65 | Serial.println("Powering up sensors..."); 66 | pinMode(POWER_PIN, OUTPUT); 67 | digitalWrite(POWER_PIN, HIGH); 68 | delay(200); 69 | } 70 | } 71 | 72 | void loop() { 73 | boolean found = false; // have we identified the sensor yet? 74 | 75 | for (byte i = '0'; i <= '9'; i++) { // scan address space 0-9 76 | if (found) break; 77 | if (checkActive(i)) { 78 | found = true; 79 | oldAddress = i; 80 | } 81 | } 82 | 83 | for (byte i = 'a'; i <= 'z'; i++) { // scan address space a-z 84 | if (found) break; 85 | if (checkActive(i)) { 86 | found = true; 87 | oldAddress = i; 88 | } 89 | } 90 | 91 | for (byte i = 'A'; i <= 'Z'; i++) { // scan address space A-Z 92 | if (found) break; 93 | if (checkActive(i)) { 94 | found = true; 95 | oldAddress = i; 96 | } 97 | } 98 | 99 | if (!found) { 100 | Serial.println( 101 | "No sensor detected. Check physical connections."); // couldn't find a sensor. 102 | // check connections.. 103 | } else { 104 | Serial.print("Sensor active at address "); // found a sensor! 105 | Serial.print(oldAddress); 106 | Serial.println("."); 107 | 108 | Serial.println("Enter new address."); // prompt for a new address 109 | while (!Serial.available()) 110 | ; 111 | char newAdd = Serial.read(); 112 | 113 | // wait for valid response 114 | while (((newAdd < '0') || (newAdd > '9')) && ((newAdd < 'a') || (newAdd > 'z')) && 115 | ((newAdd < 'A') || (newAdd > 'Z'))) { 116 | if (!(newAdd == '\n') || (newAdd == '\r') || (newAdd == ' ')) { 117 | Serial.println( 118 | "Not a valid address. Please enter '0'-'9', 'a'-'A', or 'z'-'Z'."); 119 | } 120 | while (!Serial.available()) 121 | ; 122 | newAdd = Serial.read(); 123 | } 124 | 125 | /* the syntax of the change address command is: 126 | [currentAddress]A[newAddress]! */ 127 | 128 | Serial.println("Readdressing sensor."); 129 | myCommand = ""; 130 | myCommand += (char)oldAddress; 131 | myCommand += "A"; 132 | myCommand += (char)newAdd; 133 | myCommand += "!"; 134 | mySDI12.sendCommand(myCommand); 135 | 136 | /* wait for the response then throw it away by 137 | clearing the buffer with clearBuffer() */ 138 | delay(300); 139 | mySDI12.clearBuffer(); 140 | 141 | Serial.println("Success. Rescanning for verification."); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | **** 8 | 9 | ## v2.1.4 (2021-05-05) [Revert wake delay to 0ms](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.4) 10 | 11 | ### Possibly breaking changes 12 | - Reverted the default wake delay to 0ms. 13 | - In 92055d377b26fa862c43d1429de1ccbef054af01 this was bumped up to 10ms, which caused problems for several people. 14 | - The delay can now also be set using the build flag `-D SDI12_WAKE_DELAY=#` 15 | 16 | ## v2.1.3 (2021-03-24) [Migrate to GitHub Actions](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.3) 17 | 18 | ### Improvements 19 | - Migrate from Travis to GitHub actions 20 | 21 | ## v2.1.1 (2020-08-20) [Patches for ATTiny](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.1) 22 | 23 | ### Bug Fixes 24 | - fixes for the timer and pre-scaler for the ATTiny, courtesy of @gabbas1 25 | 26 | ## v2.1.0 (2020-07-10) [Library Rename and ESP support](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.0) 27 | 28 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3939731.svg)](https://doi.org/10.5281/zenodo.3939731) 29 | 30 | **To comply with requirements for inclusion in the Arduino IDE, the word Arduino has been removed from the name of this library!** The repository name is unchanged. 31 | 32 | ### New Features 33 | - Adds support for Espressif ESP8266 and ESP32 34 | - Add option of adding a delay before sending a command to allow the sensor to wake. Take advantage of this by calling the function ```sendCommand(command, extraWakeTime)```. This may resolve issues with some Campbell sensors that would not previous communicate with this library. See https://www.envirodiy.org/topic/campbell-scientific-cs-215-sdi-12-communication-issues-w-mayfly/#post-14103 35 | - Adds Doxygen (Javadoc) style comments to **ALL** members of the library. The generated documentation is available at https://envirodiy.github.io/Arduino-SDI-12/. 36 | 37 | ## v1.3.6 (2019-08-29) [Fixed extra compiler warnings](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.6) 38 | 39 | ### Bug Fixes 40 | - A very minor update to fix compiler warnings found when using -Wextra in addition to -Wall. 41 | 42 | ## v1.3.5 (2019-07-01) [Removed SAMD Tone Conflict](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.5) 43 | 44 | ### Improvements 45 | - SAMD boards will no longer have a conflict with the Tone functions in the Arduino core. AVR boards will still conflict. If you need to use Tone and SDI-12 together for some reason on an AVR boards, you must use the "delayBase" branch. 46 | - Examples were also updated and given platformio.ini files. 47 | 48 | ## v1.3.4 (2019-10-29) [Timer class](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.4) 49 | 50 | ### Improvements 51 | - Made the timer changes into a compiled class. 52 | 53 | Maintaining interrupt control for SAMD processors as there are no interrupt vectors to be in conflict. Because the pin mode changes from input to output and back, allowing another library to control interrupts doesn't work. 54 | 55 | ## v1.3.3 (2018-05-11) [Unset prescalers](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.3) 56 | 57 | ### Improvements 58 | - Now unsetting timer prescalers and setting the isActive pointer to NULL in both the end and the destructor functions. 59 | - Also some clean-up of the examples. 60 | 61 | ## v1.3.1 (2018-04-06) [Added processor timer for greater stability](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.1) 62 | 63 | ### New Features 64 | - Changed the incoming data ISR to use a processor timer, this makes the reception more stable, especially when the ISR is controlled by an external library. This also creates some conflicts with other libraries that use Timer2. 65 | 66 | ### Improvements 67 | - Made changes to the write functions to use the timer to reduce the amount of time that all system interrupts are off. 68 | - Forcing all SDI-12 objects to use the same buffer to reduce ram usage. 69 | 70 | ## v1.1.0 (2018-03-15) [Better integration inside other libraries](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.1.0) 71 | 72 | ### Improvements 73 | - Added notes and an empty constructor/populated begin method to allow this library to be more easily called inside of other libraries. 74 | 75 | ## v1.0.6 (2018-03-09) [Fixed timeout values](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.0.6) 76 | 77 | ### Bug Fixes 78 | - Fixes the time-out values for the ParseInt and ParseFloat to be -9999. This was the intended behavior all along, but at some point those functions changed in the stream library and the identically named functions within SDI-12 intended to "hide" the stream functions ceased to be called. 79 | 80 | ## v1.0.1 (2017-05-16) [Initial Release](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.0.1) 81 | 82 | The first "official" release of this interrupt-based SDI-12 library for AVR and SAMD Arduino boards. -------------------------------------------------------------------------------- /examples/ReadMe.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page examples_page Examples ) 2 | # Examples using the SDI-12 Library 3 | 4 | [//]: # ( @brief Examples using the SDI-12 Library ) 5 | 6 | [//]: # ( Start GitHub Only ) 7 | - [Example A](@ref a_wild_card.ino): 8 | - Gets sensor information from a single attached sensor and prints it to the serial port 9 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/a_wild_card) 10 | - [Example B](@ref b_address_change.ino): 11 | - Allows you to change the address of your SDI-12 Sensor 12 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/b_address_change) 13 | - [Example C](@ref c_check_all_addresses.ino): 14 | - Checks all addresses for active sensors, and prints their status to the serial port 15 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/c_check_all_addresses) 16 | - [Example D](@ref d_simple_logger.ino): 17 | - Checks all addresses for active sensors, and logs data for each sensor every minute 18 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/d_simple_logger) 19 | - [Example E](@ref e_simple_parsing.ino): 20 | - Demonstrates the ability to parse integers and floats from the buffer. 21 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/e_simple_parsing) 22 | - [Example F](@ref f_basic_data_request.ino): 23 | - Issues a data request to a single specified sensor 24 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/f_basic_data_request) 25 | - [Example G](@ref g_terminal_window.ino): 26 | - Demonstrates using the Arduino as a command terminal for SDI-12 sensors. 27 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/g_terminal_window) 28 | - [Example H](@ref h_SDI-12_slave_implementation.ino): 29 | - Demonstrates using SDI-12 in slave mode 30 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/h_SDI-12_slave_implementation) 31 | - [Example I](@ref i_SDI-12_interface.ino): 32 | - Shows code for an Arduino-based USB dongle to translate between SDI-12 and a PC 33 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/i_SDI-12_interface) 34 | - [Example J](@ref j_external_pcint_library.ino): 35 | - Shows how to use an external PCInt library to call the interrupts for this library. 36 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/j_external_pcint_library) 37 | - [Example K](@ref k_concurrent_logger.ino): 38 | - Shows how to request concurrent measurements 39 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/k_concurrent_logger) 40 | 41 | [//]: # ( End GitHub Only ) 42 | 43 | - [Example A](@ref a_wild_card.ino): 44 | - Gets sensor information from a single attached sensor and prints it to the serial port 45 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/a_wild_card) 46 | - [Example B](@ref b_address_change.ino): 47 | - Allows you to change the address of your SDI-12 Sensor 48 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/b_address_change) 49 | - [Example C](@ref c_check_all_addresses.ino): 50 | - Checks all addresses for active sensors, and prints their status to the serial port 51 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/c_check_all_addresses) 52 | - [Example D](@ref d_simple_logger.ino): 53 | - Checks all addresses for active sensors, and logs data for each sensor every minute 54 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/d_simple_logger) 55 | - [Example E](@ref e_simple_parsing.ino): 56 | - Demonstrates the ability to parse integers and floats from the buffer. 57 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/e_simple_parsing) 58 | - [Example F](@ref f_basic_data_request.ino): 59 | - Issues a data request to a single specified sensor 60 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/f_basic_data_request) 61 | - [Example G](@ref g_terminal_window.ino): 62 | - Demonstrates using the Arduino as a command terminal for SDI-12 sensors. 63 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/g_terminal_window) 64 | - [Example H](@ref h_SDI-12_slave_implementation.ino): 65 | - Demonstrates using SDI-12 in slave mode 66 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/h_SDI-12_slave_implementation) 67 | - [Example I](@ref i_SDI-12_interface.ino): 68 | - Shows code for an Arduino-based USB dongle to translate between SDI-12 and a PC 69 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/i_SDI-12_interface) 70 | - [Example J](@ref j_external_pcint_library.ino): 71 | - Shows how to use an external PCInt library to call the interrupts for this library. 72 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/j_external_pcint_library) 73 | - [Example K](@ref k_concurrent_logger.ino): 74 | - Shows how to request concurrent measurements 75 | - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/k_concurrent_logger) -------------------------------------------------------------------------------- /docs/documentExamples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import fileinput 3 | import re 4 | import os 5 | import glob 6 | 7 | fileDir = os.path.dirname(os.path.realpath("__file__")) 8 | # print(fileDir) 9 | 10 | output_file = "examples.dox" 11 | read_mes = [ 12 | "../examples/ReadMe.md", 13 | "../examples/a_wild_card/ReadMe.md", 14 | "../examples/b_address_change/ReadMe.md", 15 | "../examples/c_check_all_addresses/ReadMe.md", 16 | "../examples/d_simple_logger/ReadMe.md", 17 | "../examples/e_continuous_measurement/ReadMe.md", 18 | "../examples/f_basic_data_request/ReadMe.md", 19 | "../examples/g_terminal_window/ReadMe.md", 20 | "../examples/h_SDI-12_slave_implementation/ReadMe.md", 21 | "../examples/i_SDI-12_interface/ReadMe.md", 22 | "../examples/j_external_pcint_library/ReadMe.md", 23 | "../examples/k_concurrent_logger/ReadMe.md", 24 | ] 25 | 26 | if not os.path.exists(os.path.join(fileDir, "examples")): 27 | os.makedirs(os.path.join(fileDir, "examples")) 28 | 29 | for filename in read_mes: 30 | out_path = os.path.join(fileDir, "examples") 31 | out_dir = filename.split("/")[2] 32 | out_name = out_dir + ".dox" 33 | if out_name == "ReadMe.md.dox": 34 | out_name = "examples.dox" 35 | abs_out = os.path.join(out_path, out_name) 36 | # print(abs_out) 37 | # with open(output_file, 'w+') as out_file: 38 | with open(abs_out, "w+") as out_file: 39 | 40 | abs_file_path = os.path.join(fileDir, filename) 41 | abs_file_path = os.path.abspath(os.path.realpath(abs_file_path)) 42 | # print(abs_file_path) 43 | 44 | with open(abs_file_path, "r") as in_file: # open in readonly mode 45 | out_file.write("/**\n") 46 | if out_name != "examples.dox": 47 | # out_file.write( 48 | # "@example{{lineno}} {} @m_examplenavigation{{examples_page,{}/}} @m_footernavigation \n\n".format( 49 | # filename.replace("..\\examples\\", "").replace( 50 | # "\\ReadMe.md", ".ino" 51 | # ), out_dir 52 | # ) 53 | # ) 54 | out_file.write( 55 | "@example{{lineno}} {} @m_examplenavigation{{examples_page,}} @m_footernavigation \n\n".format( 56 | filename.replace("../examples/", "").replace( 57 | "/ReadMe.md", ".ino" 58 | ) 59 | ) 60 | ) 61 | # out_file.write( 62 | # "@example{{lineno}} {} \n\n".format( 63 | # filename.replace("..\\examples\\", "").replace( 64 | # "\\ReadMe.md", ".ino" 65 | # ) 66 | # ) 67 | # ) 68 | # out_file.write('\n@tableofcontents\n\n') 69 | 70 | print_me = True 71 | skip_me = False 72 | i = 1 73 | lines = in_file.readlines() 74 | for line in lines: 75 | # print(i, print_me, skip_me, line) 76 | 77 | # Remove markdown comment tags from doxygen commands within the markdown 78 | if print_me and not skip_me: 79 | new_line = ( 80 | re.sub(r"\[//\]: # \( @(\w+?.*) \)", r"@\1", line) 81 | .replace("```ini", "@code{.ini}") 82 | .replace("```cpp", "@code{.cpp}") 83 | .replace("```", "@endcode") 84 | ) 85 | if out_name != "examples.dox": 86 | new_line = new_line.replace("@page", "@section") 87 | # .replace('@section', '') 88 | # .replace('@subsection', '') 89 | # .replace('@subsubsection', '') 90 | # .replace('@paragraph', '') 91 | # .replace('@par', '') 92 | out_file.write(new_line) 93 | 94 | # using skip_me to skip single lines, so unset it after reading a line 95 | if skip_me: 96 | skip_me = False 97 | 98 | # a page, section, subsection, or subsubsection commands followed 99 | # immediately with by a markdown header leads to that section appearing 100 | # twice in the doxygen html table of contents. 101 | # I'm putting the section markers right above the header and then will skip the header. 102 | if re.match(r"\[//\]: # \( @mainpage", line) is not None: 103 | skip_me = True 104 | if re.match(r"\[//\]: # \( @page", line) is not None: 105 | skip_me = True 106 | if re.match(r"\[//\]: # \( @.*section", line) is not None: 107 | skip_me = True 108 | if re.match(r"\[//\]: # \( @paragraph", line) is not None: 109 | skip_me = True 110 | 111 | # I'm using these comments to fence off content that is only intended for 112 | # github mardown rendering 113 | if "[//]: # ( Start GitHub Only )" in line: 114 | print_me = False 115 | 116 | if "[//]: # ( End GitHub Only )" in line: 117 | print_me = True 118 | 119 | i += 1 120 | 121 | out_file.write("\n*/\n\n") 122 | -------------------------------------------------------------------------------- /tools/TestWarmUp/TestWarmUp.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file d_simple_logger.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Sara Damiano 7 | * @date March 2021 8 | */ 9 | 10 | #include 11 | 12 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 13 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 14 | #define SENSOR_ADDRESS '0' /*!< The address of the SDI-12 sensor */ 15 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 16 | 17 | /** Define the SDI-12 bus */ 18 | SDI12 mySDI12(DATA_PIN); 19 | int32_t wake_delay = 0; /*!< The time for the board to wake after a line break. */ 20 | int32_t increment_wake = 10; /*!< The time to lengthen waits between reps. */ 21 | int32_t power_delay = 5400; /*!< The time for the board to wake after power on. */ 22 | int32_t increment_power = 50; /*!< The time to lengthen waits between reps. */ 23 | int32_t max_power_delay = 10000L; /*!< The max time to test wake after power on. */ 24 | 25 | /** 26 | * @brief gets identification information from a sensor, and prints it to the serial 27 | * port 28 | * 29 | * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. 30 | */ 31 | bool printInfo(char i, bool printCommands = true) { 32 | String command = ""; 33 | command += (char)i; 34 | command += "I!"; 35 | mySDI12.sendCommand(command, wake_delay); 36 | if (printCommands) { 37 | Serial.print(">>>"); 38 | Serial.println(command); 39 | } 40 | delay(100); 41 | 42 | String sdiResponse = mySDI12.readStringUntil('\n'); 43 | sdiResponse.trim(); 44 | // allccccccccmmmmmmvvvxxx...xx 45 | if (printCommands) { 46 | Serial.print("<<<"); 47 | Serial.println(sdiResponse); 48 | } 49 | 50 | Serial.print("Address: "); 51 | Serial.print(sdiResponse.substring(0, 1)); // address 52 | Serial.print(", SDI-12 Version: "); 53 | Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number 54 | Serial.print(", Vendor ID: "); 55 | Serial.print(sdiResponse.substring(3, 11)); // vendor id 56 | Serial.print(", Sensor Model: "); 57 | Serial.print(sdiResponse.substring(11, 17)); // sensor model 58 | Serial.print(", Sensor Version: "); 59 | Serial.print(sdiResponse.substring(17, 20)); // sensor version 60 | Serial.print(", Sensor ID: "); 61 | Serial.print(sdiResponse.substring(20)); // sensor id 62 | Serial.println(); 63 | 64 | if (sdiResponse.length() < 3) { return false; }; 65 | return true; 66 | } 67 | 68 | // this checks for activity at a particular address 69 | // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' 70 | boolean checkActive(char i, int8_t numPings = 3, bool printCommands = false) { 71 | String command = ""; 72 | command += (char)i; // sends basic 'acknowledge' command [address][!] 73 | command += "!"; 74 | 75 | for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts 76 | if (printCommands) { 77 | Serial.print(">>>"); 78 | Serial.println(command); 79 | } 80 | mySDI12.sendCommand(command, wake_delay); 81 | delay(100); 82 | if (mySDI12.available()) { // If we here anything, assume we have an active sensor 83 | if (printCommands) { 84 | Serial.print("<<<"); 85 | while (mySDI12.available()) { 86 | Serial.write(mySDI12.read()); 87 | delay(10); 88 | } 89 | } else { 90 | mySDI12.clearBuffer(); 91 | } 92 | return true; 93 | } 94 | } 95 | mySDI12.clearBuffer(); 96 | return false; 97 | } 98 | 99 | 100 | void setup() { 101 | Serial.begin(SERIAL_BAUD); 102 | while (!Serial) 103 | ; 104 | 105 | Serial.println("Opening SDI-12 bus..."); 106 | mySDI12.begin(); 107 | delay(500); // allow things to settle 108 | 109 | Serial.println("Timeout value: "); 110 | Serial.println(mySDI12.TIMEOUT); 111 | } 112 | 113 | void loop() { 114 | // Power the sensors; 115 | if (POWER_PIN > 0) { 116 | Serial.println("Powering down sensors..."); 117 | pinMode(POWER_PIN, OUTPUT); 118 | digitalWrite(POWER_PIN, LOW); 119 | delay(2500L); 120 | } 121 | 122 | // Power the sensors; 123 | if (POWER_PIN > 0) { 124 | Serial.println("Powering up sensors..."); 125 | pinMode(POWER_PIN, OUTPUT); 126 | digitalWrite(POWER_PIN, HIGH); 127 | delay(power_delay); 128 | } 129 | 130 | if (checkActive(SENSOR_ADDRESS, 5, true)) { 131 | Serial.print("Got response after "); 132 | Serial.print(power_delay); 133 | Serial.print("ms after power with "); 134 | Serial.print(wake_delay); 135 | Serial.println("ms with wake delay"); 136 | if (printInfo(SENSOR_ADDRESS, true)) { 137 | // if we got sensor info, stop 138 | while (1) 139 | ; 140 | } 141 | } else { 142 | Serial.print("No response after "); 143 | Serial.print(power_delay); 144 | Serial.print("ms after power with "); 145 | Serial.print(wake_delay); 146 | Serial.println("ms with wake delay"); 147 | } 148 | Serial.println("-------------------------------------------------------------------" 149 | "------------"); 150 | if (power_delay > max_power_delay) { 151 | power_delay = 0; 152 | wake_delay = wake_delay + increment_wake; 153 | } else { 154 | power_delay = power_delay + increment_power; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /continuous_integration/.travis.yml_archive: -------------------------------------------------------------------------------- 1 | before_install: 2 | - git config --global user.email "sdamiano@stroudcenter.org" 3 | - git config --global user.name "SRGDamia1" 4 | 5 | sudo: false 6 | git: 7 | depth: 1 8 | branches: 9 | except: 10 | - gh-pages 11 | 12 | cache: 13 | pip: true 14 | directories: 15 | - "~/.platformio" 16 | - $TRAVIS_BUILD_DIR/doxygen-src 17 | 18 | language: python 19 | python: 20 | - "2.7" 21 | 22 | install: 23 | # Remove the cloned repo to emulate a user library installation 24 | - git rm library.json 25 | # - git rm library.properties 26 | # - git rm -r pioScripts 27 | # - git rm -r src 28 | # Install PlatformIO (this should be cached!) 29 | - pip install -U platformio 30 | - pio upgrade 31 | # Uninstall any old version of the current library from the Travis cache 32 | - if pio lib --global uninstall EnviroDIY_Arduino-SDI-12; then 33 | echo "Uninstalled cached version of Arduino-SDI-12"; 34 | fi 35 | - if pio lib --global uninstall EnviroDIY_SDI-12; then 36 | echo "Uninstalled cached version of SDI-12"; 37 | fi 38 | # Install this library from the branch we're working on 39 | # echo "Installing SDI-12 from https://github.com/$TRAVIS_REPO_SLUG.git#$TRAVIS_BRANCH"; 40 | # echo "Installing SDI-12 from https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_BRANCH"; 41 | - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 42 | echo "Installing SDI-12 from https://github.com/$TRAVIS_REPO_SLUG.git#$TRAVIS_COMMIT"; 43 | else 44 | echo "Installing SDI-12 from https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_SHA"; 45 | fi 46 | # pio lib --global install https://github.com/$TRAVIS_REPO_SLUG.git#$BRANCH; 47 | # pio lib --global install https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_BRANCH; 48 | - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 49 | pio lib --global install https://github.com/$TRAVIS_REPO_SLUG.git#$TRAVIS_COMMIT; 50 | else 51 | pio lib --global install https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_SHA; 52 | fi 53 | - pio update 54 | 55 | script: 56 | - platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 --board=huzzah --board=featheresp32 57 | 58 | jobs: 59 | include: 60 | - name: "Verify library JSON format" 61 | language: node_js 62 | install: npm install -g jsonlint 63 | script: jsonlint -q library.json 64 | after_success: | 65 | echo "TRAVIS_BRANCH=$TRAVIS_BRANCH TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST" 66 | if [[ ($TRAVIS_BRANCH == master) && 67 | ($TRAVIS_PULL_REQUEST == false) ]] ; then 68 | curl -LO --retry 3 https://raw.github.com/mernst/plume-lib/master/bin/trigger-travis.sh 69 | sh trigger-travis.sh EnviroDIY Libraries $TRAVIS_ACCESS_TOKEN 70 | fi 71 | 72 | - name: "Build Doxygen Documentation" 73 | if: branch = master AND type != pull_request 74 | language: python 75 | python: 76 | - "3.7" 77 | before_install: 78 | - git config --global user.email "sdamiano@stroudcenter.org" 79 | - git config --global user.name "SRGDamia1" 80 | - git config --global push.default simple 81 | - sudo apt-get update 82 | - sudo apt-get -y install build-essential 83 | - sudo apt-get -y install graphviz 84 | - sudo apt-get -y install flex 85 | - sudo apt-get -y install bison 86 | - sudo apt-get -y install texlive-base 87 | - sudo apt-get -y install texlive-latex-extra 88 | - sudo apt-get -y install texlive-fonts-extra 89 | - sudo apt-get -y install texlive-fonts-recommended 90 | - pip3 install jinja2 Pygments 91 | install: 92 | - cd $TRAVIS_BUILD_DIR 93 | - chmod +x travis/copy-doc-sources.sh 94 | - sh travis/copy-doc-sources.sh 95 | - cd $TRAVIS_BUILD_DIR 96 | - chmod +x travis/build-install-doxygen.sh 97 | - sh travis/build-install-doxygen.sh 98 | script: 99 | - cd $TRAVIS_BUILD_DIR 100 | - chmod +x travis/generate-documentation.sh 101 | - sh travis/generate-documentation.sh 102 | # after_success: 103 | # - cd $TRAVIS_BUILD_DIR 104 | # - chmod +x travis/deploy-documentation.sh 105 | # - sh travis/deploy-documentation.sh 106 | deploy: 107 | provider: pages:git 108 | token: $GH_REPO_TOKEN 109 | edge: true # opt in to dpl v2 110 | keep_history: false 111 | local_dir: $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12Doxygen/m.css 112 | project_name: Arduino-SDI-12 113 | 114 | - name: "a_wild_card" 115 | env: 116 | - PLATFORMIO_CI_SRC=examples/a_wild_card/a_wild_card.ino 117 | 118 | - name: "b_address_change" 119 | env: 120 | - PLATFORMIO_CI_SRC=examples/b_address_change/b_address_change.ino 121 | 122 | - name: "c_check_all_addresses" 123 | env: 124 | - PLATFORMIO_CI_SRC=examples/c_check_all_addresses/c_check_all_addresses.ino 125 | 126 | - name: "d_simple_logger" 127 | env: 128 | - PLATFORMIO_CI_SRC=examples/d_simple_logger/d_simple_logger.ino 129 | 130 | - name: "e_simple_parsing" 131 | env: 132 | - PLATFORMIO_CI_SRC=examples/e_simple_parsing/e_simple_parsing.ino 133 | 134 | - name: "f_basic_data_request" 135 | env: 136 | - PLATFORMIO_CI_SRC=examples/f_basic_data_request/f_basic_data_request.ino 137 | 138 | - name: "g_terminal_window" 139 | env: 140 | - PLATFORMIO_CI_SRC=examples/g_terminal_window/g_terminal_window.ino 141 | 142 | - name: "h_SDI-12_slave_implementation" 143 | env: 144 | - PLATFORMIO_CI_SRC=examples/h_SDI-12_slave_implementation/h_SDI-12_slave_implementation.ino 145 | 146 | - name: "i_SDI-12_interface" 147 | env: 148 | - PLATFORMIO_CI_SRC=examples/i_SDI-12_interface/i_SDI-12_interface.ino 149 | 150 | - name: "j_external_pcint_library" 151 | env: 152 | - PLATFORMIO_CI_SRC=examples/j_external_pcint_library/j_external_pcint_library.ino 153 | - PLATFORMIO_BUILD_FLAGS=-DSDI12_EXTERNAL_PCINT 154 | script: 155 | - pio lib --global install EnableInterrupt 156 | - platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 157 | 158 | - name: "k_concurrent_logger" 159 | env: 160 | - PLATFORMIO_CI_SRC=examples/k_concurrent_logger/k_concurrent_logger.ino 161 | -------------------------------------------------------------------------------- /examples/i_SDI-12_interface/i_SDI-12_interface.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file h_SDI-12_slave_implementation.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @date 2016 7 | * @author D. Wasielewski 8 | * 9 | * @brief Example I: SDI-12 PC Interface 10 | * 11 | * Arduino-based USB dongle translates serial comm from PC to SDI-12 (electrical and 12 | * timing) 13 | * 1. Allows user to communicate to SDI-12 devices from a serial terminal emulator 14 | * (e.g. PuTTY). 15 | * 2. Able to spy on an SDI-12 bus for troubleshooting comm between datalogger and 16 | * sensors. 17 | * 3. Can also be used as a hardware middleman for interfacing software to an SDI-12 18 | * sensor. For example, implementing an SDI-12 datalogger in Python on a PC. Use 19 | * verbatim mode with feedback off in this case. 20 | * 21 | * Note: "translation" means timing and electrical interface. It does not ensure 22 | * SDI-12 compliance of commands sent via it. 23 | * 24 | * D. Wasielewski, 2016 25 | * Builds upon work started by: 26 | * https://github.com/jrzondagh/AgriApps-SDI-12-Arduino-Sensor 27 | * https://github.com/Jorge-Mendes/Agro-Shield/tree/master/SDI-12ArduinoSensor 28 | * 29 | * Known issues: 30 | * - Backspace adds a "backspace character" into the serialMsgStr (which gets sent 31 | * out on the SDI-12 interface) instead of removing the previous char from it 32 | * - Suceptible to noise on the SDI-12 data line; consider hardware filtering or 33 | * software error-checking 34 | */ 35 | 36 | #define HELPTEXT \ 37 | "OPTIONS:\r\n" \ 38 | "help : Print this message\r\n" \ 39 | "mode s : SDI-12 command mode (uppercase and ! automatically corrected) " \ 40 | "[default]\r\n" \ 41 | "mode v : verbatim mode (text will be sent verbatim)\r\n" \ 42 | "fb on : Enable feedback (characters visible while typing) [default]\r\n" \ 43 | "fb off : Disable feedback (characters not visible while typing; may be desired " \ 44 | "for developers)\r\n" \ 45 | "(else) : send command to SDI-12 bus" 46 | 47 | #include 48 | 49 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 50 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 51 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 52 | #define SENSOR_ADDRESS 1 53 | 54 | /** Define the SDI-12 bus */ 55 | SDI12 mySDI12(DATA_PIN); 56 | 57 | void setup() { 58 | Serial.begin(SERIAL_BAUD); 59 | while (!Serial) 60 | ; 61 | 62 | // Power the sensors; 63 | if (POWER_PIN > 0) { 64 | Serial.println("Powering up sensors..."); 65 | pinMode(POWER_PIN, OUTPUT); 66 | digitalWrite(POWER_PIN, HIGH); 67 | delay(200); 68 | } 69 | 70 | // Initiate serial connection to SDI-12 bus 71 | mySDI12.begin(); 72 | delay(500); 73 | mySDI12.forceListen(); 74 | 75 | // Print help text (may wish to comment out if used for communicating to software) 76 | Serial.println(HELPTEXT); 77 | } 78 | 79 | void loop() { 80 | static String serialMsgStr; 81 | static boolean serialMsgReady = false; 82 | 83 | static String sdiMsgStr; 84 | static boolean sdiMsgReady = false; 85 | 86 | static boolean verbatim = false; 87 | static boolean feedback = true; 88 | 89 | 90 | // -- READ SERIAL (PC COMMS) DATA -- 91 | // If serial data is available, read in a single byte and add it to 92 | // a String on each iteration 93 | if (Serial.available()) { 94 | char inByte1 = Serial.read(); 95 | if (feedback) { Serial.print(inByte1); } 96 | if (inByte1 == '\r' || inByte1 == '\n') { 97 | serialMsgReady = true; 98 | } else { 99 | serialMsgStr += inByte1; 100 | } 101 | } 102 | 103 | // -- READ SDI-12 DATA -- 104 | // If SDI-12 data is available, keep reading until full message consumed 105 | // (Normally I would prefer to allow the loop() to keep executing while the string 106 | // is being read in--as the serial example above--but SDI-12 depends on very precise 107 | // timing, so it is probably best to let it hold up loop() until the string is 108 | // complete) 109 | int avail = mySDI12.available(); 110 | if (avail < 0) { 111 | mySDI12.clearBuffer(); 112 | } // Buffer is full; clear 113 | else if (avail > 0) { 114 | for (int a = 0; a < avail; a++) { 115 | char inByte2 = mySDI12.read(); 116 | Serial.println(inByte2); 117 | if (inByte2 == '\n') { 118 | sdiMsgReady = true; 119 | } else if (inByte2 == '!') { 120 | sdiMsgStr += "!"; 121 | sdiMsgReady = true; 122 | } else { 123 | sdiMsgStr += String(inByte2); 124 | } 125 | } 126 | } 127 | 128 | 129 | // Report completed SDI-12 messages back to serial interface 130 | if (sdiMsgReady) { 131 | Serial.println(sdiMsgStr); 132 | // Reset String for next SDI-12 message 133 | sdiMsgReady = false; 134 | sdiMsgStr = ""; 135 | } 136 | 137 | // Send completed Serial message as SDI-12 command 138 | if (serialMsgReady) { 139 | Serial.println(); 140 | // Check if the serial message is a known command to the SDI-12 interface program 141 | String lowerMsgStr = serialMsgStr; 142 | lowerMsgStr.toLowerCase(); 143 | if (lowerMsgStr == "mode v") { 144 | verbatim = true; 145 | Serial.println("Verbatim mode; exact text will be sent. Enter \"mode s\" for " 146 | "SDI-12 command mode."); 147 | } else if (lowerMsgStr == "mode s") { 148 | verbatim = false; 149 | Serial.println("SDI-12 command mode; uppercase and ! suffix optional. Enter " 150 | "\"mode v\" for verbatim mode."); 151 | } else if (lowerMsgStr == "help") { 152 | Serial.println(HELPTEXT); 153 | } else if (lowerMsgStr == "fb off") { 154 | feedback = false; 155 | Serial.println("Feedback off; typed commands will not be visible. Enter \"fb " 156 | "on\" to enable feedback."); 157 | } else if (lowerMsgStr == "fb on") { 158 | feedback = true; 159 | Serial.println("Feedback on; typed commands will be visible. Enter \"fb off\" " 160 | "to disable feedback."); 161 | } 162 | // If not a known command to the SDI-12 interface program, send out on SDI-12 data 163 | // pin 164 | else { 165 | if (verbatim) { 166 | mySDI12.sendCommand(serialMsgStr); 167 | } else { 168 | serialMsgStr.toUpperCase(); 169 | mySDI12.sendCommand(serialMsgStr + "!"); 170 | } 171 | } 172 | // Reset String for next serial message 173 | serialMsgReady = false; 174 | serialMsgStr = ""; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /examples/e_continuous_measurement/e_continuous_measurement.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file d_simple_logger.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Kevin M.Smith 7 | * @date August 2013 8 | * 9 | * @brief Example D: Check all Addresses for Active Sensors and Log Data 10 | * 11 | * This is a simple demonstration of the SDI-12 library for Arduino. 12 | * 13 | * It discovers the address of all sensors active on a single bus and takes continuous 14 | * measurements from them. 15 | */ 16 | 17 | #include 18 | 19 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 20 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 21 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 22 | 23 | /** Define the SDI-12 bus */ 24 | SDI12 mySDI12(DATA_PIN); 25 | 26 | // keeps track of active addresses 27 | bool isActive[64] = { 28 | 0, 29 | }; 30 | 31 | uint8_t numSensors = 0; 32 | 33 | 34 | /** 35 | * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a 36 | * decimal number between 0 and 61 (inclusive) to cover the 62 possible 37 | * addresses. 38 | */ 39 | byte charToDec(char i) { 40 | if ((i >= '0') && (i <= '9')) return i - '0'; 41 | if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10; 42 | if ((i >= 'A') && (i <= 'Z')) 43 | return i - 'A' + 36; 44 | else 45 | return i; 46 | } 47 | 48 | /** 49 | * @brief maps a decimal number between 0 and 61 (inclusive) to allowable 50 | * address characters '0'-'9', 'a'-'z', 'A'-'Z', 51 | * 52 | * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL. 53 | */ 54 | char decToChar(byte i) { 55 | if (i < 10) return i + '0'; 56 | if ((i >= 10) && (i < 36)) return i + 'a' - 10; 57 | if ((i >= 36) && (i <= 62)) 58 | return i + 'A' - 36; 59 | else 60 | return i; 61 | } 62 | 63 | /** 64 | * @brief gets identification information from a sensor, and prints it to the serial 65 | * port 66 | * 67 | * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. 68 | */ 69 | void printInfo(char i) { 70 | String command = ""; 71 | command += (char)i; 72 | command += "I!"; 73 | mySDI12.sendCommand(command); 74 | delay(100); 75 | 76 | String sdiResponse = mySDI12.readStringUntil('\n'); 77 | sdiResponse.trim(); 78 | // allccccccccmmmmmmvvvxxx...xx 79 | Serial.print(sdiResponse.substring(0, 1)); // address 80 | Serial.print(", "); 81 | Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number 82 | Serial.print(", "); 83 | Serial.print(sdiResponse.substring(3, 11)); // vendor id 84 | Serial.print(", "); 85 | Serial.print(sdiResponse.substring(11, 17)); // sensor model 86 | Serial.print(", "); 87 | Serial.print(sdiResponse.substring(17, 20)); // sensor version 88 | Serial.print(", "); 89 | Serial.print(sdiResponse.substring(20)); // sensor id 90 | Serial.print(", "); 91 | } 92 | 93 | bool getContinuousResults(char i, int resultsExpected) { 94 | uint8_t resultsReceived = 0; 95 | uint8_t cmd_number = 0; 96 | while (resultsReceived < resultsExpected && cmd_number <= 9) { 97 | String command = ""; 98 | // in this example we will only take the 'DO' measurement 99 | command = ""; 100 | command += i; 101 | command += "R"; 102 | command += cmd_number; 103 | command += "!"; // SDI-12 command to get data [address][D][dataOption][!] 104 | mySDI12.sendCommand(command); 105 | 106 | uint32_t start = millis(); 107 | while (mySDI12.available() < 3 && (millis() - start) < 1500) {} 108 | mySDI12.read(); // ignore the repeated SDI12 address 109 | char c = mySDI12.peek(); // check if there's a '+' and toss if so 110 | if (c == '+') { mySDI12.read(); } 111 | 112 | while (mySDI12.available()) { 113 | char c = mySDI12.peek(); 114 | if (c == '-' || (c >= '0' && c <= '9') || c == '.') { 115 | float result = mySDI12.parseFloat(SKIP_NONE); 116 | Serial.print(String(result, 10)); 117 | if (result != -9999) { resultsReceived++; } 118 | } else if (c == '+') { 119 | mySDI12.read(); 120 | Serial.print(", "); 121 | } else { 122 | mySDI12.read(); 123 | } 124 | delay(10); // 1 character ~ 7.5ms 125 | } 126 | if (resultsReceived < resultsExpected) { Serial.print(", "); } 127 | cmd_number++; 128 | } 129 | mySDI12.clearBuffer(); 130 | 131 | return resultsReceived == resultsExpected; 132 | } 133 | 134 | // this checks for activity at a particular address 135 | // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' 136 | boolean checkActive(char i) { 137 | String myCommand = ""; 138 | myCommand = ""; 139 | myCommand += (char)i; // sends basic 'acknowledge' command [address][!] 140 | myCommand += "!"; 141 | 142 | for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts 143 | mySDI12.sendCommand(myCommand); 144 | delay(100); 145 | if (mySDI12.available()) { // If we here anything, assume we have an active sensor 146 | mySDI12.clearBuffer(); 147 | return true; 148 | } 149 | } 150 | mySDI12.clearBuffer(); 151 | return false; 152 | } 153 | 154 | 155 | void setup() { 156 | Serial.begin(SERIAL_BAUD); 157 | while (!Serial) 158 | ; 159 | 160 | Serial.println("Opening SDI-12 bus..."); 161 | mySDI12.begin(); 162 | delay(500); // allow things to settle 163 | 164 | Serial.println("Timeout value: "); 165 | Serial.println(mySDI12.TIMEOUT); 166 | 167 | // Power the sensors; 168 | if (POWER_PIN > 0) { 169 | Serial.println("Powering up sensors..."); 170 | pinMode(POWER_PIN, OUTPUT); 171 | digitalWrite(POWER_PIN, HIGH); 172 | delay(200); 173 | } 174 | 175 | // Quickly Scan the Address Space 176 | Serial.println("Scanning all addresses, please wait..."); 177 | Serial.println("Sensor Address, Protocol Version, Sensor Vendor, Sensor Model, " 178 | "Sensor Version, Sensor ID"); 179 | 180 | for (byte i = 0; i < 62; i++) { 181 | char addr = decToChar(i); 182 | if (checkActive(addr)) { 183 | numSensors++; 184 | isActive[i] = 1; 185 | printInfo(addr); 186 | Serial.println(); 187 | } 188 | } 189 | Serial.print("Total number of sensors found: "); 190 | Serial.println(numSensors); 191 | 192 | if (numSensors == 0) { 193 | Serial.println( 194 | "No sensors found, please check connections and restart the Arduino."); 195 | while (true) { delay(10); } // do nothing forever 196 | } 197 | 198 | Serial.println(); 199 | Serial.println( 200 | "Time Elapsed (s), Sensor Address, Est Measurement Time (s), Number Measurements, " 201 | "Real Measurement Time (ms), Measurement 1, Measurement 2, ... etc."); 202 | Serial.println( 203 | "-------------------------------------------------------------------------------"); 204 | } 205 | 206 | void loop() { 207 | // measure one at a time 208 | for (byte i = 0; i < 62; i++) { 209 | char addr = decToChar(i); 210 | if (isActive[i]) { 211 | // Serial.print(millis() / 1000); 212 | Serial.print(millis()); 213 | Serial.print(", "); 214 | getContinuousResults(addr, 4); 215 | Serial.println(); 216 | } 217 | } 218 | 219 | delay(5000L); // wait ten seconds between measurement attempts. 220 | } 221 | -------------------------------------------------------------------------------- /docs/OverviewOfInterrupts.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page interrupts_page Overview of Interrupts ) 2 | # Overview of Interrupts 3 | 4 | [//]: # ( @tableofcontents ) 5 | 6 | [//]: # ( Start GitHub Only ) 7 | - [Overview of Interrupts](#overview-of-interrupts) 8 | - [What is an Interrupt?](#what-is-an-interrupt) 9 | - [Directly Controlling Interrupts on an AVR Board](#directly-controlling-interrupts-on-an-avr-board) 10 | - [Some Vocabulary:](#some-vocabulary) 11 | - [Enabling an Interrupt](#enabling-an-interrupt) 12 | - [Disabling an Interrupt](#disabling-an-interrupt) 13 | 14 | [//]: # ( End GitHub Only ) 15 | 16 | [//]: # ( @section interrupts_what What is an Interrupt? ) 17 | ## What is an Interrupt? 18 | An interrupt is a signal that causes the microcontroller to halt execution of the program, and perform a subroutine known as an interrupt handler or Interrupt Service Routine (ISR). 19 | After the ISR, program execution continues where it left off. 20 | This allows the microcontroller to efficiently handle a time-sensitive function such as receiving a burst of data on one of its pins, by not forcing the microcontroller to wait for the data. 21 | It can perform other tasks until the interrupt is called. 22 | 23 | All processors running some sort of Arduino core can support multiple type of interrupts. 24 | This library specifically makes us of pin change interrupts - interrupts that trigger an ISR on any change of state detected for a specified pin. 25 | 26 | Obviously, we don't want the processor to be halting operation every time any pin changes voltage, so we can disable or enable interrupts on a pin-by-pin basis. 27 | For Atmel SAMD or Espressif processors the processor has dedicated control registers for each pin and the Arduino core provides us with a handy "attachInterrupt" function to use to tie our ISR to that pin. 28 | For AVR processors, like the Arduino Uno or the EnviroDIY Mayfly, we have to use a get a bit fancier to control the interrupts. 29 | 30 | [//]: # ( @section interrupts_avr Directly Controlling Interrupts on an AVR Board ) 31 | ## Directly Controlling Interrupts on an AVR Board 32 | 33 | [//]: # ( @subsection interrupts_vocab Some Vocabulary ) 34 | ### Some Vocabulary: 35 | 36 | **Registers**: small 1-byte (8-bit) stores of memory directly accessible by processor 37 | PCMSK0, PCMSK1, PCMSK2, PCMSK3 38 | 39 | **PCICRx**: a register where the three least significant bits enable or disable pin change interrupts on a range of pins 40 | - i.e. {0,0,0,0,0,PCIE2,PCIE1,PCIE0}, where PCIE2 maps to PCMSK2, PCIE1 maps to PCMSK1, and PCIE0 maps to PCMSK0. 41 | 42 | **PCMSKx**: a register that stores the state (enabled/disabled) of pin change interrupts on a single pin 43 | - Each bit stores a 1 (enabled) or 0 (disabled). 44 | 45 | On an Arduino Uno: 46 | - There is on PCICR register controlling three ranges of pins 47 | - There are three mask registers (PCMSK0, PCMSK1, and PCMSK2) controlling individual pins. 48 | - Looking at one mask register, PCMSK0: 49 | - the 8 bits represent: PCMSK0 {PCINT7, PCINT6, PCINT5, PCINT4, PCINT3, PCINT2, PCINT1, PCINT0} 50 | - these map to: PCMSK0 {XTAL2, XTAL1, Pin 13, Pin 12, Pin 11, Pin 10, Pin 9, Pin 8} 51 | 52 | **noInterrupts()**: a function to globally disable interrupts (of all types) 53 | 54 | **interrupts()**: a function to globally enable interrupts (of all types) 55 | - interrupts will only occur if the requisite registers are set (e.g. PCMSK and PCICR). 56 | 57 | [//]: # ( @subsection interrupts_enable Enabling an Interrupt ) 58 | ### Enabling an Interrupt 59 | 60 | Initially, no interrupts are enabled, so PCMSK0 looks like: `{00000000}`. 61 | If we were to use pin 9 as the data pin, we would set the bit in the pin 9 position to 1, like so: `{00000010}`. 62 | 63 | To accomplish this, we can make use of some predefined macros. 64 | 65 | One macro `digitalPinToPCMSK` is defined in "pins_arduino.h" which allows us to quickly get the proper register (PCMSK0, PCMSK1, or PCMSK2) given the number of the Arduino pin. 66 | So when we write: `digitalPinToPCMSK(9)`, the address of PCMSK0 is returned. 67 | We can use the dereferencing operator '\*' to get the value of the address. 68 | 69 | That is, `*digitalPinToPCMSK(9)` returns: `{00000000}`. 70 | 71 | Another macro , `digitalPinToPCMSKbit` is also defined in "pins_arduino.h" and returns the position of the bit of interest within the PCMSK of interest. 72 | 73 | So when we write: `digitalPinToPCMSKbit(9)`, the value returned is 1, because pin 9 is represented by the "1st bit" within PCMSK0, which has a zero-based index. 74 | That is: PCMSK { 7th bit, 6th bit, 5th bit, 4th bit, 3rd bit, 2nd bit, 1st bit, 0th bit } 75 | 76 | The leftward bit shift operator `<<` can then used to create a "mask". 77 | Masks are data that are used during bitwise operations to manipulate (enable / disable) one or more individual bits simultaneously. 78 | 79 | The syntax for the operator is (variable << number_of_bits). 80 | Some examples: 81 | 82 | ```cpp 83 | byte a = 5; // binary: a = 00000101 84 | 85 | byte b = a << 4; // binary: b = 01010000 86 | ``` 87 | 88 | So when we write: `(1< 7 | * 8 | * @brief Example J: Using External Interrupts 9 | * 10 | * This is identical to example B, except that it uses the library 11 | * [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) to define the 12 | * interrupt vector. This allows it to play nicely with any other libraries which define 13 | * interrupt vectors. 14 | * 15 | * For this to work, you must remove the comment braces around 16 | * `#define SDI12_EXTERNAL_PCINT` in the library and re-compile it. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 23 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 24 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 25 | 26 | /** Define the SDI-12 bus */ 27 | SDI12 mySDI12(DATA_PIN); 28 | 29 | // keeps track of active addresses 30 | bool isActive[64] = { 31 | 0, 32 | }; 33 | 34 | uint8_t numSensors = 0; 35 | 36 | 37 | /** 38 | * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a 39 | * decimal number between 0 and 61 (inclusive) to cover the 62 possible 40 | * addresses. 41 | */ 42 | byte charToDec(char i) { 43 | if ((i >= '0') && (i <= '9')) return i - '0'; 44 | if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10; 45 | if ((i >= 'A') && (i <= 'Z')) 46 | return i - 'A' + 36; 47 | else 48 | return i; 49 | } 50 | 51 | /** 52 | * @brief maps a decimal number between 0 and 61 (inclusive) to allowable 53 | * address characters '0'-'9', 'a'-'z', 'A'-'Z', 54 | * 55 | * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL. 56 | */ 57 | char decToChar(byte i) { 58 | if (i < 10) return i + '0'; 59 | if ((i >= 10) && (i < 36)) return i + 'a' - 10; 60 | if ((i >= 36) && (i <= 62)) 61 | return i + 'A' - 36; 62 | else 63 | return i; 64 | } 65 | 66 | /** 67 | * @brief gets identification information from a sensor, and prints it to the serial 68 | * port 69 | * 70 | * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. 71 | */ 72 | void printInfo(char i) { 73 | String command = ""; 74 | command += (char)i; 75 | command += "I!"; 76 | mySDI12.sendCommand(command); 77 | delay(100); 78 | 79 | String sdiResponse = mySDI12.readStringUntil('\n'); 80 | sdiResponse.trim(); 81 | // allccccccccmmmmmmvvvxxx...xx 82 | Serial.print(sdiResponse.substring(0, 1)); // address 83 | Serial.print(", "); 84 | Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number 85 | Serial.print(", "); 86 | Serial.print(sdiResponse.substring(3, 11)); // vendor id 87 | Serial.print(", "); 88 | Serial.print(sdiResponse.substring(11, 17)); // sensor model 89 | Serial.print(", "); 90 | Serial.print(sdiResponse.substring(17, 20)); // sensor version 91 | Serial.print(", "); 92 | Serial.print(sdiResponse.substring(20)); // sensor id 93 | Serial.print(", "); 94 | } 95 | 96 | bool getResults(char i, int resultsExpected) { 97 | uint8_t resultsReceived = 0; 98 | uint8_t cmd_number = 0; 99 | while (resultsReceived < resultsExpected && cmd_number <= 9) { 100 | String command = ""; 101 | // in this example we will only take the 'DO' measurement 102 | command = ""; 103 | command += i; 104 | command += "D"; 105 | command += cmd_number; 106 | command += "!"; // SDI-12 command to get data [address][D][dataOption][!] 107 | mySDI12.sendCommand(command); 108 | 109 | uint32_t start = millis(); 110 | while (mySDI12.available() < 3 && (millis() - start) < 1500) {} 111 | mySDI12.read(); // ignore the repeated SDI12 address 112 | char c = mySDI12.peek(); // check if there's a '+' and toss if so 113 | if (c == '+') { mySDI12.read(); } 114 | 115 | while (mySDI12.available()) { 116 | char c = mySDI12.peek(); 117 | if (c == '-' || (c >= '0' && c <= '9') || c == '.') { 118 | float result = mySDI12.parseFloat(SKIP_NONE); 119 | Serial.print(String(result, 10)); 120 | if (result != -9999) { resultsReceived++; } 121 | } else if (c == '+') { 122 | mySDI12.read(); 123 | Serial.print(", "); 124 | } else { 125 | mySDI12.read(); 126 | } 127 | delay(10); // 1 character ~ 7.5ms 128 | } 129 | if (resultsReceived < resultsExpected) { Serial.print(", "); } 130 | cmd_number++; 131 | } 132 | mySDI12.clearBuffer(); 133 | 134 | return resultsReceived == resultsExpected; 135 | } 136 | 137 | bool takeMeasurement(char i, String meas_type = "") { 138 | mySDI12.clearBuffer(); 139 | String command = ""; 140 | command += i; 141 | command += "M"; 142 | command += meas_type; 143 | command += "!"; // SDI-12 measurement command format [address]['M'][!] 144 | mySDI12.sendCommand(command); 145 | delay(100); 146 | 147 | // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of 148 | // measurments available, 0-9] 149 | String sdiResponse = mySDI12.readStringUntil('\n'); 150 | sdiResponse.trim(); 151 | 152 | String addr = sdiResponse.substring(0, 1); 153 | Serial.print(addr); 154 | Serial.print(", "); 155 | 156 | // find out how long we have to wait (in seconds). 157 | uint8_t wait = sdiResponse.substring(1, 4).toInt(); 158 | Serial.print(wait); 159 | Serial.print(", "); 160 | 161 | // Set up the number of results to expect 162 | int numResults = sdiResponse.substring(4).toInt(); 163 | Serial.print(numResults); 164 | Serial.print(", "); 165 | 166 | unsigned long timerStart = millis(); 167 | while ((millis() - timerStart) < (1000 * (wait + 1))) { 168 | if (mySDI12.available()) // sensor can interrupt us to let us know it is done early 169 | { 170 | Serial.print(millis() - timerStart); 171 | Serial.print(", "); 172 | mySDI12.clearBuffer(); 173 | break; 174 | } 175 | } 176 | // Wait for anything else and clear it out 177 | delay(30); 178 | mySDI12.clearBuffer(); 179 | 180 | if (numResults > 0) { return getResults(i, numResults); } 181 | 182 | return true; 183 | } 184 | 185 | // this checks for activity at a particular address 186 | // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' 187 | boolean checkActive(char i) { 188 | String myCommand = ""; 189 | myCommand = ""; 190 | myCommand += (char)i; // sends basic 'acknowledge' command [address][!] 191 | myCommand += "!"; 192 | 193 | for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts 194 | mySDI12.sendCommand(myCommand); 195 | delay(100); 196 | if (mySDI12.available()) { // If we here anything, assume we have an active sensor 197 | mySDI12.clearBuffer(); 198 | return true; 199 | } 200 | } 201 | mySDI12.clearBuffer(); 202 | return false; 203 | } 204 | 205 | 206 | void setup() { 207 | Serial.begin(SERIAL_BAUD); 208 | while (!Serial) 209 | ; 210 | 211 | Serial.println("Opening SDI-12 bus..."); 212 | mySDI12.begin(); 213 | delay(500); // allow things to settle 214 | 215 | Serial.println("Timeout value: "); 216 | Serial.println(mySDI12.TIMEOUT); 217 | 218 | // Power the sensors; 219 | if (POWER_PIN > 0) { 220 | Serial.println("Powering up sensors..."); 221 | pinMode(POWER_PIN, OUTPUT); 222 | digitalWrite(POWER_PIN, HIGH); 223 | delay(200); 224 | } 225 | 226 | // Enable interrupts for the recieve pin 227 | pinMode(DATA_PIN, INPUT_PULLUP); 228 | enableInterrupt(DATA_PIN, SDI12::handleInterrupt, CHANGE); 229 | 230 | // Quickly Scan the Address Space 231 | Serial.println("Scanning all addresses, please wait..."); 232 | Serial.println("Protocol Version, Sensor Address, Sensor Vendor, Sensor Model, " 233 | "Sensor Version, Sensor ID"); 234 | 235 | for (byte i = 0; i < 62; i++) { 236 | char addr = decToChar(i); 237 | if (checkActive(addr)) { 238 | numSensors++; 239 | isActive[i] = 1; 240 | printInfo(addr); 241 | Serial.println(); 242 | } 243 | } 244 | Serial.print("Total number of sensors found: "); 245 | Serial.println(numSensors); 246 | 247 | if (numSensors == 0) { 248 | Serial.println( 249 | "No sensors found, please check connections and restart the Arduino."); 250 | while (true) { delay(10); } // do nothing forever 251 | } 252 | 253 | Serial.println(); 254 | Serial.println("Time Elapsed (s), Est Measurement Time (s), Number Measurements, " 255 | "Real Measurement Time (ms), Measurement 1, Measurement 2, ... etc."); 256 | Serial.println( 257 | "-------------------------------------------------------------------------------"); 258 | } 259 | 260 | void loop() { 261 | // measure one at a time 262 | for (byte i = 0; i < 62; i++) { 263 | char addr = decToChar(i); 264 | if (isActive[i]) { 265 | Serial.print(millis() / 1000); 266 | Serial.print(", "); 267 | takeMeasurement(addr); 268 | Serial.println(); 269 | } 270 | } 271 | 272 | delay(10000); // wait ten seconds between measurement attempts. 273 | } 274 | -------------------------------------------------------------------------------- /examples/d_simple_logger/d_simple_logger.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file d_simple_logger.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Kevin M.Smith 7 | * @date August 2013 8 | * 9 | * @brief Example D: Check all Addresses for Active Sensors and Log Data 10 | * 11 | * This is a simple demonstration of the SDI-12 library for Arduino. 12 | * 13 | * It discovers the address of all sensors active on a single bus and takes measurements 14 | * from them. 15 | * 16 | * Every SDI-12 device is different in the time it takes to take a 17 | * measurement, and the amount of data it returns. This sketch will not serve every 18 | * sensor type, but it will likely be helpful in getting you started. 19 | * 20 | * Each sensor should have a unique address already - if not, multiple sensors may 21 | * respond simultaenously to the same request and the output will not be readable by the 22 | * Arduino. 23 | * 24 | * To address a sensor, please see Example B: b_address_change.ino 25 | */ 26 | 27 | #include 28 | 29 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 30 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 31 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 32 | 33 | /** Define the SDI-12 bus */ 34 | SDI12 mySDI12(DATA_PIN); 35 | 36 | // keeps track of active addresses 37 | bool isActive[64] = { 38 | 0, 39 | }; 40 | 41 | uint8_t numSensors = 0; 42 | 43 | 44 | /** 45 | * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a 46 | * decimal number between 0 and 61 (inclusive) to cover the 62 possible 47 | * addresses. 48 | */ 49 | byte charToDec(char i) { 50 | if ((i >= '0') && (i <= '9')) return i - '0'; 51 | if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10; 52 | if ((i >= 'A') && (i <= 'Z')) 53 | return i - 'A' + 36; 54 | else 55 | return i; 56 | } 57 | 58 | /** 59 | * @brief maps a decimal number between 0 and 61 (inclusive) to allowable 60 | * address characters '0'-'9', 'a'-'z', 'A'-'Z', 61 | * 62 | * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL. 63 | */ 64 | char decToChar(byte i) { 65 | if (i < 10) return i + '0'; 66 | if ((i >= 10) && (i < 36)) return i + 'a' - 10; 67 | if ((i >= 36) && (i <= 62)) 68 | return i + 'A' - 36; 69 | else 70 | return i; 71 | } 72 | 73 | /** 74 | * @brief gets identification information from a sensor, and prints it to the serial 75 | * port 76 | * 77 | * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. 78 | */ 79 | void printInfo(char i) { 80 | String command = ""; 81 | command += (char)i; 82 | command += "I!"; 83 | mySDI12.sendCommand(command); 84 | delay(100); 85 | 86 | String sdiResponse = mySDI12.readStringUntil('\n'); 87 | sdiResponse.trim(); 88 | // allccccccccmmmmmmvvvxxx...xx 89 | Serial.print(sdiResponse.substring(0, 1)); // address 90 | Serial.print(", "); 91 | Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number 92 | Serial.print(", "); 93 | Serial.print(sdiResponse.substring(3, 11)); // vendor id 94 | Serial.print(", "); 95 | Serial.print(sdiResponse.substring(11, 17)); // sensor model 96 | Serial.print(", "); 97 | Serial.print(sdiResponse.substring(17, 20)); // sensor version 98 | Serial.print(", "); 99 | Serial.print(sdiResponse.substring(20)); // sensor id 100 | Serial.print(", "); 101 | } 102 | 103 | bool getResults(char i, int resultsExpected) { 104 | uint8_t resultsReceived = 0; 105 | uint8_t cmd_number = 0; 106 | while (resultsReceived < resultsExpected && cmd_number <= 9) { 107 | String command = ""; 108 | // in this example we will only take the 'DO' measurement 109 | command = ""; 110 | command += i; 111 | command += "D"; 112 | command += cmd_number; 113 | command += "!"; // SDI-12 command to get data [address][D][dataOption][!] 114 | mySDI12.sendCommand(command); 115 | 116 | uint32_t start = millis(); 117 | while (mySDI12.available() < 3 && (millis() - start) < 1500) {} 118 | mySDI12.read(); // ignore the repeated SDI12 address 119 | char c = mySDI12.peek(); // check if there's a '+' and toss if so 120 | if (c == '+') { mySDI12.read(); } 121 | 122 | while (mySDI12.available()) { 123 | char c = mySDI12.peek(); 124 | if (c == '-' || (c >= '0' && c <= '9') || c == '.') { 125 | float result = mySDI12.parseFloat(SKIP_NONE); 126 | Serial.print(String(result, 10)); 127 | if (result != -9999) { resultsReceived++; } 128 | } else if (c == '+') { 129 | mySDI12.read(); 130 | Serial.print(", "); 131 | } else { 132 | mySDI12.read(); 133 | } 134 | delay(10); // 1 character ~ 7.5ms 135 | } 136 | if (resultsReceived < resultsExpected) { Serial.print(", "); } 137 | cmd_number++; 138 | } 139 | mySDI12.clearBuffer(); 140 | 141 | return resultsReceived == resultsExpected; 142 | } 143 | 144 | bool takeMeasurement(char i, String meas_type = "") { 145 | mySDI12.clearBuffer(); 146 | String command = ""; 147 | command += i; 148 | command += "M"; 149 | command += meas_type; 150 | command += "!"; // SDI-12 measurement command format [address]['M'][!] 151 | mySDI12.sendCommand(command); 152 | delay(100); 153 | 154 | // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of 155 | // measurments available, 0-9] 156 | String sdiResponse = mySDI12.readStringUntil('\n'); 157 | sdiResponse.trim(); 158 | 159 | String addr = sdiResponse.substring(0, 1); 160 | Serial.print(addr); 161 | Serial.print(", "); 162 | 163 | // find out how long we have to wait (in seconds). 164 | uint8_t wait = sdiResponse.substring(1, 4).toInt(); 165 | Serial.print(wait); 166 | Serial.print(", "); 167 | 168 | // Set up the number of results to expect 169 | int numResults = sdiResponse.substring(4).toInt(); 170 | Serial.print(numResults); 171 | Serial.print(", "); 172 | 173 | unsigned long timerStart = millis(); 174 | while ((millis() - timerStart) < (1000 * (wait + 1))) { 175 | if (mySDI12.available()) // sensor can interrupt us to let us know it is done early 176 | { 177 | Serial.print(millis() - timerStart); 178 | Serial.print(", "); 179 | mySDI12.clearBuffer(); 180 | break; 181 | } 182 | } 183 | // Wait for anything else and clear it out 184 | delay(30); 185 | mySDI12.clearBuffer(); 186 | 187 | if (numResults > 0) { return getResults(i, numResults); } 188 | 189 | return true; 190 | } 191 | 192 | // this checks for activity at a particular address 193 | // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' 194 | boolean checkActive(char i) { 195 | String myCommand = ""; 196 | myCommand = ""; 197 | myCommand += (char)i; // sends basic 'acknowledge' command [address][!] 198 | myCommand += "!"; 199 | 200 | for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts 201 | mySDI12.sendCommand(myCommand); 202 | delay(100); 203 | if (mySDI12.available()) { // If we here anything, assume we have an active sensor 204 | mySDI12.clearBuffer(); 205 | return true; 206 | } 207 | } 208 | mySDI12.clearBuffer(); 209 | return false; 210 | } 211 | 212 | 213 | void setup() { 214 | Serial.begin(SERIAL_BAUD); 215 | while (!Serial) 216 | ; 217 | 218 | Serial.println("Opening SDI-12 bus..."); 219 | mySDI12.begin(); 220 | delay(500); // allow things to settle 221 | 222 | Serial.println("Timeout value: "); 223 | Serial.println(mySDI12.TIMEOUT); 224 | 225 | // Power the sensors; 226 | if (POWER_PIN > 0) { 227 | Serial.println("Powering up sensors..."); 228 | pinMode(POWER_PIN, OUTPUT); 229 | digitalWrite(POWER_PIN, HIGH); 230 | delay(200); 231 | } 232 | 233 | // Quickly Scan the Address Space 234 | Serial.println("Scanning all addresses, please wait..."); 235 | Serial.println("Sensor Address, Protocol Version, Sensor Vendor, Sensor Model, " 236 | "Sensor Version, Sensor ID"); 237 | 238 | for (byte i = 0; i < 62; i++) { 239 | char addr = decToChar(i); 240 | if (checkActive(addr)) { 241 | numSensors++; 242 | isActive[i] = 1; 243 | printInfo(addr); 244 | Serial.println(); 245 | } 246 | } 247 | Serial.print("Total number of sensors found: "); 248 | Serial.println(numSensors); 249 | 250 | if (numSensors == 0) { 251 | Serial.println( 252 | "No sensors found, please check connections and restart the Arduino."); 253 | while (true) { delay(10); } // do nothing forever 254 | } 255 | 256 | Serial.println(); 257 | Serial.println( 258 | "Time Elapsed (s), Sensor Address, Est Measurement Time (s), Number Measurements, " 259 | "Real Measurement Time (ms), Measurement 1, Measurement 2, ... etc."); 260 | Serial.println( 261 | "-------------------------------------------------------------------------------"); 262 | } 263 | 264 | void loop() { 265 | String commands[] = {"", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; 266 | for (uint8_t a = 0; a < 3; a++) { 267 | // measure one at a time 268 | for (byte i = 0; i < 62; i++) { 269 | char addr = decToChar(i); 270 | if (isActive[i]) { 271 | // Serial.print(millis() / 1000); 272 | Serial.print(millis()); 273 | Serial.print(", "); 274 | takeMeasurement(addr, commands[a]); 275 | Serial.println(); 276 | } 277 | } 278 | } 279 | 280 | delay(10000L); // wait ten seconds between measurement attempts. 281 | } 282 | -------------------------------------------------------------------------------- /examples/k_concurrent_logger/k_concurrent_logger.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file k_concurrent_logger.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @author Sara Geleskie Damiano 7 | * 8 | * @brief Example K: Concurrent Measurements 9 | * 10 | * This is very similar to example B - finding all attached sensors and logging data 11 | * from them. Unlike example B, however, which waits for each sensor to complete a 12 | * measurement, this asks all sensors to take measurements concurrently and then waits 13 | * until each is finished to query for results. This can be much faster than waiting for 14 | * each sensor when you have multiple sensor attached. 15 | */ 16 | 17 | #include 18 | 19 | #define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ 20 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 21 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 22 | 23 | /** Define the SDI-12 bus */ 24 | SDI12 mySDI12(DATA_PIN); 25 | 26 | // keeps track of active addresses 27 | bool isActive[64] = { 28 | 0, 29 | }; 30 | 31 | // keeps track of the wait time for each active addresses 32 | uint8_t waitTime[64] = { 33 | 0, 34 | }; 35 | 36 | // keeps track of the time each sensor was started 37 | uint32_t millisStarted[64] = { 38 | 0, 39 | }; 40 | 41 | // keeps track of the time each sensor will be ready 42 | uint32_t millisReady[64] = { 43 | 0, 44 | }; 45 | 46 | // keeps track of the number of results expected 47 | uint8_t returnedResults[64] = { 48 | 0, 49 | }; 50 | 51 | uint8_t numSensors = 0; 52 | 53 | 54 | /** 55 | * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a 56 | * decimal number between 0 and 61 (inclusive) to cover the 62 possible 57 | * addresses. 58 | */ 59 | byte charToDec(char i) { 60 | if ((i >= '0') && (i <= '9')) return i - '0'; 61 | if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10; 62 | if ((i >= 'A') && (i <= 'Z')) 63 | return i - 'A' + 36; 64 | else 65 | return i; 66 | } 67 | 68 | /** 69 | * @brief maps a decimal number between 0 and 61 (inclusive) to allowable 70 | * address characters '0'-'9', 'a'-'z', 'A'-'Z', 71 | * 72 | * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL. 73 | */ 74 | char decToChar(byte i) { 75 | if (i < 10) return i + '0'; 76 | if ((i >= 10) && (i < 36)) return i + 'a' - 10; 77 | if ((i >= 36) && (i <= 62)) 78 | return i + 'A' - 36; 79 | else 80 | return i; 81 | } 82 | 83 | /** 84 | * @brief gets identification information from a sensor, and prints it to the serial 85 | * port 86 | * 87 | * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. 88 | */ 89 | void printInfo(char i) { 90 | String command = ""; 91 | command += (char)i; 92 | command += "I!"; 93 | mySDI12.sendCommand(command); 94 | delay(100); 95 | 96 | String sdiResponse = mySDI12.readStringUntil('\n'); 97 | sdiResponse.trim(); 98 | // allccccccccmmmmmmvvvxxx...xx 99 | Serial.print(sdiResponse.substring(0, 1)); // address 100 | Serial.print(", "); 101 | Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number 102 | Serial.print(", "); 103 | Serial.print(sdiResponse.substring(3, 11)); // vendor id 104 | Serial.print(", "); 105 | Serial.print(sdiResponse.substring(11, 17)); // sensor model 106 | Serial.print(", "); 107 | Serial.print(sdiResponse.substring(17, 20)); // sensor version 108 | Serial.print(", "); 109 | Serial.print(sdiResponse.substring(20)); // sensor id 110 | Serial.print(", "); 111 | } 112 | 113 | bool getResults(char i, int resultsExpected) { 114 | uint8_t resultsReceived = 0; 115 | uint8_t cmd_number = 0; 116 | while (resultsReceived < resultsExpected && cmd_number <= 9) { 117 | String command = ""; 118 | // in this example we will only take the 'DO' measurement 119 | command = ""; 120 | command += i; 121 | command += "D"; 122 | command += cmd_number; 123 | command += "!"; // SDI-12 command to get data [address][D][dataOption][!] 124 | mySDI12.sendCommand(command); 125 | 126 | uint32_t start = millis(); 127 | while (mySDI12.available() < 3 && (millis() - start) < 1500) {} 128 | mySDI12.read(); // ignore the repeated SDI12 address 129 | char c = mySDI12.peek(); // check if there's a '+' and toss if so 130 | if (c == '+') { mySDI12.read(); } 131 | 132 | while (mySDI12.available()) { 133 | char c = mySDI12.peek(); 134 | if (c == '-' || (c >= '0' && c <= '9') || c == '.') { 135 | float result = mySDI12.parseFloat(SKIP_NONE); 136 | Serial.print(String(result, 10)); 137 | if (result != -9999) { resultsReceived++; } 138 | } else if (c == '+') { 139 | mySDI12.read(); 140 | Serial.print(", "); 141 | } else { 142 | mySDI12.read(); 143 | } 144 | delay(10); // 1 character ~ 7.5ms 145 | } 146 | if (resultsReceived < resultsExpected) { Serial.print(", "); } 147 | cmd_number++; 148 | } 149 | mySDI12.clearBuffer(); 150 | 151 | return resultsReceived == resultsExpected; 152 | } 153 | 154 | int startConcurrentMeasurement(char i, String meas_type = "") { 155 | mySDI12.clearBuffer(); 156 | String command = ""; 157 | command += i; 158 | command += "C"; 159 | command += meas_type; 160 | command += "!"; // SDI-12 concurrent measurement command format [address]['C'][!] 161 | mySDI12.sendCommand(command); 162 | delay(30); 163 | 164 | // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of 165 | // measurments available, 0-9] 166 | String sdiResponse = mySDI12.readStringUntil('\n'); 167 | sdiResponse.trim(); 168 | 169 | // find out how long we have to wait (in seconds). 170 | uint8_t wait = sdiResponse.substring(1, 4).toInt(); 171 | 172 | // Set up the number of results to expect 173 | int numResults = sdiResponse.substring(4).toInt(); 174 | 175 | uint8_t sensorNum = charToDec(i); // e.g. convert '0' to 0, 'a' to 10, 'Z' to 61. 176 | waitTime[sensorNum] = wait; 177 | millisStarted[sensorNum] = millis(); 178 | if (wait == 0) { 179 | millisReady[sensorNum] = millis(); 180 | } else { 181 | millisReady[sensorNum] = millis() + wait * 1000; 182 | } 183 | returnedResults[sensorNum] = numResults; 184 | 185 | return numResults; 186 | } 187 | 188 | // this checks for activity at a particular address 189 | // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' 190 | boolean checkActive(char i) { 191 | String myCommand = ""; 192 | myCommand = ""; 193 | myCommand += (char)i; // sends basic 'acknowledge' command [address][!] 194 | myCommand += "!"; 195 | 196 | for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts 197 | mySDI12.sendCommand(myCommand); 198 | delay(100); 199 | if (mySDI12.available()) { // If we here anything, assume we have an active sensor 200 | mySDI12.clearBuffer(); 201 | return true; 202 | } 203 | } 204 | mySDI12.clearBuffer(); 205 | return false; 206 | } 207 | 208 | 209 | void setup() { 210 | Serial.begin(SERIAL_BAUD); 211 | while (!Serial) 212 | ; 213 | 214 | Serial.println("Opening SDI-12 bus..."); 215 | mySDI12.begin(); 216 | delay(500); // allow things to settle 217 | 218 | Serial.println("Timeout value: "); 219 | Serial.println(mySDI12.TIMEOUT); 220 | 221 | // Power the sensors; 222 | if (POWER_PIN > 0) { 223 | Serial.println("Powering up sensors..."); 224 | pinMode(POWER_PIN, OUTPUT); 225 | digitalWrite(POWER_PIN, HIGH); 226 | delay(200); 227 | } 228 | 229 | // Quickly Scan the Address Space 230 | Serial.println("Scanning all addresses, please wait..."); 231 | Serial.println("Protocol Version, Sensor Address, Sensor Vendor, Sensor Model, " 232 | "Sensor Version, Sensor ID"); 233 | 234 | for (byte i = 0; i < 62; i++) { 235 | char addr = decToChar(i); 236 | if (checkActive(addr)) { 237 | numSensors++; 238 | isActive[i] = 1; 239 | printInfo(addr); 240 | Serial.println(); 241 | } 242 | } 243 | Serial.print("Total number of sensors found: "); 244 | Serial.println(numSensors); 245 | 246 | if (numSensors == 0) { 247 | Serial.println( 248 | "No sensors found, please check connections and restart the Arduino."); 249 | while (true) { delay(10); } // do nothing forever 250 | } 251 | 252 | Serial.println(); 253 | Serial.println("Time Elapsed (s), Measurement 1, Measurement 2, ... etc."); 254 | Serial.println( 255 | "-------------------------------------------------------------------------------"); 256 | } 257 | 258 | void loop() { 259 | // start all sensors measuring concurrently 260 | for (byte i = 0; i < 62; i++) { 261 | char addr = decToChar(i); 262 | if (isActive[i]) { startConcurrentMeasurement(addr); } 263 | } 264 | 265 | // get all readings 266 | uint8_t numReadingsRecorded = 0; 267 | while (numReadingsRecorded < numSensors) { 268 | for (byte i = 0; i < 62; i++) { 269 | char addr = decToChar(i); 270 | if (isActive[i]) { 271 | if (millis() > millisReady[i]) { 272 | if (returnedResults[i] > 0) { 273 | Serial.print(millis() / 1000); 274 | Serial.print(", "); 275 | Serial.print(addr); 276 | Serial.print(", "); 277 | getResults(addr, returnedResults[i]); 278 | Serial.println(); 279 | } 280 | numReadingsRecorded++; 281 | } 282 | } 283 | } 284 | } 285 | 286 | delay(10000); // wait ten seconds between measurement attempts. 287 | } 288 | -------------------------------------------------------------------------------- /docs/DoxygenLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /src/SDI12_boards.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SDI12_boards.cpp 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * @author Sara Geleskie Damiano (sdamiano@stroudcenter.org) 6 | * 7 | * @brief This file implements the setting and unsetting of the proper prescalers for 8 | * the timers for SDI-12. 9 | * 10 | */ 11 | 12 | /* ======================== Arduino SDI-12 ================================= 13 | An Arduino library for SDI-12 communication with a wide variety of environmental 14 | sensors. This library provides a general software solution, without requiring 15 | ======================== Arduino SDI-12 =================================*/ 16 | 17 | #include "SDI12_boards.h" 18 | 19 | SDI12Timer::SDI12Timer() {} 20 | 21 | // Most 'standard' AVR boards 22 | // 23 | #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || \ 24 | defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || \ 25 | defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || \ 26 | defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) 27 | 28 | /** 29 | * @brief The value of timer control register 2A prior to being set for SDI-12. 30 | */ 31 | static uint8_t preSDI12_TCCR2A; 32 | /** 33 | * @brief The value of timer control register 2B prior to being set for SDI-12. 34 | */ 35 | static uint8_t preSDI12_TCCR2B; 36 | 37 | #if F_CPU == 16000000L 38 | 39 | void SDI12Timer::configSDI12TimerPrescale(void) { 40 | preSDI12_TCCR2A = TCCR2A; 41 | preSDI12_TCCR2B = TCCR2B; 42 | TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & 43 | // OC2B disconnected 44 | TCCR2B = 0x07; // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - 45 | // prescaler set to CK/1024 46 | } 47 | void SDI12Timer::resetSDI12TimerPrescale(void) { 48 | TCCR2A = preSDI12_TCCR2A; 49 | TCCR2B = preSDI12_TCCR2B; 50 | } 51 | 52 | #elif F_CPU == 12000000L 53 | 54 | void SDI12Timer::configSDI12TimerPrescale(void) { 55 | preSDI12_TCCR2A = TCCR2A; 56 | preSDI12_TCCR2B = TCCR2B; 57 | TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & 58 | // OC2B disconnected 59 | TCCR2B = 0x07; // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - 60 | // prescaler set to CK/1024 61 | } 62 | void SDI12Timer::resetSDI12TimerPrescale(void) { 63 | TCCR2A = preSDI12_TCCR2A; 64 | TCCR2B = preSDI12_TCCR2B; 65 | } 66 | 67 | #elif F_CPU == 8000000L 68 | 69 | void SDI12Timer::configSDI12TimerPrescale(void) { 70 | preSDI12_TCCR2A = TCCR2A; 71 | preSDI12_TCCR2B = TCCR2B; 72 | TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & 73 | // OC2B disconnected 74 | TCCR2B = 0x06; // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - 75 | // prescaler set to CK/256 76 | } 77 | void SDI12Timer::resetSDI12TimerPrescale(void) { 78 | TCCR2A = preSDI12_TCCR2A; 79 | TCCR2B = preSDI12_TCCR2B; 80 | } 81 | 82 | // void SDI12Timer::configSDI12TimerPrescale(void) 83 | // { 84 | // preSDI12_TCCR2A = TCCR2A; 85 | // preSDI12_TCCR2B = TCCR2B; 86 | // TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, 87 | // OC2A & OC2B disconnected TCCR2B = 0x07; // TCCR2B = 0x07 = 0b00000111 - Clock 88 | // Select bits 22, 21, & 20 on - prescaler set to CK/1024 89 | // } 90 | // void SDI12Timer::resetSDI12TimerPrescale(void) 91 | // { 92 | // TCCR2A = preSDI12_TCCR2A; 93 | // TCCR2B = preSDI12_TCCR2B; 94 | // } 95 | #endif 96 | 97 | 98 | // ATtiny boards (ie, adafruit trinket) 99 | // 100 | #elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) 101 | 102 | /** 103 | * @brief The value of timer control register 1A prior to being set for SDI-12. 104 | */ 105 | static uint8_t preSDI12_TCCR1A; 106 | 107 | #if F_CPU == 16000000L 108 | 109 | void SDI12Timer::configSDI12TimerPrescale(void) { 110 | preSDI12_TCCR1A = TCCR1; 111 | TCCR1 = 0b00001011; // Set the prescaler to 1024 112 | } 113 | void SDI12Timer::resetSDI12TimerPrescale(void) { 114 | TCCR1 = preSDI12_TCCR1A; 115 | } 116 | 117 | 118 | #elif F_CPU == 8000000L 119 | 120 | void SDI12Timer::configSDI12TimerPrescale(void) { 121 | preSDI12_TCCR1A = TCCR1; 122 | TCCR1 = 0b00001010; // Set the prescaler to 512 123 | } 124 | void SDI12Timer::resetSDI12TimerPrescale(void) { 125 | TCCR1 = preSDI12_TCCR1A; 126 | } 127 | #endif 128 | 129 | 130 | // Arduino Leonardo & Yun and other 32U4 boards 131 | // 132 | #elif defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_LEONARDO) || \ 133 | defined(__AVR_ATmega32U4__) 134 | 135 | /** 136 | * @brief The value of timer control register 4A prior to being set for SDI-12. 137 | */ 138 | static uint8_t preSDI12_TCCR4A; 139 | /** 140 | * @brief The value of timer control register 4B prior to being set for SDI-12. 141 | */ 142 | static uint8_t preSDI12_TCCR4B; 143 | /** 144 | * @brief The value of timer control register 4C prior to being set for SDI-12. 145 | */ 146 | static uint8_t preSDI12_TCCR4C; 147 | /** 148 | * @brief The value of timer control register 4D prior to being set for SDI-12. 149 | */ 150 | static uint8_t preSDI12_TCCR4D; 151 | /** 152 | * @brief The value of timer control register 4E prior to being set for SDI-12. 153 | */ 154 | static uint8_t preSDI12_TCCR4E; 155 | 156 | #if F_CPU == 16000000L 157 | 158 | void SDI12Timer::configSDI12TimerPrescale(void) { 159 | preSDI12_TCCR4A = TCCR4A; 160 | preSDI12_TCCR4B = TCCR4B; 161 | preSDI12_TCCR4C = TCCR4C; 162 | preSDI12_TCCR4D = TCCR4D; 163 | preSDI12_TCCR4E = TCCR4E; 164 | TCCR4A = 0x00; // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & 165 | // OC4B disconnected 166 | TCCR4B = 0x0B; // TCCR4B = 0x0B = 0b00001011 - Clock Select bits 43, 41, & 40 on - 167 | // prescaler set to CK/1024 168 | TCCR4C = 0x00; // TCCR4C = 0x00 = "normal" operation - Normal port operation, OC4D0 169 | // disconnected 170 | TCCR4D = 0x00; // TCCR4D = 0x00 = No fault protection 171 | TCCR4E = 0x00; // TCCR4E = 0x00 = No register locks or overrides 172 | } 173 | void SDI12Timer::resetSDI12TimerPrescale(void) { 174 | TCCR4A = preSDI12_TCCR4A; 175 | TCCR4B = preSDI12_TCCR4B; 176 | TCCR4C = preSDI12_TCCR4C; 177 | TCCR4D = preSDI12_TCCR4D; 178 | TCCR4E = preSDI12_TCCR4E; 179 | } 180 | 181 | #elif F_CPU == 8000000L 182 | void SDI12Timer::configSDI12TimerPrescale(void) { 183 | preSDI12_TCCR4A = TCCR4A; 184 | preSDI12_TCCR4B = TCCR4B; 185 | preSDI12_TCCR4C = TCCR4C; 186 | preSDI12_TCCR4D = TCCR4D; 187 | preSDI12_TCCR4E = TCCR4E; 188 | TCCR4A = 0x00; // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & 189 | // OC4B disconnected 190 | TCCR4B = 0x0A; // TCCR4B = 0x0A = 0b00001010 - Clock Select bits 43 & 41 on - 191 | // prescaler set to CK/512 192 | TCCR4C = 0x00; // TCCR4C = 0x00 = "normal" operation - Normal port operation, OC4D0 193 | // disconnected 194 | TCCR4D = 0x00; // TCCR4D = 0x00 = No fault protection 195 | TCCR4E = 0x00; // TCCR4E = 0x00 = No register locks or overrides 196 | } 197 | void SDI12Timer::resetSDI12TimerPrescale(void) { 198 | TCCR4A = preSDI12_TCCR4A; 199 | TCCR4B = preSDI12_TCCR4B; 200 | TCCR4C = preSDI12_TCCR4C; 201 | TCCR4D = preSDI12_TCCR4D; 202 | TCCR4E = preSDI12_TCCR4E; 203 | } 204 | #endif 205 | 206 | 207 | // Arduino Zero other SAMD21 boards 208 | // 209 | #elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || \ 210 | defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__SAMD21E18A__) 211 | 212 | void SDI12Timer::configSDI12TimerPrescale(void) { 213 | // Select generic clock generator 4 (Arduino core uses 0, 1, and 3. RTCZero uses 2) 214 | // Many examples use clock generator 4.. consider yourself warned! 215 | // I would use a higher clock number, but some of the cores don't include them for 216 | // some reason 217 | REG_GCLK_GENDIV = GCLK_GENDIV_ID(4) | // Select Generic Clock Generator 4 218 | GCLK_GENDIV_DIV(3); // Divide the clock source by divisor 3 219 | while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization 220 | 221 | 222 | // Write the generic clock generator 4 configuration 223 | REG_GCLK_GENCTRL = (GCLK_GENCTRL_ID(4) | // Select GCLK4 224 | GCLK_GENCTRL_SRC_DFLL48M | // Select the 48MHz clock source 225 | GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW 226 | GCLK_GENCTRL_GENEN) & // Enable the generic clock clontrol 227 | ~GCLK_GENCTRL_RUNSTDBY & // Do NOT run in stand by 228 | ~GCLK_GENCTRL_DIVSEL; // Divide clock source by GENDIV.DIV: 48MHz/3=16MHz 229 | // ^^ & ~ for DIVSEL because not not divided 230 | while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization 231 | 232 | // Feed GCLK4 to TC3 (also feeds to TCC2, the two must have the same source) 233 | // TC3 (and TCC2) seem to be free, so I'm using them 234 | // TC4 is used by Tone, TC5 is tied to the same clock as TC4 235 | // TC6 and TC7 are not available on all boards 236 | REG_GCLK_CLKCTRL = GCLK_CLKCTRL_GEN_GCLK4 | // Select Generic Clock Generator 4 237 | GCLK_CLKCTRL_CLKEN | // Enable the generic clock generator 238 | GCLK_CLKCTRL_ID_TCC2_TC3; // Feed the Generic Clock Generator 4 to TCC2 and TC3 239 | while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization 240 | 241 | REG_TC3_CTRLA |= 242 | TC_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024, 16MHz/1024 = 15.625kHz 243 | TC_CTRLA_WAVEGEN_NFRQ | // Put the timer TC3 into normal frequency (NFRQ) mode 244 | TC_CTRLA_MODE_COUNT8 | // Put the timer TC3 into 8-bit mode 245 | TC_CTRLA_ENABLE; // Enable TC3 246 | while (TC3->COUNT16.STATUS.bit.SYNCBUSY) {} // Wait for synchronization 247 | } 248 | // NOT resetting the SAMD timer settings 249 | void SDI12Timer::resetSDI12TimerPrescale(void) { 250 | // Disable TCx 251 | TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; 252 | while (TC3->COUNT16.STATUS.bit.SYNCBUSY) {} 253 | 254 | // Reset TCx 255 | TC3->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; 256 | while (TC3->COUNT16.STATUS.bit.SYNCBUSY) {} 257 | while (TC3->COUNT16.CTRLA.bit.SWRST) {} 258 | 259 | // Disable generic clock generator 260 | REG_GCLK_GENCTRL = GCLK_GENCTRL_ID(4) & // Select GCLK4 261 | ~GCLK_GENCTRL_GENEN; // Disable the generic clock control 262 | while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization 263 | } 264 | 265 | // Espressif ESP32/ESP8266 boards 266 | // 267 | #elif defined(ESP32) || defined(ESP8266) 268 | 269 | void SDI12Timer::configSDI12TimerPrescale(void) {} 270 | void SDI12Timer::resetSDI12TimerPrescale(void) {} 271 | sdi12timer_t SDI12Timer::SDI12TimerRead(void) { 272 | // Its a one microsecond clock but we want 64uS ticks so divide by 64 i.e. right shift 273 | // 6 274 | return ((sdi12timer_t)(micros() >> 6)); 275 | } 276 | // Unknown board 277 | #else 278 | #error "Please define your board timer and pins" 279 | #endif 280 | -------------------------------------------------------------------------------- /examples/h_SDI-12_slave_implementation/h_SDI-12_slave_implementation.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * @file h_SDI-12_slave_implementation.ino 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * This example is published under the BSD-3 license. 6 | * @date 2016 7 | * @author D. Wasielewski 8 | * 9 | * @brief Example H: Using SDI-12 in Slave Mode 10 | * 11 | * Example sketch demonstrating how to implement an arduino as a slave on an SDI-12 bus. 12 | * This may be used, for example, as a middleman between an I2C sensor and an SDI-12 13 | * datalogger. 14 | * 15 | * Note that an SDI-12 slave must respond to M! or C! with the number of values it will 16 | * report and the max time until these values will be available. This example uses 9 17 | * values available in 21 s, but references to these numbers and the output array size 18 | * and datatype should be changed for your specific application. 19 | * 20 | * D. Wasielewski, 2016 21 | * Builds upon work started by: 22 | * https://github.com/jrzondagh/AgriApps-SDI-12-Arduino-Sensor 23 | * https://github.com/Jorge-Mendes/Agro-Shield/tree/master/SDI-12ArduinoSensor 24 | * 25 | * Suggested improvements: 26 | * - Get away from memory-hungry arduino String objects in favor of char buffers 27 | * - Make an int variable for the "number of values to report" instead of the 28 | * hard-coded 9s interspersed throughout the code 29 | */ 30 | 31 | #include 32 | 33 | #define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ 34 | #define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ 35 | 36 | char sensorAddress = '5'; 37 | int state = 0; 38 | 39 | #define WAIT 0 40 | #define INITIATE_CONCURRENT 1 41 | #define INITIATE_MEASUREMENT 2 42 | 43 | // Create object by which to communicate with the SDI-12 bus on SDIPIN 44 | SDI12 slaveSDI12(DATA_PIN); 45 | 46 | 47 | void pollSensor(float* measurementValues) { 48 | measurementValues[0] = 1.111111; 49 | measurementValues[1] = -2.222222; 50 | measurementValues[2] = 3.333333; 51 | measurementValues[3] = -4.444444; 52 | measurementValues[4] = 5.555555; 53 | measurementValues[5] = -6.666666; 54 | measurementValues[6] = 7.777777; 55 | measurementValues[7] = -8.888888; 56 | measurementValues[8] = -9.999999; 57 | } 58 | 59 | void parseSdi12Cmd(String command, String* dValues) { 60 | /* Ingests a command from an SDI-12 master, sends the applicable response, and 61 | * (when applicable) sets a flag to initiate a measurement 62 | */ 63 | 64 | // First char of command is always either (a) the address of the device being 65 | // probed OR (b) a '?' for address query. 66 | // Do nothing if this command is addressed to a different device 67 | if (command.charAt(0) != sensorAddress && command.charAt(0) != '?') { return; } 68 | 69 | // If execution reaches this point, the slave should respond with something in 70 | // the form:
71 | // The following if-switch-case block determines what to put into , 72 | // and the full response will be constructed afterward. For '?!' (address query) 73 | // or 'a!' (acknowledge active) commands, responseStr is blank so section is skipped 74 | String responseStr = ""; 75 | if (command.length() > 1) { 76 | switch (command.charAt(1)) { 77 | case 'I': 78 | // Identify command 79 | // Slave should respond with ID message: 2-char SDI-12 version + 8-char 80 | // company name + 6-char sensor model + 3-char sensor version + 0-13 char S/N 81 | responseStr = "13COMPNAME0000011.0001"; // Substitute proper ID String here 82 | break; 83 | case 'C': 84 | // Initiate concurrent measurement command 85 | // Slave should immediately respond with: "tttnn": 86 | // 3-digit (seconds until measurement is available) + 87 | // 2-digit (number of values that will be available) 88 | // Slave should also start a measurment and relinquish control of the data line 89 | responseStr = 90 | "02109"; // 9 values ready in 21 sec; Substitue sensor-specific values here 91 | // It is not preferred for the actual measurement to occur in this subfunction, 92 | // because doing to would hold the main program hostage until the measurement 93 | // is complete. Instead, we'll just set a flag and handle the measurement 94 | // elsewhere. 95 | state = INITIATE_CONCURRENT; 96 | break; 97 | // NOTE: "aC1...9!" commands may be added by duplicating this case and adding 98 | // additional states to the state flag 99 | case 'M': 100 | // Initiate measurement command 101 | // Slave should immediately respond with: "tttnn": 102 | // 3-digit (seconds until measurement is available) + 103 | // 1-digit (number of values that will be available) 104 | // Slave should also start a measurment but may keep control of the data line 105 | // until advertised time elapsed OR measurement is complete and service request 106 | // sent 107 | responseStr = 108 | "0219"; // 9 values ready in 21 sec; Substitue sensor-specific values here 109 | // It is not preferred for the actual measurement to occur in this subfunction, 110 | // because doing to would hold the main program hostage until the measurement is 111 | // complete. Instead, we'll just set a flag and handle the measurement 112 | // elsewhere. It is preferred though not required that the slave send a service 113 | // request upon completion of the measurement. This should be handled in the 114 | // main loop(). 115 | state = INITIATE_MEASUREMENT; 116 | break; 117 | // NOTE: "aM1...9!" commands may be added by duplicating this case and adding 118 | // additional states to the state flag 119 | 120 | case 'D': 121 | // Send data command 122 | // Slave should respond with a String of values 123 | // Values to be returned must be split into Strings of 35 characters or fewer 124 | // (75 or fewer for concurrent). The number following "D" in the SDI-12 command 125 | // specifies which String to send 126 | responseStr = dValues[(int)command.charAt(2) - 48]; 127 | break; 128 | case 'A': 129 | // Change address command 130 | // Slave should respond with blank message (just the [new] address + + 131 | // ) 132 | sensorAddress = command.charAt(2); 133 | break; 134 | default: 135 | // Mostly for debugging; send back UNKN if unexpected command received 136 | responseStr = "UNKN"; 137 | break; 138 | } 139 | } 140 | 141 | // Issue the response speficied in the switch-case structure above. 142 | slaveSDI12.sendResponse(String(sensorAddress) + responseStr + "\r\n"); 143 | } 144 | 145 | 146 | void formatOutputSDI(float* measurementValues, String* dValues, unsigned int maxChar) { 147 | /* Ingests an array of floats and produces Strings in SDI-12 output format */ 148 | 149 | dValues[0] = ""; 150 | int j = 0; 151 | 152 | // upper limit on i should be number of elements in measurementValues 153 | for (int i = 0; i < 9; i++) { 154 | // Read float value "i" as a String with 6 deceimal digits 155 | // (NOTE: SDI-12 specifies max of 7 digits per value; we can only use 6 156 | // decimal place precision if integer part is one digit) 157 | String valStr = String(measurementValues[i], 6); 158 | // Explictly add implied + sign if non-negative 159 | if (valStr.charAt(0) != '-') { valStr = '+' + valStr; } 160 | // Append dValues[j] if it will not exceed 35 (aM!) or 75 (aC!) characters 161 | if (dValues[j].length() + valStr.length() < maxChar) { 162 | dValues[j] += valStr; 163 | } 164 | // Start a new dValues "line" if appending would exceed 35/75 characters 165 | else { 166 | dValues[++j] = valStr; 167 | } 168 | } 169 | 170 | // Fill rest of dValues with blank strings 171 | while (j < 9) { dValues[++j] = ""; } 172 | } 173 | 174 | 175 | void setup() { 176 | slaveSDI12.begin(); 177 | delay(500); 178 | slaveSDI12.forceListen(); // sets SDIPIN as input to prepare for incoming message 179 | } 180 | 181 | void loop() { 182 | static float measurementValues[9]; // 9 floats to hold simulated sensor data 183 | static String 184 | dValues[10]; // 10 String objects to hold the responses to aD0!-aD9! commands 185 | static String commandReceived = ""; // String object to hold the incoming command 186 | 187 | 188 | // If a byte is available, an SDI message is queued up. Read in the entire message 189 | // before proceding. It may be more robust to add a single character per loop() 190 | // iteration to a static char buffer; however, the SDI-12 spec requires a precise 191 | // response time, and this method is invariant to the remaining loop() contents. 192 | int avail = slaveSDI12.available(); 193 | if (avail < 0) { 194 | slaveSDI12.clearBuffer(); 195 | } // Buffer is full; clear 196 | else if (avail > 0) { 197 | for (int a = 0; a < avail; a++) { 198 | char charReceived = slaveSDI12.read(); 199 | // Character '!' indicates the end of an SDI-12 command; if the current 200 | // character is '!', stop listening and respond to the command 201 | if (charReceived == '!') { 202 | // Command string is completed; do something with it 203 | parseSdi12Cmd(commandReceived, dValues); 204 | // Clear command string to reset for next command 205 | commandReceived = ""; 206 | // '!' should be the last available character anyway, but exit the "for" loop 207 | // just in case there are any stray characters 208 | slaveSDI12.clearBuffer(); 209 | // eliminate the chance of getting anything else after the '!' 210 | slaveSDI12.forceHold(); 211 | break; 212 | } 213 | // If the current character is anything but '!', it is part of the command 214 | // string. Append the commandReceived String object. 215 | else { 216 | // Append command string with new character 217 | commandReceived += String(charReceived); 218 | } 219 | } 220 | } 221 | 222 | // For aM! and aC! commands, parseSdi12Cmd will modify "state" to indicate that 223 | // a measurement should be taken 224 | switch (state) { 225 | case WAIT: break; 226 | case INITIATE_CONCURRENT: 227 | // Do whatever the sensor is supposed to do here 228 | // For this example, we will just create arbitrary "simulated" sensor data 229 | // NOTE: Your application might have a different data type (e.g. int) and 230 | // number of values to report! 231 | pollSensor(measurementValues); 232 | // Populate the "dValues" String array with the values in SDI-12 format 233 | formatOutputSDI(measurementValues, dValues, 75); 234 | state = WAIT; 235 | slaveSDI12.forceListen(); // sets SDI-12 pin as input to prepare for incoming 236 | // message AGAIN 237 | break; 238 | case INITIATE_MEASUREMENT: 239 | // Do whatever the sensor is supposed to do here 240 | // For this example, we will just create arbitrary "simulated" sensor data 241 | // NOTE: Your application might have a different data type (e.g. int) and 242 | // number of values to report! 243 | pollSensor(measurementValues); 244 | // Populate the "dValues" String array with the values in SDI-12 format 245 | formatOutputSDI(measurementValues, dValues, 35); 246 | // For aM!, Send "service request" (
) when data is ready 247 | slaveSDI12.sendResponse(String(sensorAddress) + "\r\n"); 248 | state = WAIT; 249 | slaveSDI12.forceListen(); // sets SDI-12 pin as input to prepare for incoming 250 | // message AGAIN 251 | break; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [//]: # ( @mainpage SDI-12 for Arduino) 3 | # SDI-12 for Arduino 4 | 5 | [//]: # ( @section mainpage_intro Introduction ) 6 | ## Introduction 7 | 8 | This is an Arduino library for SDI-12 communication with a wide variety of environmental sensors. 9 | It 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. 10 | 11 | [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. 12 | [Advantages of SDI-12](http://en.wikipedia.org/wiki/SDI-12) include the ability to use a single available data channel for many sensors. 13 | 14 | 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/). 15 | 16 | [//]: # ( Start GitHub Only ) 17 | ## Documentation 18 | 19 | Extensive documentation on the SDI-12 functions and classes is available here: https://envirodiy.github.io/Arduino-SDI-12/index.html 20 | 21 | [//]: # ( End GitHub Only ) 22 | 23 | [//]: # ( @subsection mainpage_rename Renaming Notice ) 24 | ### Renaming Notice 25 | **As of version 2.0.0 this library was renamed from "Arduino-SDI-12" to simply "SDI-12" to comply with requirements for inclusion in the Arduino.cc's IDE and Library Manager.** 26 | 27 | [//]: # ( @tableofcontents ) 28 | 29 | [//]: # ( Start GitHub Only ) 30 | - [SDI-12 for Arduino](#sdi-12-for-arduino) 31 | - [Introduction](#introduction) 32 | - [Documentation](#documentation) 33 | - [Renaming Notice](#renaming-notice) 34 | - [Getting Started](#getting-started) 35 | - [Origins and Inherited Limitations](#origins-and-inherited-limitations) 36 | - [Compatibility Considerations](#compatibility-considerations) 37 | - [Variants and Branches](#variants-and-branches) 38 | - [EnviroDIY_SDI12](#envirodiy_sdi12) 39 | - [EnviroDIY_SDI12_PCINT3](#envirodiy_sdi12_pcint3) 40 | - [EnviroDIY_SDI12_ExtInts](#envirodiy_sdi12_extints) 41 | - [Contribute](#contribute) 42 | - [License](#license) 43 | - [Credits](#credits) 44 | 45 | [//]: # ( End GitHub Only ) 46 | 47 | [//]: # ( @section mainpage_getting_started Getting Started ) 48 | ## Getting Started 49 | 50 | Learn more, below, about this library's: 51 | * [Origins and Inherited Limitations](https://github.com/EnviroDIY/Arduino-SDI-12#origins-and-inherited-limitations); 52 | * [Compatibility Considerations](https://github.com/EnviroDIY/Arduino-SDI-12#compatibility-considerations); 53 | * [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) we created to overcome some limitations. 54 | 55 | Try running our [Example sketches](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples) with your Arduino board and SDI-12 sensor. 56 | 57 | Full details on the library functionality can be found on github pages: https://envirodiy.github.io/Arduino-SDI-12/ 58 | 59 | 60 | [//]: # ( @section mainpage_origins Origins and Inherited Limitations ) 61 | ## Origins and Inherited Limitations 62 | 63 | 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). 64 | 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). 65 | 66 | The most obvious "limitation" is that this library will conflict with all other libraries that make use of pin change interrupts. 67 | You will be unable to compile them together. 68 | 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. 69 | 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. 70 | 71 | 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. 72 | That includes other pin-change interrupts, clock/timer interrupts, external interrupts, and every other type of processor interrupt. 73 | This is particularly problematic for SDI-12, because SDI-12 operates at a very slow baud rate (only 1200 baud). 74 | 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. 75 | 76 | 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. 77 | 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. 78 | All 8MHz AVR boards will also have unresolvable prescaler conflicts with [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial). 79 | The pre-scaler values needed for the SDI-12 functionality are set in the begin() function and reset to their original values in the end() function. 80 | 81 | [//]: # ( @section mainpage_compatibility Compatibility Considerations ) 82 | ## Compatibility Considerations 83 | 84 | This library has been tested with an Arduino Uno (AtMega328p), EnviroDIY Mayfly (AtMega1284p), Adafruit Feather 32u4 (AtMega32u4, identical to Arduino Leonardo), an Adafruit Feather M0 (SAMD21G18, identical to Arduino Zero), the ESP8266, and the ESP32. 85 | It should also work on an Arduino Mega (AtMega2560), Gemma/AtTiny board, and most other AVR processors running on the Arduino framework. 86 | 87 | The Arduino Due, Arduino 101, and Teensy boards are not supported at this time. 88 | If you are interested in adding support for those boards, please send pull requests. 89 | 90 | Due to the use of pin change interrupts, not all data pins are available for use with this SDI-12 library. 91 | Pin availability depends on the micro-controller. 92 | These pins will work on those processors: 93 | 94 | This library requires the use of pin change interrupts (PCINT). 95 | 96 | Not all Arduino boards have the same pin capabilities. 97 | The known compatibile pins for common variants are shown below: 98 | 99 | **AtMega328p / Arduino Uno:** 100 | - Any pin 101 | 102 | **AtMega1284p / EnviroDIY Mayfly** 103 | - Any pin 104 | 105 | **ATmega2560 / Arduino Mega or Mega 2560:** 106 | - 0, 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) 107 | 108 | **AtMega32u4 / Arduino Leonardo or Adafruit Feather:** 109 | - 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI) 110 | 111 | **SAMD21G18 / Arduino Zero:** 112 | - Any pin (except 4 on the zero) 113 | 114 | **ESP8266:** 115 | - Any GPIO, except GPIO16 116 | 117 | **ESP32:** 118 | - Any GPIO 119 | 120 | 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. 121 | 122 | 123 | [//]: # ( @section mainpage_variants Variants and Branches ) 124 | ## Variants and Branches 125 | 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. 126 | To allow simultaneous use of SDI-12 and SoftwareSerial, we have created additional variants of these libraries that we maintain as separate branches of this repository. 127 | 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/). 128 | 129 | [//]: # ( @subsection mainpage_master EnviroDIY_SDI12 ) 130 | #### EnviroDIY_SDI12 131 | EnviroDIY_SDI12 is the default master branch of this repository. 132 | 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. 133 | 134 | [//]: # ( @subsection mainpage_pcint3 EnviroDIY_SDI12_PCINT3 ) 135 | #### EnviroDIY_SDI12_PCINT3 136 | EnviroDIY_SDI12_PCINT3 is in the Mayfly branch of this repository, and was historically was called "SDI12_mod". 137 | 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. 138 | 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). 139 | 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. 140 | 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). 141 | 142 | [//]: # ( @subsection mainpage_extints EnviroDIY_SDI12_ExtInts ) 143 | #### EnviroDIY_SDI12_ExtInts 144 | EnviroDIY_SDI12_ExtInts is the ExtInt branch of this repository. 145 | 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. 146 | 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. 147 | It's also the easiest to get working in combination with any other pin change interrupt based library. 148 | It can be paired with the [EnviroDIY_SoftwareSerial_ExtInts](https://github.com/EnviroDIY/SoftwareSerial_ExternalInts) libraries (which is, by the way, extremely unstable). 149 | 150 | 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. 151 | Then, in your own code call `SDI12::handleInterrupt()` as the interrupt for the SDI12 pin using the other interrupt library. 152 | Example j shows doing this in GreyGnome's [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) library. 153 | 154 | 155 | [//]: # ( @section mainpage_contribute Contribute ) 156 | ## Contribute 157 | Open an [issue](https://github.com/EnviroDIY/Arduino-SDI-12/issues) to suggest and discuss potential changes/additions. 158 | 159 | For power contributors: 160 | 161 | 1. Fork it! 162 | 2. Create your feature branch: `git checkout -b my-new-feature` 163 | 3. Commit your changes: `git commit -am 'Add some feature'` 164 | 4. Push to the branch: `git push origin my-new-feature` 165 | 5. Submit a pull request :D 166 | 167 | 168 | [//]: # ( @section mainpage_license License ) 169 | ## License 170 | The SDI12 library code is released under the GNU Lesser Public License (LGPL 2.1) -- See [LICENSE-examples.md](https://github.com/EnviroDIY/Arduino-SDI-12/blob/master/LICENSE) file for details. 171 | 172 | Example Arduino sketches are released under the BSD 3-Clause License -- See [LICENSE-examples.md](https://github.com/EnviroDIY/Arduino-SDI-12/blob/master/LICENSE.md) file for details. 173 | 174 | Documentation is licensed as [Creative Commons Attribution-ShareAlike 4.0](https://creativecommons.org/licenses/by-sa/4.0/) (CC-BY-SA) copyright. 175 | 176 | [//]: # ( @section mainpage_credits Credits ) 177 | ## Credits 178 | [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. 179 | 180 | [Kevin M. Smith](https://github.com/Kevin-M-Smith) was the primary developer of the SDI-12 library, with input from [S. Hicks](https://github.com/s-hicks2) and [Anthony Aufdenkampe](https://github.com/aufdenkampe). 181 | 182 | [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). 183 | 184 | This project has benefited from the support from the following funders: 185 | 186 | * 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) 187 | * William Penn Foundation, grant 158-15 188 | * Stroud Water Research Center endowment 189 | -------------------------------------------------------------------------------- /src/SDI12_boards.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SDI12_boards.h 3 | * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) 4 | * and the EnviroDIY Development Team 5 | * @author Sara Geleskie Damiano (sdamiano@stroudcenter.org) 6 | * 7 | * @brief This file defines the timing units needed for various Arduino boards. 8 | * 9 | */ 10 | 11 | /* ======================== Arduino SDI-12 ================================= 12 | An Arduino library for SDI-12 communication with a wide variety of environmental 13 | sensors. This library provides a general software solution, without requiring 14 | ======================== Arduino SDI-12 =================================*/ 15 | 16 | #ifndef SRC_SDI12_BOARDS_H_ 17 | #define SRC_SDI12_BOARDS_H_ 18 | 19 | #include 20 | 21 | #if defined(ESP32) || defined(ESP8266) 22 | /** The interger type (size) of the timer return value */ 23 | typedef uint32_t sdi12timer_t; 24 | #else 25 | /** The interger type (size) of the timer return value */ 26 | typedef uint8_t sdi12timer_t; 27 | #endif 28 | 29 | /** 30 | * @brief The class used to define the processor timer for the SDI-12 serial emulation. 31 | */ 32 | class SDI12Timer { 33 | public: 34 | /** 35 | * @brief Construct a new SDI12Timer 36 | */ 37 | SDI12Timer(); 38 | /** 39 | * @brief Set the processor timer prescaler such that the 10 bits of an SDI-12 40 | * character are divided into the rollover time of the timer. 41 | * 42 | * @note The ESP32 and ESP8266 are fast enough processors that they can take the 43 | * time to read the core 'micros()' function still complete the other processing 44 | * needed on the serial bits. All of the other processors using the Arduino core also 45 | * have the micros function, but the rest are not fast enough to waste the processor 46 | * cycles to use the micros function and must manually configure the processor timer 47 | * and use the faster assembly macros to read that processor timer directly. 48 | */ 49 | void configSDI12TimerPrescale(void); 50 | /** 51 | * @brief Reset the processor timer prescaler to whatever it was prior to being 52 | * adjusted for this library. 53 | * 54 | * @note The prescaler is *NOT* set back to initial values for SAMD boards! On those 55 | * processors, generic clock generator 4 will remain configured for SDI-12 until it is 56 | * reset outside of this library. 57 | */ 58 | void resetSDI12TimerPrescale(void); 59 | 60 | // Most 'standard' AVR boards 61 | // 62 | #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || \ 63 | defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || \ 64 | defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || \ 65 | defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) 66 | 67 | /** 68 | * @brief A string description of the timer to use 69 | */ 70 | #define TIMER_IN_USE_STR "TCNT2" 71 | /** 72 | * @brief The c macro name for the assembly timer to use 73 | */ 74 | #define TCNTX TCNT2 // Using Timer 2 75 | 76 | #if F_CPU == 16000000L 77 | /** 78 | * @brief A string description of the prescaler in use. 79 | */ 80 | #define PRESCALE_IN_USE_STR "1024" 81 | /** 82 | * @brief The number of "ticks" of the timer that occur within the timing of one bit at 83 | * the SDI-12 baud rate of 1200 bits/second. 84 | * 85 | * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' 86 | * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit 87 | */ 88 | #define TICKS_PER_BIT 13 89 | /** 90 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 91 | * 92 | * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 93 | */ 94 | #define BITS_PER_TICK_Q10 79 95 | /** 96 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 97 | * uneven tick increments get rounded up. 98 | * 99 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 100 | */ 101 | #define RX_WINDOW_FUDGE 2 102 | 103 | #elif F_CPU == 12000000L 104 | /** 105 | * @brief A string description of the prescaler in use. 106 | */ 107 | #define PRESCALE_IN_USE_STR "1024" 108 | /** 109 | * @brief The number of "ticks" of the timer that occur within the timing of one bit at 110 | * the SDI-12 baud rate of 1200 bits/second. 111 | * 112 | * 12MHz / 1024 prescaler = 11719 'ticks'/sec = 85 µs / 'tick' 113 | * (1 sec/1200 bits) * (1 tick/85 µs) = 9.765625 ticks/bit 114 | */ 115 | #define TICKS_PER_BIT 10 116 | /** 117 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 118 | * 119 | * 1/(9.765625 ticks/bit) * 2^10 = 104.8576 120 | */ 121 | #define BITS_PER_TICK_Q10 105 122 | /** 123 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 124 | * uneven tick increments get rounded up. 125 | * 126 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 127 | */ 128 | #define RX_WINDOW_FUDGE 2 129 | 130 | #elif F_CPU == 8000000L 131 | /** 132 | * @brief A string description of the prescaler in use. 133 | */ 134 | #define PRESCALE_IN_USE_STR "256" 135 | /** 136 | * @brief The number of "ticks" of the timer that occur within the timing of one bit at 137 | * the SDI-12 baud rate of 1200 bits/second. 138 | * 139 | * 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' 140 | * (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit 141 | */ 142 | #define TICKS_PER_BIT 26 143 | /** 144 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 145 | * 146 | * 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 147 | */ 148 | #define BITS_PER_TICK_Q10 39 149 | /** 150 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 151 | * uneven tick increments get rounded up. 152 | * 153 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 154 | */ 155 | #define RX_WINDOW_FUDGE 10 156 | 157 | // #define PRESCALE_IN_USE_STR "1024" 158 | // #define TICKS_PER_BIT 6 159 | // // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' 160 | // // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit 161 | // #define BITS_PER_TICK_Q10 157 162 | // // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 163 | // #define RX_WINDOW_FUDGE 5 164 | 165 | #endif 166 | 167 | 168 | // ATtiny boards (ie, adafruit trinket) 169 | // 170 | #elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) 171 | 172 | /** 173 | * @brief A string description of the timer to use 174 | */ 175 | #define TIMER_IN_USE_STR "TCNT1" 176 | /** 177 | * @brief The c macro name for the assembly timer to use 178 | */ 179 | #define TCNTX TCNT1 // Using Timer 1 180 | 181 | #if F_CPU == 16000000L 182 | /** 183 | * @brief A string description of the prescaler in use. 184 | */ 185 | #define PRESCALE_IN_USE_STR "1024" 186 | /** 187 | * @brief The number of "ticks" of the timer that occur within the timing of one bit at 188 | * the SDI-12 baud rate of 1200 bits/second. 189 | * 190 | * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' 191 | * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit 192 | */ 193 | #define TICKS_PER_BIT 13 194 | /** 195 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 196 | * 197 | * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 198 | */ 199 | #define BITS_PER_TICK_Q10 79 200 | /** 201 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 202 | * uneven tick increments get rounded up. 203 | * 204 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 205 | */ 206 | #define RX_WINDOW_FUDGE 2 207 | 208 | #elif F_CPU == 8000000L 209 | #define PRESCALE_IN_USE_STR "512" 210 | /** 211 | * @brief The number of "ticks" of the timer that occur within the timing of one bit at 212 | * the SDI-12 baud rate of 1200 bits/second. 213 | * 214 | * 8MHz / 512 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' 215 | * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit 216 | */ 217 | #define TICKS_PER_BIT 13 218 | /** 219 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 220 | * 221 | * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 222 | */ 223 | #define BITS_PER_TICK_Q10 79 224 | /** 225 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 226 | * uneven tick increments get rounded up. 227 | * 228 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 229 | */ 230 | #define RX_WINDOW_FUDGE 5 231 | 232 | #endif 233 | 234 | 235 | // Arduino Leonardo & Yun and other 32U4 boards 236 | // 237 | #elif defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_LEONARDO) || \ 238 | defined(__AVR_ATmega32U4__) 239 | 240 | /** 241 | * @brief A string description of the timer to use 242 | */ 243 | #define TIMER_IN_USE_STR "TCNT4" 244 | /** 245 | * @brief The c macro name for the assembly timer to use 246 | */ 247 | #define TCNTX TCNT4 // Using Timer 4 248 | 249 | #if F_CPU == 16000000L 250 | /** 251 | * @brief A string description of the prescaler in use. 252 | */ 253 | #define PRESCALE_IN_USE_STR "1024" 254 | /** 255 | * @brief The number of "ticks" of the timer that occur within the timing of one bit at 256 | * the SDI-12 baud rate of 1200 bits/second. 257 | * 258 | * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' 259 | * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit 260 | */ 261 | #define TICKS_PER_BIT 13 262 | /** 263 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 264 | * 265 | * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 266 | */ 267 | #define BITS_PER_TICK_Q10 79 268 | /** 269 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 270 | * uneven tick increments get rounded up. 271 | * 272 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 273 | */ 274 | #define RX_WINDOW_FUDGE 2 275 | 276 | #elif F_CPU == 8000000L 277 | /** 278 | * @brief A string description of the prescaler in use. 279 | */ 280 | #define PRESCALE_IN_USE_STR "512" 281 | /** 282 | * @brief The number of "ticks" of the timer that occur within the timing of one bit 283 | * at the SDI-12 baud rate of 1200 bits/second. 284 | * 285 | * 8MHz / 512 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' 286 | * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit 287 | */ 288 | #define TICKS_PER_BIT 13 289 | /** 290 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 291 | * 292 | * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 293 | */ 294 | #define BITS_PER_TICK_Q10 79 295 | /** 296 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 297 | * uneven tick increments get rounded up. 298 | * 299 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 300 | */ 301 | #define RX_WINDOW_FUDGE 5 302 | 303 | #endif 304 | 305 | 306 | // Arduino Zero other SAMD21 boards 307 | // 308 | #elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || \ 309 | defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__SAMD21E18A__) 310 | 311 | /** 312 | * @brief A string description of the timer to use 313 | */ 314 | #define TIMER_IN_USE_STR "GCLK4-TC3" 315 | /** 316 | * @brief The c macro name for the assembly timer to use 317 | */ 318 | #define TCNTX REG_TC3_COUNT8_COUNT // Using Timer 3 with generic clock 4 319 | 320 | /** 321 | * @brief A string description of the prescaler in use. 322 | */ 323 | #define PRESCALE_IN_USE_STR "3x1024" 324 | /** 325 | * @brief The number of "ticks" of the timer that occur within the timing of one bit at 326 | * the SDI-12 baud rate of 1200 bits/second. 327 | * 328 | * 48MHz / 3 pre-prescaler = 16MHz 329 | * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' 330 | * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit 331 | */ 332 | #define TICKS_PER_BIT 13 333 | /** 334 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 335 | * 336 | * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 337 | */ 338 | #define BITS_PER_TICK_Q10 79 339 | /** 340 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 341 | * uneven tick increments get rounded up. 342 | * 343 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 344 | */ 345 | #define RX_WINDOW_FUDGE 2 346 | 347 | // Espressif ESP32/ESP8266 boards 348 | // 349 | #elif defined(ESP32) || defined(ESP8266) 350 | /** 351 | * @brief Read the processor micros and right shift 6 bits (ie, divide by 64) to get a 352 | * 64µs tick. 353 | * 354 | * @note The ESP32 and ESP8266 are fast enough processors that they can take the time 355 | * to read the core 'micros()' function still complete the other processing needed on 356 | * the serial bits. All of the other processors using the Arduino core also have the 357 | * micros function, but the rest are not fast enough to waste the processor cycles to 358 | * use the micros function and must use the faster assembly macros to read the 359 | * processor timer directly. 360 | * 361 | * @return **sdi12timer_t** The current processor micros 362 | */ 363 | sdi12timer_t SDI12TimerRead(void); 364 | 365 | /** 366 | * @brief The number of "ticks" of the timer that occur within the timing of one bit 367 | * at the SDI-12 baud rate of 1200 bits/second. 368 | * 369 | * 48MHz / 3 pre-prescaler = 16MHz 370 | * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' 371 | * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit 372 | */ 373 | #define TICKS_PER_BIT 13 374 | /** 375 | * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. 376 | * 377 | * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 378 | */ 379 | #define BITS_PER_TICK_Q10 79 380 | /** 381 | * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that 382 | * uneven tick increments get rounded up. 383 | * 384 | * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 385 | */ 386 | #define RX_WINDOW_FUDGE 2 387 | 388 | // Unknown board 389 | #else 390 | #error "Please define your board timer and pins" 391 | #endif 392 | }; 393 | 394 | #endif // SRC_SDI12_BOARDS_H_ 395 | -------------------------------------------------------------------------------- /docs/CreatingACharacter.md: -------------------------------------------------------------------------------- 1 | [//]: # ( @page rx_page Creating a Character - Stepping through the Rx ISR ) 2 | # Creating a Character - Stepping through the Rx ISR 3 | 4 | [//]: # ( @tableofcontents ) 5 | 6 | [//]: # ( Start GitHub Only ) 7 | - [Creating a Character - Stepping through the Rx ISR](#creating-a-character---stepping-through-the-rx-isr) 8 | - [How a Character Looks in SDI-12](#how-a-character-looks-in-sdi-12) 9 | - [Static Variables we Need](#static-variables-we-need) 10 | - [Following the Mask](#following-the-mask) 11 | - [Waiting for a Start Bit](#waiting-for-a-start-bit) 12 | - [The Start of a Character](#the-start-of-a-character) 13 | - [The Interrupt Fires!](#the-interrupt-fires) 14 | - [Bit by Bit](#bit-by-bit) 15 | - [A LOW/1 Bit](#a-low1-bit) 16 | - [A HIGH/0 Bit](#a-high0-bit) 17 | - [Shifting Up](#shifting-up) 18 | - [A Finished Character](#a-finished-character) 19 | - [The Full Interrupt Function](#the-full-interrupt-function) 20 | 21 | [//]: # ( End GitHub Only ) 22 | 23 | Here we'll walk step-by-step through how the SDI-12 library (and NeoSWSerial) create a character from the ISR. 24 | Unlike SoftwareSerial which listens for a start bit and then halts all program and other ISR execution until the end of the character, this library grabs the time of the interrupt, does some quick math, and lets the processor move on. 25 | The logic of creating a character this way is harder for a person to follow, but it pays off because we're not tieing up the processor in an ISR that lasts for 8.33ms for each character. 26 | [10 bits @ 1200 bits/s] 27 | For a person, that 8.33ms is trivial, but for even a "slow" 8MHz processor, that's over 60,000 ticks sitting idle per character. 28 | 29 | So, let's look at what's happening. 30 | 31 | [//]: # ( @section rx_specs How a Character Looks in SDI-12 ) 32 | ## How a Character Looks in SDI-12 33 | 34 | First we need to keep in mind the specifications of SDI-12: 35 | - We use *inverse logic* that means a "1" bit is at LOW level and a "0" bit is HIGH level. 36 | - characters are sent as 10 bits 37 | - 1 start bit, which is always a 0/HIGH 38 | - 7 data bits 39 | - 1 parity bit 40 | - 1 stop bit, which is always 1/LOW 41 | 42 | [//]: # ( @section rx_vars Static Variables we Need ) 43 | ## Static Variables we Need 44 | 45 | And lets remind ourselves of the static variables we're using to store states: 46 | - `prevBitTCNT` stores the time of the previous RX transition in micros 47 | - `rxState` tracks how many bits are accounted for on an incoming character. 48 | - if 0: indicates that we got a start bit 49 | - if >0: indicates the number of bits received 50 | - `WAITING-FOR-START-BIT` is a mask for the rxState while waiting for a start bit, it's set to 0b11111111 51 | - `rxMask` is a bit mask for building a received character 52 | - The mask has a single bit set, in the place of the active bit based on the rxState 53 | - `rxValue` is the value of the character being built 54 | 55 | [//]: # ( @section rx_mask Following the Mask ) 56 | ## Following the Mask 57 | 58 | [//]: # ( @subsection rx_mask_wait Waiting for a Start Bit ) 59 | ### Waiting for a Start Bit 60 | 61 | The `rxState`, `rxMask`, and `rxValue` all work together to form a character. 62 | When we're waiting for a start bit `rxValue` is empty, `rxMask` has only the bottom bit set, and `rxState` is set to WAITING-FOR-START-BIT: 63 | 64 | ``` 65 | rxValue: | 0 0 0 0 0 0 0 0 66 | -------------|----------------------------------- 67 | rxMask: | 0 0 0 0 0 0 0 1 68 | rxState: | 1 1 1 1 1 1 1 1 69 | ``` 70 | 71 | 72 | [//]: # ( @subsection rx_mask_start The Start of a Character ) 73 | ### The Start of a Character 74 | 75 | After we get a start bit, the `startChar()` function creates a blank slate for the new character, so our values are: 76 | 77 | ``` 78 | rxValue: | 0 0 0 0 0 0 0 0 79 | -------------|----------------------------------- 80 | rxMask: | 0 0 0 0 0 0 0 1 81 | rxState: | 0 0 0 0 0 0 0 0 82 | ``` 83 | 84 | 85 | [//]: # ( @subsection rx_mask_fire The Interrupt Fires! ) 86 | ### The Interrupt Fires! 87 | 88 | When an interrupts is received, we use capture the time if the interrupt in `thisBitTCNT`. 89 | Then we subtract `prevBitTCNT` from `thisBitTCNT` and use the `bitTimes()` function to calculate how many bit-times have passed between this interrupt and the previous one. 90 | (There's also a fudge factor in this calculation we call the [rxWindowWidth](https://github.com/SlashDevin/NeoSWSerial/pull/13#issuecomment-315463522).) 91 | 92 | 93 | [//]: # ( @subsection rx_mask_bit Bit by Bit ) 94 | ### Bit by Bit 95 | 96 | For **each bit time that passed**, we apply the `rxMask` to the `rxValue`. 97 | - Keep in mind multiple bit times can pass between interrupts - this happens any time there are two (or more) high or low bits in a row. 98 | - We also leave time for the (high) start and (low) stop bit, but do anything with the `rxState`, `rxMask`, or `rxValue` for those bits. 99 | 100 | 101 | [//]: # ( @subsubsection rx_mask_low A LOW/1 Bit ) 102 | #### A LOW/1 Bit 103 | 104 | - if the data bit received is LOW (1) we do an `|=` (bitwise OR) between the `rxMask` and the `rxValue` 105 | 106 | ``` 107 | rxValue: | 0 0 0 0 0 0 0 1 108 | -------------|---------------------------------^- bit-wise or puts the one 109 | rxMask: | 0 0 0 0 0 0 0 1 from the rxMask into 110 | rxState: | 0 0 0 0 0 0 0 0 the rxValue 111 | ``` 112 | 113 | 114 | [//]: # ( @subsubsection rx_mask_high A HIGH/0 Bit ) 115 | #### A HIGH/0 Bit 116 | 117 | - if the data bit received is HIGH (0) we do nothing 118 | 119 | ``` 120 | rxValue: | 0 0 0 0 0 0 0 0 121 | -------------|---------------------------------x- nothing happens 122 | rxMask: | 0 0 0 0 0 0 0 1 123 | rxState: | 0 0 0 0 0 0 0 0 124 | ``` 125 | 126 | 127 | [//]: # ( @subsubsection rx_mask_shift Shifting Up ) 128 | #### Shifting Up 129 | 130 | - *After* applying the mask, we push everything over one bit to the left. 131 | The top bit falls off. 132 | - we always add a 1 on the `rxState`, to indicate the bit arrived 133 | - we always add a 0 on the `rxMask` and the `rxValue` 134 | - the values of the second bit of the `rxValue` (?) depends on what we did in the step above 135 | 136 | ``` 137 | rxValue: | 0 <--- | 0 0 0 0 0 0 ? 0 <--- add a zero 138 | -------------|-------------------|---------------------------|--- 139 | rxMask: | 0 <--- | 0 0 0 0 0 0 1 0 <--- add a zero 140 | rxState: | 0 <--- | 0 0 0 0 0 0 0 1 <--- add a one 141 | -------------|-------------------|---------------------------|--- 142 | | falls off the top | | added to the bottom 143 | ``` 144 | 145 | 146 | [//]: # ( @subsection rx_mask_fin A Finished Character ) 147 | ### A Finished Character 148 | 149 | After 8 bit times have passed, we should have a fully formed character with 8 bits of data (7 of the character + 1 parity). 150 | The `rxMask` will have the one in the top bit. 151 | And the rxState will be filled - which just happens to be the value of `WAITING-FOR-START-BIT` for the next character. 152 | 153 | ``` 154 | rxValue: | ? ? ? ? ? ? ? ? 155 | -------------|----------------------------------- 156 | rxMask: | 1 0 0 0 0 0 0 0 157 | rxState: | 1 1 1 1 1 1 1 1 158 | ``` 159 | 160 | 161 | [//]: # ( @section rx_fxn The Full Interrupt Function ) 162 | ## The Full Interrupt Function 163 | 164 | Understanding how the masking creates the character, you should now be able to follow the full interrupt function below. 165 | 166 | ```cpp 167 | // Creates a blank slate of bits for an incoming character 168 | void SDI12::startChar() { 169 | rxState = 0x00; // 0b00000000, got a start bit 170 | rxMask = 0x01; // 0b00000001, bit mask, lsb first 171 | rxValue = 0x00; // 0b00000000, RX character to be, a blank slate 172 | } // startChar 173 | 174 | // The actual interrupt service routine 175 | void SDI12::receiveISR() { 176 | // time of this data transition (plus ISR latency) 177 | sdi12timer-t thisBitTCNT = READTIME; 178 | 179 | uint8-t pinLevel = digitalRead(-dataPin); // current RX data level 180 | 181 | // Check if we're ready for a start bit, and if this could possibly be it. 182 | if (rxState == WAITING-FOR-START-BIT) { 183 | // If we are waiting for a start bit and the pin is low it's not a start bit, exit 184 | // Inverse logic start bit = HIGH 185 | if (pinLevel == LOW) { return; } 186 | // If the pin is HIGH, this should be a start bit. 187 | // Thus startChar(), which sets the rxState to 0, create an empty character, and a 188 | // new mask with a 1 in the lowest place 189 | startChar(); 190 | } else { 191 | // If we're not waiting for a start bit, it's because we're in the middle of an 192 | // incomplete character and therefore this change in the pin state must be from a 193 | // data, parity, or stop bit. 194 | 195 | // Check how many bit times have passed since the last change 196 | uint16-t rxBits = bitTimes((uint8-t)(thisBitTCNT - prevBitTCNT)); 197 | // Calculate how many *data+parity* bits should be left in the current character 198 | // - Each character has a total of 10 bits, 1 start bit, 7 data bits, 1 parity 199 | // bit, and 1 stop bit 200 | // - The #rxState holds record of how many of the data + parity bits we've 201 | // gotten (up to 8) 202 | // - We have to treat the parity bit as a data bit because we don't know its 203 | // state 204 | // - Since we're mid character, we know the start bit is past which knocks us 205 | // down to 9 206 | // - There will always be one left over for the stop bit, which will be LOW/1 207 | uint8-t bitsLeft = 9 - rxState; 208 | // If the number of bits passed since the last transition is more than then number 209 | // of bits left on the character we were working on, a new character must have 210 | // started. 211 | // This will happen if the parity bit is 1 or the last bit(s) of the character and 212 | // the parity bit are all 1's. 213 | bool nextCharStarted = (rxBits > bitsLeft); 214 | 215 | // Check how many data+parity bits have been sent in this frame. This will be 216 | // different from the rxBits if a new character has started because of the start 217 | // and stop bits. 218 | // - If the total number of bits in this frame is more than the number of 219 | // data+parity bits remaining in the character, then the number of data+parity bits 220 | // is equal to the number of bits remaining for the character and partiy. 221 | // - If the total number of bits in this frame is less than the number of data 222 | // bits left for the character and parity, then the number of data+parity bits 223 | // received in this frame is equal to the total number of bits received in this 224 | // frame. 225 | // translation: 226 | // if nextCharStarted then bitsThisFrame = bitsLeft 227 | // else bitsThisFrame = rxBits 228 | uint8-t bitsThisFrame = nextCharStarted ? bitsLeft : rxBits; 229 | // Tick up the rxState by the number of data+parity bits received in the frame 230 | rxState += bitsThisFrame; 231 | 232 | // Set all the bits received between the last change and this change 233 | if (pinLevel == HIGH) { 234 | // If the current state is HIGH (and it just became so), then all bits between 235 | // the last change and now must have been LOW. 236 | // back fill previous bits with 1's (inverse logic - LOW = 1) 237 | while (bitsThisFrame-- > 0) { 238 | // for each of the bits that happened in this frame 239 | 240 | rxValue |= rxMask; // Add a 1 to the LSB/right-most place of our character 241 | // value from the mask 242 | rxMask = rxMask << 1; // Shift the 1 in the mask up by one position 243 | } 244 | // And shift the 1 in the mask up by one more position for the current bit. 245 | // It's HIGH/0 now, so we don't use `|=` with the mask for this last one. 246 | rxMask = rxMask << 1; 247 | } else { 248 | // If the current state is LOW (and it just became so), then this bit is LOW 249 | // but all bits between the last change and now must have been HIGH 250 | 251 | // pinLevel==LOW 252 | // previous bits were 0's so only this bit is a 1 (inverse logic - LOW = 1) 253 | rxMask = rxMask << (bitsThisFrame - 254 | 1); // Shift the 1 in the mask up by the number of bits past 255 | rxValue |= rxMask; // And add that shifted one to the character being created 256 | } 257 | 258 | // If this was the 8th or more bit then the character and parity are complete. 259 | if (rxState > 7) { 260 | rxValue &= 0x7F; // Throw away the parity bit (and with 0b01111111) 261 | charToBuffer(rxValue); // Put the finished character into the buffer 262 | 263 | 264 | // if this is LOW, or we haven't exceeded the number of bits in a 265 | // character (but have gotten all the data bits) then this should be a 266 | // stop bit and we can start looking for a new start bit. 267 | if ((pinLevel == LOW) || !nextCharStarted) { 268 | rxState = WAITING-FOR-START-BIT; // DISABLE STOP BIT TIMER 269 | } else { 270 | // If we just switched to HIGH, or we've exceeded the total number of 271 | // bits in a character, then the character must have ended with 1's/LOW, 272 | // and this new 0/HIGH is actually the start bit of the next character. 273 | startChar(); 274 | } 275 | } 276 | } 277 | prevBitTCNT = thisBitTCNT; // finally remember time stamp of this change! 278 | } 279 | 280 | // Put a new character in the buffer 281 | void SDI12::charToBuffer(uint8-t c) { 282 | // Check for a buffer overflow. If not, proceed. 283 | if ((-rxBufferTail + 1) % SDI12-BUFFER-SIZE == -rxBufferHead) { 284 | -bufferOverflow = true; 285 | } else { 286 | // Save the character, advance buffer tail. 287 | -rxBuffer[-rxBufferTail] = c; 288 | -rxBufferTail = (-rxBufferTail + 1) % SDI12-BUFFER-SIZE; 289 | } 290 | } 291 | ``` --------------------------------------------------------------------------------