diff --git a/.gitattributes b/.gitattributes index 7c8fffe..bf3d914 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,7 +15,8 @@ # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary - +*.pdf binary +*.pdf export-ignore # used to exclude files from archiving/compression diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0f88bec..7318fd3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ updates: - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: 'daily' + interval: 'weekly' labels: - 'CI/CD' commit-message: diff --git a/.github/workflows/build_documentation.yaml b/.github/workflows/build_documentation.yaml index 3b3cfe6..c834db0 100644 --- a/.github/workflows/build_documentation.yaml +++ b/.github/workflows/build_documentation.yaml @@ -1,111 +1,39 @@ -name: Build and Publish Documentation +name: Check, Build, and Publish Documentation on: - # Trigger the workflow on push or pull request, - # but only for the main branch + # Triggers the workflow on push or pull request events push: - branches: - - master - # Also trigger on page_build, as well as release created events - page_build: + pull_request: + # Trigger when a release is created + # NOTE: This will only trigger if the release is created from the UI or with a personal access token release: - types: # This configuration does not affect the page_build event above - - created - -env: - DOXYGEN_VERSION: Release_1_9_1 + types: + - published + # Trigger with the release workflow finishes + workflow_run: + workflows: ['Create a New Release'] + types: [completed] + branches: [master] + # Also give a manual trigger + workflow_dispatch: + inputs: + publish: + description: 'Publish Documentation to GitHub Pages' + required: false + type: boolean + default: false + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - build: - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, 'ci skip')" - - steps: - # check out the Arduino-SDI-12 repo - - uses: actions/checkout@v4 - with: - path: code_docs/Arduino-SDI-12 - - - name: Restore or Cache pip - uses: actions/cache@v4.0.2 - id: cache_pip - with: - path: ~/.cache/pip - # if requirements.txt hasn't changed, then it will be a "cache hit" and pip will be restored - # 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 - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: ${{ runner.os }}-pip- - - - name: Restore or Cache PlatformIO and Libraries - uses: actions/cache@v4.0.2 - id: cache_pio - with: - path: ~/.platformio - # if nothing in the lock files has changed, then it will be a "cache hit" - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - # This should be pulled from cache, if there's not a new version - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - # Install *all* the dependencies! - # We're including the dependencies just so the includes can follow in the doxygen pre-processor - - name: Install the dependencies at global level - run: | - echo "::debug::Installing greygnome/EnableInterrupt" - pio lib -g install greygnome/EnableInterrupt - - - name: Update Libraries from Cache - run: pio lib -g update - - - name: Install GraphViz (dot) - run: sudo apt-get -y install graphviz - - - name: Restore or Cache Doxygen - id: cache_doxygen - uses: actions/cache@v4.0.2 - with: - path: doxygen-src - key: ${{ runner.os }}-doxygen-${{ env.DOXYGEN_VERSION }} - - - name: Clone and build doxygen - if: steps.cache_doxygen.outputs.cache-hit != 'true' - env: - TRAVIS_BUILD_DIR: ${{ github.workspace }} - run: | - cd ${{ github.workspace }}/code_docs/Arduino-SDI-12/ - chmod +x continuous_integration/build-install-doxygen.sh - sh continuous_integration/build-install-doxygen.sh - - # This should be pulled from cache, if there's not a new version - - name: Install Pygments and other m.css requirements - run: pip3 install jinja2 Pygments beautifulsoup4 - - # check out my fork of m.css, for processing Doxygen output - - name: Checkout m.css - uses: actions/checkout@v4 - with: - # Repository name with owner. For example, actions/checkout - repository: SRGDamia1/m.css - path: code_docs/m.css - - - name: Generate all the documentation - env: - TRAVIS_BUILD_DIR: ${{ github.workspace }} - run: | - cd ${{ github.workspace }}/code_docs/Arduino-SDI-12/ - chmod +x continuous_integration/generate-documentation.sh - sh continuous_integration/generate-documentation.sh - - - name: Deploy to github pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ${{ github.workspace }}/code_docs/Arduino-SDI-12Doxygen/m.css + doc_build: + if: ${{ (! contains(github.event.head_commit.message, 'ci skip')) && (github.event_name != 'workflow_run' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')) }} + name: Build documentation + uses: EnviroDIY/workflows/.github/workflows/build_documentation.yaml@main + with: + use_graphviz: false + publish: ${{ (github.event_name == 'release' && github.event.action == 'published') || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true')}} + rebuild_cache_number: 1 + secrets: inherit diff --git a/.github/workflows/build_examples.yaml b/.github/workflows/build_examples.yaml index d867bff..ba96fc9 100644 --- a/.github/workflows/build_examples.yaml +++ b/.github/workflows/build_examples.yaml @@ -3,88 +3,37 @@ name: Build Examples # Triggers the workflow on push or pull request events on: [push, pull_request] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - build: + build_examples: + name: Build standard examples with PlatformIO and the Arduino CLI + if: ${{ ! contains(github.event.head_commit.message, 'ci skip') }} + uses: EnviroDIY/workflows/.github/workflows/build_examples.yaml@main + with: + boards_to_build: 'mayfly,uno,megaatmega2560,leonardo,zeroUSB,arduino_nano_esp32,feather328p,feather32u4,adafruit_feather_m0,adafruit_feather_m4,huzzah,featheresp32,esp32-c3-devkitm-1,esp32-s3-devkitm-1' + examples_to_build: 'examples/a_wild_card,examples/b_address_change,examples/c_check_all_addresses,examples/d_simple_logger,examples/e_continuous_measurement,examples/f_basic_data_request,examples/g_terminal_window,examples/h_SDI-12_slave_implementation,examples/i_SDI-12_interface,examples/k_concurrent_logger,examples/l_verify_crc' + secrets: inherit + + + build_ext_ints: + name: Build the External Interrupt Example runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, 'ci skip')" - - strategy: - matrix: - example: - [ - examples/a_wild_card/, - examples/b_address_change/, - examples/c_check_all_addresses/, - examples/d_simple_logger/, - examples/e_continuous_measurement/, - examples/f_basic_data_request/, - examples/g_terminal_window/, - examples/h_SDI-12_slave_implementation/, - examples/i_SDI-12_interface/, - examples/j_external_pcint_library/, - examples/k_concurrent_logger/, - ] + if: ${{ ! contains(github.event.head_commit.message, 'ci skip') }} + env: + PLATFORMIO_BUILD_CACHE_DIR: ~/.platformio/caches steps: - - uses: actions/checkout@v4 - - - name: Set variables - run: | - if [[ -z "${GITHUB_HEAD_REF}" ]]; then - echo "::debug::Push to commit ${GITHUB_SHA}" - echo "LIBRARY_INSTALL_SOURCE=https://github.com/${GITHUB_REPOSITORY}.git#${GITHUB_SHA}" >> $GITHUB_ENV - else - echo "::debug::Pull Request from the ${GITHUB_HEAD_REF} branch" - echo "LIBRARY_INSTALL_SOURCE=https://github.com/${GITHUB_REPOSITORY}.git#${GITHUB_HEAD_REF}" >> $GITHUB_ENV - fi - - - name: Restore or Cache pip - uses: actions/cache@v4.0.2 - with: - path: ~/.cache/pip - # if requirements.txt hasn't changed, then it will be a "cache hit" and pip will be restored - # 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 - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: ${{ runner.os }}-pip- + - name: Checkout code + uses: actions/checkout@v4 - - name: Restore or Cache PlatformIO and Libraries - uses: actions/cache@v4.0.2 - with: - path: ~/.platformio - # if nothing in the lock files has changed, then it will be a "cache hit" and pip will be restored - # otherwise, it will be a "cache miss" and a new cache of libraries will be created if the job completes successfully - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - # This should be pulled from cache, if there's not a new version - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - - name: Run PlatformIO - if: matrix.example != 'examples/j_external_pcint_library/' - env: - PLATFORMIO_CI_SRC: ${{ matrix.example }} - run: | - echo "${{ env.LIBRARY_INSTALL_SOURCE }}" - pio lib --global install ${{ env.LIBRARY_INSTALL_SOURCE }} - pio lib --global install EnableInterrupt - platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 --board=huzzah --board=featheresp32 - pio lib --global uninstall SDI-12 + - name: Setup PlatformIO + uses: EnviroDIY/setup-platformio-action@v1.0.2 - - name: Run PlatformIO - if: matrix.example == 'examples/j_external_pcint_library/' + - name: Build PlatformIO examples env: - PLATFORMIO_CI_SRC: ${{ matrix.example }} - PLATFORMIO_BUILD_FLAGS: -DSDI12_EXTERNAL_PCINT - run: | - echo "${{ env.LIBRARY_INSTALL_SOURCE }}" - pio lib --global install ${{ env.LIBRARY_INSTALL_SOURCE }} - pio lib --global install EnableInterrupt - platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 - pio lib --global uninstall SDI-12 + PLATFORMIO_BUILD_FLAGS: -D SDI12_EXTERNAL_PCINT + PLATFORMIO_CI_SRC: examples/j_external_pcint_library + run: pio ci --board=mayfly --board=uno --board=megaatmega2560 --board=leonardo --board=zeroUSB --board=feather328p --board=feather32u4 --board=adafruit_feather_m0 --lib="." --project-option="lib_deps=greygnome/EnableInterrupt@^1.1.0" diff --git a/.github/workflows/changelog_reminder.yaml b/.github/workflows/changelog_reminder.yaml index a1b4115..cf35380 100644 --- a/.github/workflows/changelog_reminder.yaml +++ b/.github/workflows/changelog_reminder.yaml @@ -12,7 +12,7 @@ jobs: - name: Changelog Reminder uses: peterjgrainger/action-changelog-reminder@v1.3.0 with: - changelog_regex: '/ChangeLog\/.*\/*.md' + changelog_regex: '/CHANGELOG\/.*\/*.md/i' customPrMessage: 'Please add your changes to the change log!' env: - GITHUB_TOKEN: ${{ secrets.SARA_PUSH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/prepare_release.yaml b/.github/workflows/prepare_release.yaml index 0643f7b..62f618b 100644 --- a/.github/workflows/prepare_release.yaml +++ b/.github/workflows/prepare_release.yaml @@ -7,68 +7,43 @@ on: - 'VERSION' # Push events when the VERSION file changes workflow_dispatch: -name: Prepare a new release +name: Create a New Release env: PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} jobs: - release: - name: Prepare a new release + wait_for_checks: + if: ${{ github.event_name != 'workflow_dispatch' }} + strategy: + matrix: + req_workflow: + [ + verify_library_structure.yaml, + build_examples.yaml, + build_documentation.yaml, + ] + name: Wait for Checks to complete runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set variables - run: | - echo "::debug::Get the current version number" - VER=$(cat VERSION) - echo "VERSION=$VER" >> $GITHUB_ENV - - - name: Restore or Cache pip - uses: actions/cache@v4.0.2 - with: - path: ~/.cache/pip - # if requirements.txt hasn't changed, then it will be a "cache hit" and pip will be restored - # 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 - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: ${{ runner.os }}-pip- - - - name: Set up Python - uses: actions/setup-python@v5 + - name: Wait on Workflow + uses: ArcticLampyrid/action-wait-for-workflow@v1.2.0 with: - python-version: '3.x' + workflow: ${{ matrix.req_workflow }} + sha: ${{ github.sha || github.event.pull_request.head.sha || github.event.pull_request.head.ref }} # optional + allowed-conclusions: | + success + cancelled + skipped - # This should be pulled from cache, if there's not a new version - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - - name: Get notes - id: generate_notes - uses: anmarkoulis/commitizen-changelog-reader@master - with: - # NOTE: Need to add the refs/tags to work with the generate notes action - tag_name: ${{ format('refs/tags/{0}', env.VERSION) }} - changelog: ChangeLog.md - - # Create a new release - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ env.VERSION }} - release_name: ${{ env.VERSION }} - draft: false - prerelease: false - body: ${{join(fromJson(steps.generate_notes.outputs.notes).notes, '')}} - - # Publish the new release to the pio package manager - - name: Publish release to PIO - id: publish-pio - run: pio package publish + release: + name: Prepare a new release + needs: [wait_for_checks] + if: | + always() && + (needs.wait_for_checks.result == 'success' || needs.wait_for_checks.result == 'skipped') + uses: EnviroDIY/workflows/.github/workflows/prepare_release.yaml@main + secrets: inherit + with: + library-manager: 'update' + library-compliance: 'strict' diff --git a/.github/workflows/verify_library_json.yaml b/.github/workflows/verify_library_json.yaml deleted file mode 100644 index 41dafc1..0000000 --- a/.github/workflows/verify_library_json.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: Verify JSON structure for library manifest - -# Triggers the workflow on push or pull request events -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, 'ci skip')" - - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4.0.3 - - - name: Cache Node.js modules - uses: actions/cache@v4.0.2 - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.OS }}-node- - ${{ runner.OS }}- - - - name: install jsonlint - run: npm install -g jsonlint - - - name: run jsonlint - run: jsonlint -q library.json diff --git a/.github/workflows/verify_library_structure.yaml b/.github/workflows/verify_library_structure.yaml new file mode 100644 index 0000000..bf99587 --- /dev/null +++ b/.github/workflows/verify_library_structure.yaml @@ -0,0 +1,17 @@ +name: Verify library manifest and structure + +# Triggers the workflow on push or pull request events +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify_library_structure: + name: Validate library structure + if: ${{ ! contains(github.event.head_commit.message, 'ci skip') }} + uses: EnviroDIY/workflows/.github/workflows/verify_library_structure.yaml@main + with: + library-manager: 'update' + library-compliance: 'strict' diff --git a/.gitignore b/.gitignore index 6b2d960..7a423e9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk +# Microsoft Office temp files +~$* + # ========================= # Operating System Files # ========================= @@ -49,36 +52,119 @@ Temporary Items # PyCharm .idea/ - -# VSCode -.history -.vscode - -# Atom / PlatformIO +# Python +__pycache__/ + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Ignore list for Eagle, a PCB layout tool + +# Backup files +*.s#? +*.b#? +*.l#? +*.b$? +*.s$? +*.l$? + +# Eagle project file +# It contains a serial number and references to the file structure +# on your computer. +# comment the following line if you want to have your project file included. +eagle.epf + +# Autorouter files +*.pro +*.job + +# CAM files +*.$$$ +*.cmp +*.ly2 +*.l15 +*.sol +*.plc +*.stc +*.sts +*.crc +*.crs + +*.dri +*.drl +*.gpi +*.pls +*.ger +*.xln + +*.drd +*.drd.* + +*.s#* +*.b#* + +*.info + +*.eps + +# file locks introduced since 7.x +*.lck + +# PlatformIO .pio +.pioenvs +.piolibdeps .clang_complete .gcc-flags.json lib/readme.txt -include/readme.txt -platformio.ini - - -examples/debug_print/ -doxygen/doxygenOutput.log -Doxyfile-mcss -examples/debugging_prints/* -doxygen/mcss-theme.css -doxygen/mcss-theme-compiled.css -doxygen/__pycache__/conf.cpython-37.pyc -doxygen/conf.py -SDI12 Repo Token.txt -__pycache__ -docs/LocalDoxyfile -runDoxygen.bat -docs/examples/* -docs/doxygenOutput.log -docs/mcss-doxy-output.log -docs/mcss.log -doygen-run.log -examples/TestCommands/TestCommands.ino +/platformio.ini +tests/* + +# VSCode +.vscode +.history compile_commands.json + +# Other Stuff +pioScripts +runDoxygen.bat +generateKeywords.bat +output_*.log +sync.ffs_db +docs/Doxyfile.bak +SDI12 Repo Token.txt +boards/ +lib/ +variants/ +docs/DoxygenLayout.xml_archive +/pio_common_libdeps.ini diff --git a/ChangeLog.md b/ChangeLog.md index b49c8b3..74975a7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -6,23 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 *** - ## [Unreleased] ### Changed + - Added python version to GitHub actions (for PlatformIO) +- Switched to reusable workflows for GitHub actions +- Consolidated timer prescaler math +- Moved bitTimes and mul8x8to16 functions to the timers files +- Replace c-style casts with c++ style casts +- Do not disable interrupts during Tx for processors over 48MHz +- Cast all timer values and math to the proper type for the processor timer being read + - SAMD21 boards now use the full 16-bits of the timer rather than only the first 8-bits. + - AVR xU4 boards (ie, 32u4/Leonardo) still use the 10-bit timer as an 8-bit timer. +- Shortened name of espressif ISR access macro from `ESPFAMILY_USE_INSTRUCTION_RAM` to `ISR_MEM_ACCESS` + - This does not change any functionality of the macro, just the name. +- Moved defines to the top of the SDI12_boards.h file +- Renamed the "tools" directory to "extras" in compliance with Arduino library standards. +- Updated copyright and license texts +- SAMD boards now *partially* revert clock and prescaler settings when an SDI-12 instance is ended. + - Prescalers are reset to factory settings, the clock divisor is not reset ### Added +- Added CRC checking +- Added support for SAMD51 processors using dedicated timers +- Added parity checking on character reception + - This can be disabled by defining the macro `SDI12_IGNORE_PARITY` +- Allowing (_**without testing**_) processors over 48MHz to use `micros()` function +- Added a 'yield' function within stream functions to allow the buffer to fill + - The yield time can be modified using the macro `SDI12_YIELD_MS` + ### Removed +- Offloaded some internal header file documentation to markdown files +- Consolidated redundant `READTIME` and `TCNTX` macros, removing `TCNTX` +- Removed documation m_span commands + ### Fixed +- Added an extra addition/removal of interrupts for espressif boards in the `begin` function to properly initialize the interrupts and avoid a later error with the `gpio_install_isr_service`. + *** ## v2.1.4 (2021-05-05) [Revert wake delay to 0ms](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.4) ### Possibly breaking changes + - Reverted the default wake delay to 0ms. - In 92055d377b26fa862c43d1429de1ccbef054af01 this was bumped up to 10ms, which caused problems for several people. - The delay can now also be set using the build flag `-D SDI12_WAKE_DELAY=#` @@ -30,12 +61,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## v2.1.3 (2021-03-24) [Migrate to GitHub Actions](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.3) ### Improvements + - Migrate from Travis to GitHub actions ## v2.1.1 (2020-08-20) [Patches for ATTiny](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.1) ### Bug Fixes -- fixes for the timer and pre-scaler for the ATTiny, courtesy of @gabbas1 + +- fixes for the timer and pre-scaler for the ATTiny, courtesy of \@gabbas1 ## v2.1.0 (2020-07-10) [Library Rename and ESP support](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v2.1.0) @@ -44,24 +77,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **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. ### New Features + - Adds support for Espressif ESP8266 and ESP32 -- 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 -- Adds Doxygen (Javadoc) style comments to **ALL** members of the library. The generated documentation is available at https://envirodiy.github.io/Arduino-SDI-12/. +- 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 +- Adds Doxygen (Javadoc) style comments to **ALL** members of the library. The generated documentation is available at . ## v1.3.6 (2019-08-29) [Fixed extra compiler warnings](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.6) ### Bug Fixes + - A very minor update to fix compiler warnings found when using -Wextra in addition to -Wall. ## v1.3.5 (2019-07-01) [Removed SAMD Tone Conflict](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.5) ### Improvements + - 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. - Examples were also updated and given platformio.ini files. ## v1.3.4 (2019-10-29) [Timer class](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.4) ### Improvements + - Made the timer changes into a compiled class. 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. @@ -69,26 +106,31 @@ Maintaining interrupt control for SAMD processors as there are no interrupt vect ## v1.3.3 (2018-05-11) [Unset prescalers](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.3) ### Improvements + - Now unsetting timer prescalers and setting the isActive pointer to NULL in both the end and the destructor functions. - Also some clean-up of the examples. ## v1.3.1 (2018-04-06) [Added processor timer for greater stability](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.3.1) ### New Features + - 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. ### Improvements + - Made changes to the write functions to use the timer to reduce the amount of time that all system interrupts are off. - Forcing all SDI-12 objects to use the same buffer to reduce ram usage. ## v1.1.0 (2018-03-15) [Better integration inside other libraries](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.1.0) ### Improvements + - Added notes and an empty constructor/populated begin method to allow this library to be more easily called inside of other libraries. ## v1.0.6 (2018-03-09) [Fixed timeout values](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.0.6) ### Bug Fixes + - 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. ## v1.0.1 (2017-05-16) [Initial Release](https://github.com/EnviroDIY/Arduino-SDI-12/releases/tag/v1.0.1) diff --git a/README.md b/README.md index 724aba5..200c26f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -[//]: # ( @mainpage SDI-12 for Arduino) -# SDI-12 for Arduino +# SDI-12 for Arduino -[//]: # ( @section mainpage_intro Introduction ) -## Introduction +## Introduction This is an Arduino library for SDI-12 communication with a wide variety of environmental sensors. 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. @@ -13,20 +11,23 @@ It provides a general software solution, without requiring any additional hardwa 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/). -[//]: # ( Start GitHub Only ) +[//]: # ( @cond GitHub ) + ## Documentation -Extensive documentation on the SDI-12 functions and classes is available here: https://envirodiy.github.io/Arduino-SDI-12/index.html +Extensive documentation on the SDI-12 functions and classes is available here: + +[//]: # ( @endcond ) -[//]: # ( End GitHub Only ) +### Renaming Notice -[//]: # ( @subsection mainpage_rename Renaming Notice ) -### Renaming Notice -**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.** +> [!IMPORTANT] +> **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.** [//]: # ( @tableofcontents ) -[//]: # ( Start GitHub Only ) +[//]: # ( @cond GitHub ) + - [SDI-12 for Arduino](#sdi-12-for-arduino) - [Introduction](#introduction) - [Documentation](#documentation) @@ -35,30 +36,28 @@ Extensive documentation on the SDI-12 functions and classes is available here: - [Origins and Inherited Limitations](#origins-and-inherited-limitations) - [Compatibility Considerations](#compatibility-considerations) - [Variants and Branches](#variants-and-branches) - - [EnviroDIY_SDI12](#envirodiy_sdi12) - - [EnviroDIY_SDI12_PCINT3](#envirodiy_sdi12_pcint3) - - [EnviroDIY_SDI12_ExtInts](#envirodiy_sdi12_extints) + - [EnviroDIY\_SDI12](#envirodiy_sdi12) + - [EnviroDIY\_SDI12\_PCINT3](#envirodiy_sdi12_pcint3) + - [EnviroDIY\_SDI12\_ExtInts](#envirodiy_sdi12_extints) - [Contribute](#contribute) - [License](#license) - [Credits](#credits) -[//]: # ( End GitHub Only ) +[//]: # ( @endcond ) -[//]: # ( @section mainpage_getting_started Getting Started ) -## Getting Started +## Getting Started Learn more, below, about this library's: -* [Origins and Inherited Limitations](https://github.com/EnviroDIY/Arduino-SDI-12#origins-and-inherited-limitations); -* [Compatibility Considerations](https://github.com/EnviroDIY/Arduino-SDI-12#compatibility-considerations); -* [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) we created to overcome some limitations. -Try running our [Example sketches](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples) with your Arduino board and SDI-12 sensor. +- [Origins and Inherited Limitations](https://github.com/EnviroDIY/Arduino-SDI-12#origins-and-inherited-limitations); +- [Compatibility Considerations](https://github.com/EnviroDIY/Arduino-SDI-12#compatibility-considerations); +- [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches) we created to overcome some limitations. -Full details on the library functionality can be found on github pages: https://envirodiy.github.io/Arduino-SDI-12/ +Try running our [Example sketches](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples) with your Arduino board and SDI-12 sensor. +Full details on the library functionality can be found on github pages: -[//]: # ( @section mainpage_origins Origins and Inherited Limitations ) -## Origins and Inherited Limitations +## Origins and Inherited Limitations This library was developed from the [SoftwareSerial](https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/SoftwareSerial) library that is a built-in [standard Arduino library](https://www.arduino.cc/en/Reference/Libraries). It was further modified to use a timer to improve read stability and decrease the amount of time universal interrupts are disabled using logic from [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial). @@ -78,8 +77,7 @@ There will be no obvious compile error, but because SDI-12 and the tone library All 8MHz AVR boards will also have unresolvable prescaler conflicts with [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial). 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. -[//]: # ( @section mainpage_compatibility Compatibility Considerations ) -## Compatibility Considerations +## Compatibility Considerations 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. It should also work on an Arduino Mega (AtMega2560), Gemma/AtTiny board, and most other AVR processors running on the Arduino framework. @@ -97,63 +95,68 @@ Not all Arduino boards have the same pin capabilities. The known compatibile pins for common variants are shown below: **AtMega328p / Arduino Uno:** + - Any pin **AtMega1284p / EnviroDIY Mayfly** + - Any pin **ATmega2560 / Arduino Mega or Mega 2560:** + - 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) -**AtMega32u4 / Arduino Leonardo or Adafruit Feather:** +**AtMega32u4 / Arduino Leonardo or Adafruit Feather 32u4:** + - 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI) **SAMD21G18 / Arduino Zero:** + - Any pin (except 4 on the zero) **ESP8266:** + - Any GPIO, except GPIO16 **ESP32:** + - Any GPIO Note that not all of these pins are available with our [Variants and Branches](https://github.com/EnviroDIY/Arduino-SDI-12#variants-and-branches), below. +## Variants and Branches -[//]: # ( @section mainpage_variants Variants and Branches ) -## Variants and Branches As we've described, the default "master" branch of this library will conflict with SoftwareSerial and any other library that monopolizes all pin change interrupt vectors for all AVR boards. To allow simultaneous use of SDI-12 and SoftwareSerial, we have created additional variants of these libraries that we maintain as separate branches of this repository. For background information, my be helpful to read our [Overview of Interrupts](https://github.com/EnviroDIY/Arduino-SDI-12/wiki/2b.-Overview-of-Interrupts) wiki page or this [Arduino Pin Change Interrupts article](https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/). -[//]: # ( @subsection mainpage_master EnviroDIY_SDI12 ) -#### EnviroDIY_SDI12 +### EnviroDIY_SDI12 + EnviroDIY_SDI12 is the default master branch of this repository. It controls and monopolizes all pin change interrupt vectors, and can therefore have conflicts with any variant of SoftwareSerial and other libraries that use interrupts. -[//]: # ( @subsection mainpage_pcint3 EnviroDIY_SDI12_PCINT3 ) -#### EnviroDIY_SDI12_PCINT3 +### EnviroDIY_SDI12_PCINT3 + EnviroDIY_SDI12_PCINT3 is in the Mayfly branch of this repository, and was historically was called "SDI12_mod". It's been cropped to only control interrupt vector 3, or PCINT3 (D), which on the Mayfly (or Sodaq Mbili) corresponds to Pins D0-D7. It is designed to be compatible with [EnviroDIY_SoftwareSerial_PCINT12](https://github.com/EnviroDIY/SoftwareSerial_PCINT12) library (which controls interrupt vectors PCINT1 (B) & PCINT2 (C) / Mayfly pins D08-D15 & D16-D23) and [EnviroDIY PcInt PCINT0](https://github.com/EnviroDIY/PcInt_PCINT0) (which controls interrupt vectors PCINT0 (A) / Mayfly pins D24-D31/A0-A7). Note that different AtMega1284p boards have a different mapping from the physical PIN numbers to the listed digital PIN numbers that are printed on the board. One of the most helpful lists of pins and interrupts vectors is in the the [Pin/Port Bestiary wiki page for the Enable Interrupt library](https://github.com/GreyGnome/EnableInterrupt/wiki/Usage#PIN__PORT_BESTIARY). -[//]: # ( @subsection mainpage_extints EnviroDIY_SDI12_ExtInts ) -#### EnviroDIY_SDI12_ExtInts +### EnviroDIY_SDI12_ExtInts + EnviroDIY_SDI12_ExtInts is the ExtInt branch of this repository. It doesn't control any of the interrupts, but instead relies on an external interrupt management library (like [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt)) to assign the SDI-12 receive data function to the right pin. This is the least stable because there's some extra delay because the external library is involved, but the use of timers in the SDI-12 library greatly increases it's stability. It's also the easiest to get working in combination with any other pin change interrupt based library. It can be paired with the [EnviroDIY_SoftwareSerial_ExtInts](https://github.com/EnviroDIY/SoftwareSerial_ExternalInts) libraries (which is, by the way, extremely unstable). -If you would like to use a different pin change interrupt library, uncomment the line ```#define SDI12_EXTERNAL_PCINT``` in SDI12.h and recompile the library. +If you would like to use a different pin change interrupt library, uncomment the line `#define SDI12_EXTERNAL_PCINT` in SDI12.h and recompile the library. Then, in your own code call `SDI12::handleInterrupt()` as the interrupt for the SDI12 pin using the other interrupt library. Example j shows doing this in GreyGnome's [EnableInterrupt](https://github.com/GreyGnome/EnableInterrupt) library. +## Contribute -[//]: # ( @section mainpage_contribute Contribute ) -## Contribute Open an [issue](https://github.com/EnviroDIY/Arduino-SDI-12/issues) to suggest and discuss potential changes/additions. For power contributors: @@ -164,17 +167,16 @@ For power contributors: 4. Push to the branch: `git push origin my-new-feature` 5. Submit a pull request :D +## License -[//]: # ( @section mainpage_license License ) -## License 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. 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. Documentation is licensed as [Creative Commons Attribution-ShareAlike 4.0](https://creativecommons.org/licenses/by-sa/4.0/) (CC-BY-SA) copyright. -[//]: # ( @section mainpage_credits Credits ) -## Credits +## Credits + [EnviroDIY](http://envirodiy.org/)™ is presented by the Stroud Water Research Center, with contributions from a community of enthusiasts sharing do-it-yourself ideas for environmental science and monitoring. [Kevin M. Smith](https://github.com/Kevin-M-Smith) 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). @@ -183,6 +185,6 @@ Documentation is licensed as [Creative Commons Attribution-ShareAlike 4.0](https This project has benefited from the support from the following funders: -* National Science Foundation, awards [EAR-0724971](http://www.nsf.gov/awardsearch/showAward?AWD_ID=0724971), [EAR-1331856](http://www.nsf.gov/awardsearch/showAward?AWD_ID=1331856), [ACI-1339834](http://www.nsf.gov/awardsearch/showAward?AWD_ID=1339834) -* William Penn Foundation, grant 158-15 -* Stroud Water Research Center endowment +- National Science Foundation, awards [EAR-0724971](http://www.nsf.gov/awardsearch/showAward?AWD_ID=0724971), [EAR-1331856](http://www.nsf.gov/awardsearch/showAward?AWD_ID=1331856), [ACI-1339834](http://www.nsf.gov/awardsearch/showAward?AWD_ID=1339834) +- William Penn Foundation, grant 158-15 +- Stroud Water Research Center endowment diff --git a/continuous_integration/.travis.yml_archive b/continuous_integration/.travis.yml_archive deleted file mode 100644 index f84a321..0000000 --- a/continuous_integration/.travis.yml_archive +++ /dev/null @@ -1,160 +0,0 @@ -before_install: - - git config --global user.email "sdamiano@stroudcenter.org" - - git config --global user.name "SRGDamia1" - -sudo: false -git: - depth: 1 -branches: - except: - - gh-pages - -cache: - pip: true - directories: - - "~/.platformio" - - $TRAVIS_BUILD_DIR/doxygen-src - -language: python -python: - - "2.7" - -install: - # Remove the cloned repo to emulate a user library installation - - git rm library.json - # - git rm library.properties - # - git rm -r pioScripts - # - git rm -r src - # Install PlatformIO (this should be cached!) - - pip install -U platformio - - pio upgrade - # Uninstall any old version of the current library from the Travis cache - - if pio lib --global uninstall EnviroDIY_Arduino-SDI-12; then - echo "Uninstalled cached version of Arduino-SDI-12"; - fi - - if pio lib --global uninstall EnviroDIY_SDI-12; then - echo "Uninstalled cached version of SDI-12"; - fi - # Install this library from the branch we're working on - # echo "Installing SDI-12 from https://github.com/$TRAVIS_REPO_SLUG.git#$TRAVIS_BRANCH"; - # echo "Installing SDI-12 from https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_BRANCH"; - - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then - echo "Installing SDI-12 from https://github.com/$TRAVIS_REPO_SLUG.git#$TRAVIS_COMMIT"; - else - echo "Installing SDI-12 from https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_SHA"; - fi - # pio lib --global install https://github.com/$TRAVIS_REPO_SLUG.git#$BRANCH; - # pio lib --global install https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_BRANCH; - - if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then - pio lib --global install https://github.com/$TRAVIS_REPO_SLUG.git#$TRAVIS_COMMIT; - else - pio lib --global install https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git#$TRAVIS_PULL_REQUEST_SHA; - fi - - pio update - -script: - - platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 --board=huzzah --board=featheresp32 - -jobs: - include: - - name: "Verify library JSON format" - language: node_js - install: npm install -g jsonlint - script: jsonlint -q library.json - after_success: | - echo "TRAVIS_BRANCH=$TRAVIS_BRANCH TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST" - if [[ ($TRAVIS_BRANCH == master) && - ($TRAVIS_PULL_REQUEST == false) ]] ; then - curl -LO --retry 3 https://raw.github.com/mernst/plume-lib/master/bin/trigger-travis.sh - sh trigger-travis.sh EnviroDIY Libraries $TRAVIS_ACCESS_TOKEN - fi - - - name: "Build Doxygen Documentation" - if: branch = master AND type != pull_request - language: python - python: - - "3.7" - before_install: - - git config --global user.email "sdamiano@stroudcenter.org" - - git config --global user.name "SRGDamia1" - - git config --global push.default simple - - sudo apt-get update - - sudo apt-get -y install build-essential - - sudo apt-get -y install graphviz - - sudo apt-get -y install flex - - sudo apt-get -y install bison - - sudo apt-get -y install texlive-base - - sudo apt-get -y install texlive-latex-extra - - sudo apt-get -y install texlive-fonts-extra - - sudo apt-get -y install texlive-fonts-recommended - - pip3 install jinja2 Pygments - install: - - cd $TRAVIS_BUILD_DIR - - chmod +x travis/copy-doc-sources.sh - - sh travis/copy-doc-sources.sh - - cd $TRAVIS_BUILD_DIR - - chmod +x travis/build-install-doxygen.sh - - sh travis/build-install-doxygen.sh - script: - - cd $TRAVIS_BUILD_DIR - - chmod +x travis/generate-documentation.sh - - sh travis/generate-documentation.sh - # after_success: - # - cd $TRAVIS_BUILD_DIR - # - chmod +x travis/deploy-documentation.sh - # - sh travis/deploy-documentation.sh - deploy: - provider: pages:git - token: $GH_REPO_TOKEN - edge: true # opt in to dpl v2 - keep_history: false - local_dir: $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12Doxygen/m.css - project_name: Arduino-SDI-12 - - - name: "a_wild_card" - env: - - PLATFORMIO_CI_SRC=examples/a_wild_card/a_wild_card.ino - - - name: "b_address_change" - env: - - PLATFORMIO_CI_SRC=examples/b_address_change/b_address_change.ino - - - name: "c_check_all_addresses" - env: - - PLATFORMIO_CI_SRC=examples/c_check_all_addresses/c_check_all_addresses.ino - - - name: "d_simple_logger" - env: - - PLATFORMIO_CI_SRC=examples/d_simple_logger/d_simple_logger.ino - - - name: "e_simple_parsing" - env: - - PLATFORMIO_CI_SRC=examples/e_simple_parsing/e_simple_parsing.ino - - - name: "f_basic_data_request" - env: - - PLATFORMIO_CI_SRC=examples/f_basic_data_request/f_basic_data_request.ino - - - name: "g_terminal_window" - env: - - PLATFORMIO_CI_SRC=examples/g_terminal_window/g_terminal_window.ino - - - name: "h_SDI-12_slave_implementation" - env: - - PLATFORMIO_CI_SRC=examples/h_SDI-12_slave_implementation/h_SDI-12_slave_implementation.ino - - - name: "i_SDI-12_interface" - env: - - PLATFORMIO_CI_SRC=examples/i_SDI-12_interface/i_SDI-12_interface.ino - - - name: "j_external_pcint_library" - env: - - PLATFORMIO_CI_SRC=examples/j_external_pcint_library/j_external_pcint_library.ino - - PLATFORMIO_BUILD_FLAGS=-DSDI12_EXTERNAL_PCINT - script: - - pio lib --global install EnableInterrupt - - platformio ci --board=mayfly --board=feather32u4 --board=adafruit_feather_m0 --board=uno --board=megaatmega2560 - - - name: "k_concurrent_logger" - env: - - PLATFORMIO_CI_SRC=examples/k_concurrent_logger/k_concurrent_logger.ino diff --git a/continuous_integration/build-install-doxygen.sh b/continuous_integration/build-install-doxygen.sh deleted file mode 100644 index 1129871..0000000 --- a/continuous_integration/build-install-doxygen.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh - -# Exit with nonzero exit code if anything fails -set -e - -# install all the dependencies for make for Doxygen -sudo apt-get update -sudo apt-get -y install build-essential -sudo apt-get -y install flex -sudo apt-get -y install bison -sudo apt-get -y install texlive-base -sudo apt-get -y install texlive-latex-extra -sudo apt-get -y install texlive-fonts-extra -sudo apt-get -y install texlive-fonts-recommended - -cd $TRAVIS_BUILD_DIR - -if [ ! -f $TRAVIS_BUILD_DIR/doxygen-src/build/bin/doxygen ]; then - - # Build instructions from: https://www.stack.nl/~dimitri/doxygen/download.html - echo "Cloning doxygen repository..." - git clone https://github.com/doxygen/doxygen.git doxygen-src --branch $DOXYGEN_VERSION --depth 1 - - cd doxygen-src - - echo "Create build folder..." - mkdir build - cd build - - echo "Make..." - cmake -G "Unix Makefiles" .. - make - echo "Done building doxygen." - echo "doxygen path: " $(pwd) -fi - -echo "Current Doxygen version..." -$TRAVIS_BUILD_DIR/doxygen-src/build/bin/doxygen -v - -# echo "Move Doxygen to working directory" -# cp $TRAVIS_BUILD_DIR/doxygen-src/build/bin/* $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12/docs -#make install - -cd $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12 diff --git a/continuous_integration/copy-doc-sources.sh b/continuous_integration/copy-doc-sources.sh deleted file mode 100644 index 8082ca9..0000000 --- a/continuous_integration/copy-doc-sources.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh - -# Script modified from scripts by Jeroen de Bruijn, thephez, and Adafruit -# https://gist.github.com/vidavidorra/548ffbcdae99d752da02 -# https://github.com/thephez/doxygen-travis-build -# https://learn.adafruit.com/the-well-automated-arduino-library/travis-ci - -# Exit with nonzero exit code if anything fails -set -e - -# Create a clean working directory for this script. -mkdir $TRAVIS_BUILD_DIR/code_docs -cd $TRAVIS_BUILD_DIR/code_docs - -# Re-clone the main repo, not sparsely -git clone -b master --depth 1 https://github.com/EnviroDIY/Arduino-SDI-12 Arduino-SDI-12 - -# Clone m.css for its theming -git clone --depth 1 https://github.com/SRGDamia1/m.css m.css - -# Get the current gh-pages branch -# git clone -b gh-pages https://git@$GH_REPO_REF -# cd $GH_REPO_NAME -git clone -b gh-pages --depth 1 https://github.com/EnviroDIY/Arduino-SDI-12 Arduino-SDI-12Doxygen -cd Arduino-SDI-12Doxygen -echo "Documentation path: " $(pwd) - -# Remove everything currently in the gh-pages branch. -# GitHub is smart enough to know which files have changed and which files have -# stayed the same and will only update the changed files. So the gh-pages branch -# can be safely cleaned, and it is sure that everything pushed later is the new -# documentation. -rm -rf * - -# Need to create a .nojekyll file to allow filenames starting with an underscore -# to be seen on the gh-pages site. Therefore creating an empty .nojekyll file. -# Presumably this is only needed when the SHORT_NAMES option in Doxygen is set -# to NO, which it is by default. So creating the file just in case. -echo "" > .nojekyll -ls \ No newline at end of file diff --git a/continuous_integration/deploy-documentation.sh b/continuous_integration/deploy-documentation.sh deleted file mode 100644 index 4679321..0000000 --- a/continuous_integration/deploy-documentation.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -# Script modified from scripts by Jeroen de Bruijn, thephez, and Adafruit -# https://gist.github.com/vidavidorra/548ffbcdae99d752da02 -# https://github.com/thephez/doxygen-travis-build -# https://learn.adafruit.com/the-well-automated-arduino-library/travis-ci - -# Exit with nonzero exit code if anything fails -set -e -cd $TRAVIS_BUILD_DIR/code_docs -echo "Current path: " $(pwd) -echo "Contents (ls):" -ls -cd $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12 -echo "Current path: " $(pwd) -echo "Contents (ls):" -ls -cd $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12Doxygen -echo "Current path: " $(pwd) -echo "Contents (ls):" -ls - -################################################################################ -##### Upload the documentation to the gh-pages branch of the repository. ##### -# Only upload if Doxygen successfully created the documentation. -# Check this by verifying that the html directory and the file html/index.html -# both exist. This is a good indication that Doxygen did it's work. -if [ -d $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12Doxygen/m.css ] && - [ -f $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12Doxygen/m.css/index.html ]; then - - echo 'Uploading documentation to the gh-pages branch...' - # Add everything in this directory (the Doxygen code documentation) to the - # gh-pages branch. - # GitHub is smart enough to know which files have changed and which files have - # stayed the same and will only update the changed files. - git add --all - - # Commit the added files with a title and description containing the Travis CI - # build number and the GitHub commit reference that issued this build. - git commit -m "Deploy code docs to GitHub Pages Travis build: ${TRAVIS_BUILD_NUMBER}" -m "Commit: ${TRAVIS_COMMIT}" - - # Force push to the remote gh-pages branch. - # The ouput is redirected to /dev/null to hide any sensitive credential data - # that might otherwise be exposed. - git push --force "https://${GH_REPO_TOKEN}@${GH_REPO_REF}" > /dev/null 2>&1 -else - echo '' >&2 - echo 'Warning: No documentation (html) files have been found!' >&2 - echo 'Warning: Not going to push the documentation to GitHub!' >&2 - exit 1 -fi \ No newline at end of file diff --git a/continuous_integration/generate-documentation.sh b/continuous_integration/generate-documentation.sh deleted file mode 100644 index 7402c0d..0000000 --- a/continuous_integration/generate-documentation.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -# Script modified from scripts by Jeroen de Bruijn, thephez, and Adafruit -# https://gist.github.com/vidavidorra/548ffbcdae99d752da02 -# https://github.com/thephez/doxygen-travis-build -# https://learn.adafruit.com/the-well-automated-arduino-library/travis-ci - -# Exit with nonzero exit code if anything fails -set -e - -cd $TRAVIS_BUILD_DIR/code_docs/m.css -echo 'Update the style sheets' -cd $TRAVIS_BUILD_DIR/code_docs/m.css/css/EnviroDIY -python $TRAVIS_BUILD_DIR/code_docs/m.css/css/postprocess.py "m-EnviroDIY.css" -python $TRAVIS_BUILD_DIR/code_docs/m.css/css/postprocess.py "m-EnviroDIY.css" "m-documentation.css" -o "m-EnviroDIY+documentation.compiled.css" -python $TRAVIS_BUILD_DIR/code_docs/m.css/css/postprocess.py "m-EnviroDIY.css" "m-theme-EnviroDIY.css" "m-documentation.css" --no-import -o "m-EnviroDIY.documentation.compiled.css" -cp $TRAVIS_BUILD_DIR/code_docs/m.css/css/EnviroDIY/m-EnviroDIY+documentation.compiled.css $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12/docs/css - -echo 'Creating dox files from example read-me files' -cd $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12/docs -python documentExamples.py - -echo 'Current Doxygen version...' -$TRAVIS_BUILD_DIR/doxygen-src/build/bin/doxygen -v 2>&1 - -# Redirect both stderr and stdout to the log file AND the console. -echo 'Generating Doxygen code documentation...' -$TRAVIS_BUILD_DIR/doxygen-src/build/bin/doxygen Doxyfile 2>&1 | tee doxygen.log - -echo 'Fixing errant xml section names in examples as generated by Doxygen...' -python fixXmlExampleSections.py - -python $TRAVIS_BUILD_DIR/code_docs/m.css/documentation/doxygen.py "mcss-conf.py" --no-doxygen --output mcss.log --templates "$TRAVIS_BUILD_DIR/code_docs/m.css/documentation/templates/EnviroDIY" --debug > mcss-doxy-output.log diff --git a/continuous_integration/install-current-doxygen.sh b/continuous_integration/install-current-doxygen.sh deleted file mode 100644 index 7640771..0000000 --- a/continuous_integration/install-current-doxygen.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Script modified from scripts by Jeroen de Bruijn, thephez, and Adafruit -# https://gist.github.com/vidavidorra/548ffbcdae99d752da02 -# https://github.com/thephez/doxygen-travis-build -# https://learn.adafruit.com/the-well-automated-arduino-library/travis-ci - -# Exit with nonzero exit code if anything fails -set -e - -cd $TRAVIS_BUILD_DIR/code_docs/Arduino-SDI-12 - -wget -q http://doxygen.nl/files/doxygen-1.8.18.linux.bin.tar.gz - -echo "Decompressing..." -tar -xzf doxygen-1.8.18.linux.bin.tar.gz -ls - -echo "Moving directory..." -mv doxygen-1.8.18/bin/doxygen . -chmod +x doxygen -echo "Current path: " $(pwd) -ls - -echo 'Installed Doxygen version...' -./doxygen -v 2>&1 diff --git a/docs/CreatingACharacter.md b/docs/CreatingACharacter.md index 8eeb6cb..4fcaecc 100644 --- a/docs/CreatingACharacter.md +++ b/docs/CreatingACharacter.md @@ -1,16 +1,16 @@ -[//]: # ( @page rx_page Creating a Character - Stepping through the Rx ISR ) -# Creating a Character - Stepping through the Rx ISR +# Creating a Character - Stepping through the Rx ISR [//]: # ( @tableofcontents ) -[//]: # ( Start GitHub Only ) +[//]: # ( @cond GitHub ) + - [Creating a Character - Stepping through the Rx ISR](#creating-a-character---stepping-through-the-rx-isr) - [How a Character Looks in SDI-12](#how-a-character-looks-in-sdi-12) - [Static Variables we Need](#static-variables-we-need) - [Following the Mask](#following-the-mask) - [Waiting for a Start Bit](#waiting-for-a-start-bit) - [The Start of a Character](#the-start-of-a-character) - - [The Interrupt Fires!](#the-interrupt-fires) + - [The Interrupt Fires](#the-interrupt-fires) - [Bit by Bit](#bit-by-bit) - [A LOW/1 Bit](#a-low1-bit) - [A HIGH/0 Bit](#a-high0-bit) @@ -18,7 +18,7 @@ - [A Finished Character](#a-finished-character) - [The Full Interrupt Function](#the-full-interrupt-function) -[//]: # ( End GitHub Only ) +[//]: # ( @endcond ) Here we'll walk step-by-step through how the SDI-12 library (and NeoSWSerial) create a character from the ISR. 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. @@ -28,10 +28,10 @@ For a person, that 8.33ms is trivial, but for even a "slow" 8MHz processor, that So, let's look at what's happening. -[//]: # ( @section rx_specs How a Character Looks in SDI-12 ) -## How a Character Looks in SDI-12 +## How a Character Looks in SDI-12 First we need to keep in mind the specifications of SDI-12: + - We use *inverse logic* that means a "1" bit is at LOW level and a "0" bit is HIGH level. - characters are sent as 10 bits - 1 start bit, which is always a 0/HIGH @@ -39,10 +39,10 @@ First we need to keep in mind the specifications of SDI-12: - 1 parity bit - 1 stop bit, which is always 1/LOW -[//]: # ( @section rx_vars Static Variables we Need ) -## Static Variables we Need +## Static Variables we Need And lets remind ourselves of the static variables we're using to store states: + - `prevBitTCNT` stores the time of the previous RX transition in micros - `rxState` tracks how many bits are accounted for on an incoming character. - if 0: indicates that we got a start bit @@ -52,80 +52,67 @@ And lets remind ourselves of the static variables we're using to store states: - The mask has a single bit set, in the place of the active bit based on the rxState - `rxValue` is the value of the character being built -[//]: # ( @section rx_mask Following the Mask ) -## Following the Mask +## Following the Mask -[//]: # ( @subsection rx_mask_wait Waiting for a Start Bit ) -### Waiting for a Start Bit +### Waiting for a Start Bit The `rxState`, `rxMask`, and `rxValue` all work together to form a character. 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: +```unparsed +| rxValue: | 0 0 0 0 0 0 0 0 | +| -------- | ----------------------------- | +| rxMask: | 0 0 0 0 0 0 0 1 | +| rxState: | 1 1 1 1 1 1 1 1 | ``` - rxValue: | 0 0 0 0 0 0 0 0 --------------|----------------------------------- - rxMask: | 0 0 0 0 0 0 0 1 - rxState: | 1 1 1 1 1 1 1 1 -``` - -[//]: # ( @subsection rx_mask_start The Start of a Character ) -### The Start of a Character +### The Start of a Character After we get a start bit, the `startChar()` function creates a blank slate for the new character, so our values are: +```unparsed +| rxValue: | 0 0 0 0 0 0 0 0 | +| -------- | ----------------------------- | +| rxMask: | 0 0 0 0 0 0 0 1 | +| rxState: | 0 0 0 0 0 0 0 0 | ``` - rxValue: | 0 0 0 0 0 0 0 0 --------------|----------------------------------- - rxMask: | 0 0 0 0 0 0 0 1 - rxState: | 0 0 0 0 0 0 0 0 -``` - -[//]: # ( @subsection rx_mask_fire The Interrupt Fires! ) -### The Interrupt Fires! +### The Interrupt Fires When an interrupts is received, we use capture the time if the interrupt in `thisBitTCNT`. 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. (There's also a fudge factor in this calculation we call the [rxWindowWidth](https://github.com/SlashDevin/NeoSWSerial/pull/13#issuecomment-315463522).) - -[//]: # ( @subsection rx_mask_bit Bit by Bit ) -### Bit by Bit +### Bit by Bit For **each bit time that passed**, we apply the `rxMask` to the `rxValue`. + - 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. - We also leave time for the (high) start and (low) stop bit, but do anything with the `rxState`, `rxMask`, or `rxValue` for those bits. - -[//]: # ( @subsubsection rx_mask_low A LOW/1 Bit ) -#### A LOW/1 Bit +#### A LOW/1 Bit - if the data bit received is LOW (1) we do an `|=` (bitwise OR) between the `rxMask` and the `rxValue` +```unparsed +| rxValue: | 0 0 0 0 0 0 0 1 | +| -------- | ----------------------------- |^- bit-wise or puts the one +| rxMask: | 0 0 0 0 0 0 0 1 | from the rxMask into +| rxState: | 0 0 0 0 0 0 0 0 | the rxValue ``` - rxValue: | 0 0 0 0 0 0 0 1 --------------|---------------------------------^- bit-wise or puts the one - rxMask: | 0 0 0 0 0 0 0 1 from the rxMask into - rxState: | 0 0 0 0 0 0 0 0 the rxValue -``` - -[//]: # ( @subsubsection rx_mask_high A HIGH/0 Bit ) -#### A HIGH/0 Bit +#### A HIGH/0 Bit - if the data bit received is HIGH (0) we do nothing -``` - rxValue: | 0 0 0 0 0 0 0 0 --------------|---------------------------------x- nothing happens - rxMask: | 0 0 0 0 0 0 0 1 - rxState: | 0 0 0 0 0 0 0 0 +```unparsed +| rxValue: | 0 0 0 0 0 0 0 0 | +| -------- | ----------------------------- |x- nothing happens +| rxMask: | 0 0 0 0 0 0 0 1 | +| rxState: | 0 0 0 0 0 0 0 0 | ``` - -[//]: # ( @subsubsection rx_mask_shift Shifting Up ) -#### Shifting Up +#### Shifting Up - *After* applying the mask, we push everything over one bit to the left. The top bit falls off. @@ -133,33 +120,30 @@ The top bit falls off. - we always add a 0 on the `rxMask` and the `rxValue` - the values of the second bit of the `rxValue` (?) depends on what we did in the step above -``` - rxValue: | 0 <--- | 0 0 0 0 0 0 ? 0 <--- add a zero --------------|-------------------|---------------------------|--- - rxMask: | 0 <--- | 0 0 0 0 0 0 1 0 <--- add a zero - rxState: | 0 <--- | 0 0 0 0 0 0 0 1 <--- add a one --------------|-------------------|---------------------------|--- - | falls off the top | | added to the bottom +```unparsed +| rxValue: | 0 <--- | 0 0 0 0 0 0 ? 0 <--- add a zero | +| ----------------- | ------------------- | --------------------------------------------- | +| rxMask: | 0 <--- | 0 0 0 0 0 0 1 0 <--- add a zero | +| rxState: | 0 <--- | 0 0 0 0 0 0 0 1 <--- add a one | +| ----------------- | ------------------- | --------------------------------------------- | +| ----------------- | ^ falls off the top | ------- added to the bottom ^ | ``` - -[//]: # ( @subsection rx_mask_fin A Finished Character ) -### A Finished Character +### A Finished Character After 8 bit times have passed, we should have a fully formed character with 8 bits of data (7 of the character + 1 parity). The `rxMask` will have the one in the top bit. And the rxState will be filled - which just happens to be the value of `WAITING-FOR-START-BIT` for the next character. -``` - rxValue: | ? ? ? ? ? ? ? ? --------------|----------------------------------- - rxMask: | 1 0 0 0 0 0 0 0 - rxState: | 1 1 1 1 1 1 1 1 -``` +```unparsed +| rxValue: | ? ? ? ? ? ? ? ? | +| -------- | ----------------------------- | +| rxMask: | 1 0 0 0 0 0 0 0 | +| rxState: | 1 1 1 1 1 1 1 1 | +``` -[//]: # ( @section rx_fxn The Full Interrupt Function ) -## The Full Interrupt Function +## The Full Interrupt Function Understanding how the masking creates the character, you should now be able to follow the full interrupt function below. @@ -193,7 +177,7 @@ void SDI12::receiveISR() { // data, parity, or stop bit. // Check how many bit times have passed since the last change - uint16-t rxBits = bitTimes((uint8-t)(thisBitTCNT - prevBitTCNT)); + uint16-t rxBits = bitTimes(static_cast(thisBitTCNT - prevBitTCNT)); // Calculate how many *data+parity* bits should be left in the current character // - Each character has a total of 10 bits, 1 start bit, 7 data bits, 1 parity // bit, and 1 stop bit @@ -288,4 +272,4 @@ void SDI12::charToBuffer(uint8-t c) { -rxBufferTail = (-rxBufferTail + 1) % SDI12-BUFFER-SIZE; } } -``` \ No newline at end of file +``` diff --git a/docs/Doxyfile b/docs/Doxyfile index 7caadfc..4375af5 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -1,7 +1,7 @@ -# Doxyfile 1.8.19 +# Doxyfile 1.12.0 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. +# Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -51,26 +61,44 @@ PROJECT_BRIEF = "An Arduino library for SDI-12 communication without re # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = SDI-12Text.png +PROJECT_LOGO = gp-desktop-logo.png + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = enviroDIY_Favicon.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If +# entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = ../../Arduino-SDI-12Doxygen +OUTPUT_DIRECTORY = ../../Arduino-SDI-12_Doxygen -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. @@ -79,43 +107,35 @@ CREATE_SUBDIRS = NO ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this +# documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. -REPEAT_BRIEF = YES +REPEAT_BRIEF = NO # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found @@ -139,13 +159,13 @@ ABBREVIATE_BRIEF = "The $name class" \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief +# Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. @@ -153,7 +173,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. @@ -163,14 +183,14 @@ FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to +# If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. +# will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = ../src +STRIP_FROM_PATH = .. # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -181,14 +201,14 @@ STRIP_FROM_PATH = ../src STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief @@ -197,17 +217,17 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. +# interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) @@ -215,7 +235,7 @@ JAVADOC_BANNER = NO QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this @@ -225,7 +245,15 @@ QT_AUTOBRIEF = NO # not recognized any more. # The default value is: NO. -MULTILINE_CPP_IS_BRIEF = NO +MULTILINE_CPP_IS_BRIEF = YES + +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. @@ -233,7 +261,7 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. @@ -250,20 +278,20 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) - -ALIASES = "license=@par License:\n" \ - "environment=@par Development Environment:\n" \ - "platform=@par Hardware Platform:\n" \ +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = "license=@par License^^" \ + "environment=@par Development Environment^^" \ + "platform=@par Hardware Platform^^" \ "m_div{1}=@xmlonly@endxmlonly" \ "m_enddiv=@xmlonly@endxmlonly" \ "m_span{1}=@xmlonly@endxmlonly" \ @@ -275,7 +303,8 @@ ALIASES = "license=@par License:\n" \ "m_keyword{3}=@xmlonly@endxmlonly" \ "m_enum_values_as_keywords=@xmlonly@endxmlonly" \ "m_since{2}=@since @m_class{m-label m-success m-flat} @ref changelog-\1-\2 \"since v\1.\2\"" \ - "m_deprecated_since{2}=@since deprecated in v\1.\2 @deprecated" + "m_deprecated_since{3}=@since deprecated in v\1.\2.\3 @deprecated" \ + "m_innerpage{1}=@xmlonly @endxmlonly" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -317,27 +346,30 @@ OPTIMIZE_OUTPUT_SLICE = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files +# default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = ino=C++ -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. @@ -347,12 +379,23 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. +# Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 10 -# When enabled doxygen tries to link words that correspond to documented +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = GITHUB + +# When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. @@ -362,10 +405,10 @@ AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and +# tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO @@ -377,16 +420,16 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. +# Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. @@ -395,7 +438,7 @@ SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first +# tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. @@ -453,34 +496,42 @@ TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest +# symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you +# which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = YES + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. @@ -488,13 +539,13 @@ NUM_PROC_THREADS = 1 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = YES +EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. -EXTRACT_PRIVATE = YES +EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. @@ -539,7 +590,14 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. @@ -547,22 +605,23 @@ EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -576,30 +635,44 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) and Mac users are advised to set this option to NO. -# The default value is: system dependent. +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = NO -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -612,7 +685,7 @@ SHOW_INCLUDE_FILES = YES SHOW_GROUPED_MEMB_INC = YES -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. @@ -624,14 +697,14 @@ FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = NO -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. @@ -639,7 +712,7 @@ SORT_MEMBER_DOCS = NO SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. @@ -651,7 +724,7 @@ SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. @@ -666,13 +739,13 @@ SORT_GROUP_NAMES = NO # list. # The default value is: NO. -SORT_BY_SCOPE_NAME = NO +SORT_BY_SCOPE_NAME = YES -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. @@ -742,27 +815,28 @@ SHOW_FILES = YES SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from +# Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file +# by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated +# by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can +# that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = DoxygenLayout.xml +LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -774,19 +848,35 @@ LAYOUT_FILE = DoxygenLayout.xml CITE_BIB_FILES = +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the +# standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -794,51 +884,91 @@ QUIET = NO WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = YES -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. # The default value is: NO. -WARN_AS_ERROR = NO +WARN_IF_UNDOC_ENUM_VAL = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. -# The WARN_FORMAT tag determines the format of the warning messages that doxygen +WARN_AS_ERROR = FAIL_ON_WARNINGS_PRINT + +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). -WARN_LOGFILE = doxygenOutput.log +WARN_LOGFILE = output_doxygen.log #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -850,34 +980,45 @@ WARN_LOGFILE = doxygenOutput.log # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = ../README.md \ - ../src \ - ../docs +INPUT = .. # This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. +# read by Doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -889,15 +1030,14 @@ FILE_PATTERNS = *.c \ *.hxx \ *.hpp \ *.h++ \ + *.tpp \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ - *.doc \ - *.txt \ - *.tpp + *.doc # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -909,10 +1049,15 @@ RECURSIVE = YES # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # -# Note that relative paths are relative to the directory from which doxygen is +# Note that relative paths are relative to the directory from which Doxygen is # run. -EXCLUDE = ../src/ReadMe.md +EXCLUDE = ../src/ReadMe.md \ + ../docs/mermaidDiagrams.md \ + ../docs/dotDiagrams.dox \ + ../lib \ + ../boards \ + ../variants # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -934,10 +1079,7 @@ EXCLUDE_PATTERNS = # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = @@ -945,7 +1087,8 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = ../examples +EXAMPLE_PATH = ../examples \ + ../extras # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -973,7 +1116,7 @@ EXAMPLE_RECURSIVE = YES IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should +# The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # @@ -988,9 +1131,14 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. INPUT_FILTER = @@ -1003,7 +1151,7 @@ INPUT_FILTER = # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. +# properly processed by Doxygen. FILTER_PATTERNS = "*.md=python markdown_prefilter.py" @@ -1025,9 +1173,18 @@ FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. +# and want to reuse the introduction page also for the Doxygen output. + +USE_MDFILE_AS_MAINPAGE = -USE_MDFILE_AS_MAINPAGE = README.md +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1043,12 +1200,13 @@ USE_MDFILE_AS_MAINPAGE = README.md SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. @@ -1086,7 +1244,7 @@ REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. @@ -1100,14 +1258,14 @@ SOURCE_TOOLTIPS = YES # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # -# The result: instead of the source browser generated by doxygen, the links to +# The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. @@ -1115,20 +1273,28 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories +# the include paths will already be set by Doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. @@ -1139,10 +1305,10 @@ CLANG_OPTIONS = # file is the compilation database (see: # http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the # options used when the source files were built. This is equivalent to -# specifying the "-p" option to a clang tool, such as clang-check. These options +# specifying the -p option to a clang tool, such as clang-check. These options # will then be passed to the parser. Any options specified with CLANG_OPTIONS # will be added as well. -# Note: The availability of this option depends on whether or not doxygen was +# Note: The availability of this option depends on whether or not Doxygen was # generated with the -Duse_libclang=ON option for CMake. CLANG_DATABASE_PATH = @@ -1158,17 +1324,11 @@ CLANG_DATABASE_PATH = ALPHABETICAL_INDEX = NO -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1177,7 +1337,7 @@ IGNORE_PREFIX = # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1198,40 +1358,40 @@ HTML_OUTPUT = html HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a +# each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. +# that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally +# for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description +# default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard +# generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. +# that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. +# the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. +# sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. @@ -1241,13 +1401,18 @@ HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. +# created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1262,9 +1427,22 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. @@ -1274,7 +1452,7 @@ HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 198 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1292,15 +1470,6 @@ HTML_COLORSTYLE_SAT = 83 HTML_COLORSTYLE_GAMMA = 88 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1320,6 +1489,33 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = YES +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1335,10 +1531,11 @@ HTML_INDEX_NUM_ENTRIES = 500 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, Doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1355,6 +1552,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1377,14 +1581,18 @@ DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for @@ -1404,7 +1612,7 @@ CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. +# Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1438,6 +1646,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1456,7 +1674,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1464,8 +1683,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1473,16 +1692,16 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = @@ -1494,9 +1713,9 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1539,18 +1758,30 @@ DISABLE_INDEX = NO # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. +# Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. @@ -1559,6 +1790,12 @@ GENERATE_TREEVIEW = YES ENUM_VALUES_PER_LINE = 4 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = YES + # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -1566,14 +1803,21 @@ ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 200 -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. @@ -1586,24 +1830,13 @@ HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML +# Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1621,11 +1854,29 @@ FORMULA_MACROFILE = USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1638,33 +1889,40 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then +# For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically @@ -1683,7 +1941,7 @@ SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and +# setting. When disabled, Doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing # and searching needs to be provided by external tools. See the section # "External Indexing and Searching" for details. @@ -1692,7 +1950,7 @@ SEARCHENGINE = YES SERVER_BASED_SEARCH = NO -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# When EXTERNAL_SEARCH tag is enabled Doxygen will no longer generate the PHP # script for searching. Instead the search results are written to an XML file # which needs to be processed by an external indexer. Doxygen will invoke an # external search engine pointed to by the SEARCHENGINE_URL option to obtain the @@ -1700,7 +1958,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1713,8 +1972,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1735,7 +1995,7 @@ SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through Doxygen # projects other than the one defined by this configuration file, but that are # all added to the same external search index. Each project needs to have a # unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of @@ -1749,7 +2009,7 @@ EXTRA_SEARCH_MAPPINGS = # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = NO @@ -1794,7 +2054,7 @@ MAKEINDEX_CMD_NAME = makeindex LATEX_MAKEINDEX_CMD = makeindex -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# If the COMPACT_LATEX tag is set to YES, Doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1823,36 +2083,38 @@ PAPER_TYPE = a4 EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank Doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that Doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank Doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that Doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# by Doxygen. Using this option one can overrule certain style aspects. Doxygen # will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the @@ -1878,7 +2140,7 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# If the USE_PDFLATEX tag is set to YES, Doxygen will use the engine as # specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX # files. Set this option to YES, to get a higher quality PDF documentation. # @@ -1888,32 +2150,28 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_BATCHMODE = NO -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# If the LATEX_HIDE_INDICES tag is set to YES then Doxygen will not include the # index chapters (such as File Index, Compound Index, etc.) in the output. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. @@ -1922,14 +2180,6 @@ LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -1942,7 +2192,7 @@ LATEX_EMOJI_DIRECTORY = # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# If the GENERATE_RTF tag is set to YES, Doxygen will generate RTF output. The # RTF output is optimized for Word 97 and may not look too pretty with other RTF # readers/editors. # The default value is: NO. @@ -1957,7 +2207,7 @@ GENERATE_RTF = NO RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# If the COMPACT_RTF tag is set to YES, Doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1977,38 +2227,36 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's +# Load stylesheet definitions from file. Syntax is similar to Doxygen's # configuration file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. +# default style sheet that Doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be +# similar to Doxygen's configuration file. A template extensions file can be # generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_SOURCE_CODE = NO +RTF_EXTRA_FILES = #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, Doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -2039,7 +2287,7 @@ MAN_EXTENSION = .3 MAN_SUBDIR = -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, then it # will generate one additional man file for each entity documented in the real # man page(s). These additional files only source the real man page, but without # them the man command would be unable to find the correct page. @@ -2052,7 +2300,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# If the GENERATE_XML tag is set to YES, Doxygen will generate an XML file that # captures the structure of the code including all documentation. # The default value is: NO. @@ -2066,7 +2314,7 @@ GENERATE_XML = YES XML_OUTPUT = xml -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# If the XML_PROGRAMLISTING tag is set to YES, Doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size # of the XML output. @@ -2075,18 +2323,18 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = NO -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, Doxygen will include # namespace members in file scope as well, matching the HTML output. # The default value is: NO. # This tag requires that the tag GENERATE_XML is set to YES. -XML_NS_MEMB_FILE_SCOPE = NO +XML_NS_MEMB_FILE_SCOPE = YES #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, Doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -2100,21 +2348,12 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# If the GENERATE_AUTOGEN_DEF tag is set to YES, Doxygen will generate an +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. @@ -2125,11 +2364,33 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to Sqlite3 output #--------------------------------------------------------------------------- +# If the GENERATE_SQLITE3 tag is set to YES Doxygen will generate a Sqlite3 +# database with symbols found by Doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each Doxygen run. If set to NO, Doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# If the GENERATE_PERLMOD tag is set to YES, Doxygen will generate a Perl module # file that captures the structure of the code including all documentation. # # Note that this feature is still experimental and incomplete at the moment. @@ -2137,7 +2398,7 @@ GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# If the PERLMOD_LATEX tag is set to YES, Doxygen will generate the necessary # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI # output from the Perl module output. # The default value is: NO. @@ -2167,13 +2428,13 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, Doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# If the MACRO_EXPANSION tag is set to YES, Doxygen will expand all macro names # in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. @@ -2188,7 +2449,7 @@ MACRO_EXPANSION = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = YES +EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. @@ -2199,10 +2460,11 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = ../src # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2221,7 +2483,8 @@ INCLUDE_FILE_PATTERNS = *.c \ *.hpp \ *.h++ \ *.md \ - *.markdown + *.markdown \ + *.tpp # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -2231,13 +2494,15 @@ INCLUDE_FILE_PATTERNS = *.c \ # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = __attribute__((__progmem__))= \ +PREDEFINED = DOXYGEN=1 \ + __attribute__((__progmem__))= \ PLATFORMIO=40304 \ ARDUINO_AVR_ENVIRODIY_MAYFLY \ F_CPU=8000000L \ ARDUINO_ARCH_AVR \ ARDUINO=10808 \ __AVR_ATmega1284P__ \ + SERIAL_PORT_USBVIRTUAL=0 \ SDI12_EXTERNAL_PCINT \ ESP32 @@ -2250,7 +2515,7 @@ PREDEFINED = __attribute__((__progmem__))= \ EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# If the SKIP_FUNCTION_MACROS tag is set to YES then Doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have # an all uppercase name, and do not end with a semicolon. Such function macros # are typically used for boiler-plate code, and will confuse the parser if not @@ -2274,26 +2539,26 @@ SKIP_FUNCTION_MACROS = YES # section "Linking to external documentation" for more information about the use # of tag files. # Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is +# the path). If a tag file is not located in the directory in which Doxygen is # run, you must also specify the path to the tagfile here. TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# When a file name is specified after GENERATE_TAGFILE, Doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2307,42 +2572,26 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. -HIDE_UNDOC_RELATIONS = YES +HIDE_UNDOC_RELATIONS = NO -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# If you set the HAVE_DOT tag to YES then Doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. -HAVE_DOT = YES +HAVE_DOT = NO -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of +# The DOT_NUM_THREADS specifies the number of dot invocations Doxygen is allowed +# to run in parallel. When set to 0 Doxygen will base this on the number of # processors available in the system. You can set it explicitly to a value # larger than 0 to get control over the balance between CPU load and processing # speed. @@ -2351,55 +2600,83 @@ HAVE_DOT = YES DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# Doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. -CLASS_GRAPH = YES +CLASS_GRAPH = NO -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# If the COLLABORATION_GRAPH tag is set to YES then Doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -COLLABORATION_GRAPH = YES +COLLABORATION_GRAPH = NO -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -GROUP_GRAPHS = YES +GROUP_GRAPHS = NO -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# If the UML_LOOK tag is set to YES, Doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. @@ -2416,37 +2693,64 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, Doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -TEMPLATE_RELATIONS = YES +TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the +# YES then Doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -INCLUDE_GRAPH = YES +INCLUDE_GRAPH = NO # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing +# set to YES then Doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -INCLUDED_BY_GRAPH = YES +INCLUDED_BY_GRAPH = NO -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# If the CALL_GRAPH tag is set to YES then Doxygen will generate a call # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2458,7 +2762,7 @@ INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# If the CALLER_GRAPH tag is set to YES then Doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. @@ -2470,26 +2774,36 @@ CALL_GRAPH = NO CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -GRAPHICAL_HIERARCHY = YES +GRAPHICAL_HIERARCHY = NO -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# If the DIRECTORY_GRAPH tag is set to YES then Doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. -DIRECTORY_GRAPH = YES +DIRECTORY_GRAPH = NO + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 5 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2499,7 +2813,7 @@ DIRECTORY_GRAPH = YES # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_IMAGE_FORMAT = png +DOT_IMAGE_FORMAT = svg # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. @@ -2526,11 +2840,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in Doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2538,28 +2853,28 @@ MSCFILE_DIRS = DIAFILE_DIRS = -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. +# When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for PlantUML. PLANTUML_CFG_FILE = -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. +# When using PlantUML, the specified paths are searched for files specified by +# the !include statement in a PlantUML block. PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct +# larger than this value, Doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that if the number of direct # children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that # the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. @@ -2580,18 +2895,6 @@ DOT_GRAPH_MAX_NODES = 500 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2601,17 +2904,37 @@ DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES + +# You can define message sequence charts within Doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, Doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/docs/DoxygenLayout.xml b/docs/DoxygenLayout.xml deleted file mode 100644 index 6de61a2..0000000 --- a/docs/DoxygenLayout.xml +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/OverviewOfInterrupts.md b/docs/OverviewOfInterrupts.md index c6c3edb..1d6e597 100644 --- a/docs/OverviewOfInterrupts.md +++ b/docs/OverviewOfInterrupts.md @@ -1,9 +1,9 @@ -[//]: # ( @page interrupts_page Overview of Interrupts ) -# Overview of Interrupts +# Overview of Interrupts [//]: # ( @tableofcontents ) -[//]: # ( Start GitHub Only ) +[//]: # ( @cond GitHub ) + - [Overview of Interrupts](#overview-of-interrupts) - [What is an Interrupt?](#what-is-an-interrupt) - [Directly Controlling Interrupts on an AVR Board](#directly-controlling-interrupts-on-an-avr-board) @@ -11,10 +11,10 @@ - [Enabling an Interrupt](#enabling-an-interrupt) - [Disabling an Interrupt](#disabling-an-interrupt) -[//]: # ( End GitHub Only ) +[//]: # ( @endcond ) + +## What is an Interrupt? -[//]: # ( @section interrupts_what What is an Interrupt? ) -## What is an Interrupt? 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). After the ISR, program execution continues where it left off. 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. @@ -27,35 +27,36 @@ Obviously, we don't want the processor to be halting operation every time any pi 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. For AVR processors, like the Arduino Uno or the EnviroDIY Mayfly, we have to use a get a bit fancier to control the interrupts. -[//]: # ( @section interrupts_avr Directly Controlling Interrupts on an AVR Board ) -## Directly Controlling Interrupts on an AVR Board +## Directly Controlling Interrupts on an AVR Board -[//]: # ( @subsection interrupts_vocab Some Vocabulary ) -### Some Vocabulary: +### Some Vocabulary: **Registers**: small 1-byte (8-bit) stores of memory directly accessible by processor PCMSK0, PCMSK1, PCMSK2, PCMSK3 -**PCICRx**: a register where the three least significant bits enable or disable pin change interrupts on a range of pins +`PCICRx`: a register where the three least significant bits enable or disable pin change interrupts on a range of pins + - i.e. {0,0,0,0,0,PCIE2,PCIE1,PCIE0}, where PCIE2 maps to PCMSK2, PCIE1 maps to PCMSK1, and PCIE0 maps to PCMSK0. -**PCMSKx**: a register that stores the state (enabled/disabled) of pin change interrupts on a single pin +`PCMSKx`: a register that stores the state (enabled/disabled) of pin change interrupts on a single pin + - Each bit stores a 1 (enabled) or 0 (disabled). On an Arduino Uno: + - There is on PCICR register controlling three ranges of pins - There are three mask registers (PCMSK0, PCMSK1, and PCMSK2) controlling individual pins. - Looking at one mask register, PCMSK0: - - the 8 bits represent: PCMSK0 {PCINT7, PCINT6, PCINT5, PCINT4, PCINT3, PCINT2, PCINT1, PCINT0} - - these map to: PCMSK0 {XTAL2, XTAL1, Pin 13, Pin 12, Pin 11, Pin 10, Pin 9, Pin 8} + - the 8 bits represent: PCMSK0 {PCINT7, PCINT6, PCINT5, PCINT4, PCINT3, PCINT2, PCINT1, PCINT0} + - these map to: PCMSK0 {XTAL2, XTAL1, Pin 13, Pin 12, Pin 11, Pin 10, Pin 9, Pin 8} + +`noInterrupts()`: a function to globally disable interrupts (of all types) -**noInterrupts()**: a function to globally disable interrupts (of all types) +`interrupts()`: a function to globally enable interrupts (of all types) -**interrupts()**: a function to globally enable interrupts (of all types) - interrupts will only occur if the requisite registers are set (e.g. PCMSK and PCICR). -[//]: # ( @subsection interrupts_enable Enabling an Interrupt ) -### Enabling an Interrupt +### Enabling an Interrupt Initially, no interrupts are enabled, so PCMSK0 looks like: `{00000000}`. 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}`. @@ -92,15 +93,15 @@ Or equivalently: `(1<<1)`, we get: `{00000010}`. To use the mask to set the bit of interest we use the bitwise or operator `|`. We will use the compact `|=` notation which does the operation and then stores the result back into the left hand side. - So the operation: ```cpp *digitalPinToPCMSK(_dataPin) |= (1< When the we would like to put the SDI-12 object in the DISABLED state, (e.g. the destructor is called), we need to make sure the bit corresponding to the data pin is unset. -Let us consider again the case of where an interrupt has been enabled on pin 13: {00100010}. +Let us consider again the case of where an interrupt has been enabled on pin 13: `{00100010}`. We want to be sure not to disturb this interrupt when disabling the interrupt on pin 9. We will make use of similar macros, but this time we will use an inverted bit mask and the AND operation. @@ -156,7 +153,7 @@ The inversion symbol `~` modifies the result to `{11111101}` So to finish our example: -``` +```unparsed ~(1<12ms ± 0.4ms (ie, >12.5ms) + - maximum recorder marking (LOW) before a start bit (HIGH) = >8.33ms ± 0.4ms (ie, >8.73ms) + - maximum time before relinquishing line control after stop bit = 7.5ms ± 0.4ms + - maximum marking (LOW) before a new waking break (HIGH) must be issued = 87ms ± 0.4ms + - A break is also required when switching between sensors + +- Retry Times + - There are two retry "loops" - an "inner" loop of retries without breaks between and an "outer" loop of retries with breaks in between + - Inner Retries (*without* breaks between) + - Response window before a retry = 16.67ms - 87ms (< 87ms = time before a break is required) + - A minimum of 3 "inner" retries are required. + - At least one of the "inner" retries must start >100ms after the falling edge (end) of the break that started the innter retry loop. + - Outer Retries (*with* breaks between) + - Outer retries are used after >112.5ms of inner retries have been attempted + - A minimum of 3 "outer" retries are required. + +## Ideal Timer Settings + +When setting up our timers, the goal is to be able to have as many ticks as possible for each bit. +The more ticks we have, the better job we can do with the needed averaging and fudging. +With <10 ticks/bit, we probably won't be accurate enough to be functional. +But, we also need to make sure that the clock timer doesn't roll over before the end of the 8.33ms required for each character. +When acting as a recording device, it would be even better if the timer could last until the end of a 112.5ms retry timer before rolling over. +There is no benefit to the timer lasting longer than 112.5ms before rolling over. + +Each timer has finite options for pre-scaling, often in powers of 2. +To catch all the bits we need, when selecting the prescaler, we must round **UP** to the next closest available prescaler number (round **DOWN** the Hz) to give us *more* ticks than required. + +Using a 16 bit counter, the counter rolls after 65536 ticks. + +- Going for the maximum retry time, 112.5ms / 65536 ticks + - 1.71661376953125 µsec/tick, 582.54222 kHz + - 485.25767 ticks/bit + - This is *plenty* of ticks per bit! With a 16-bit timer, there's no reason to not use this whole period. +- Going for the minimum 8.33ms per character, 8.33ms / 65536 ticks + - 0.127105712890625 µsec/tick, 7.86747 MHz + - 6553.6 ticks/bit + - This is overkill. + +- Conclusion: With a 16-bit timer, select the smallest prescaler possible that keeps the speed *below* 582 kHz + +If we only have an 8 bit timer, the counter rolls after 256 ticks. + +- Going for the maximum retry time, 112.5ms / 256 ticks + - 0.439453125 msec/tick, 2.275 kHz + - 1.89554 ticks/bit + - This is no where near enough bits / tick for accuracy, so it is not possible to keep a timer running for the 112.5 ms retry with a 8-bit timer. +- Going for the minimum 8.33ms per character, 8.33ms / 256 ticks = 25.6 ticks/bit + - 0.0325390625 msec/tick, 30.73229 kHz + - 25.6 ticks/bit + +- Conclusion: With a 8-bit timer, select the smallest prescaler possible that keeps the speed *below* 30 kHz + +## AVR Boards + +### Available Timers on AVR Boards + +#### ATmega AVR Available Timers + +[ATmega164A/PA/324A/PA/644A/PA/1284/P](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8272-8-bit-AVR-microcontroller-ATmega164A_PA-324A_PA-644A_PA-1284_P_datasheet.pdf) + +> +> - Up to 20MIPS throughput at 20MHz +> - Most Arduino boards are run at 16 or 8 MHz with a few at 12 MHz +> - Two 8-bit Timer/Counters with Separate Prescalers and Compare Modes +> - Timers 0 and 2 +> - Prescalers available at 8/64/256/1024 on Timer 0 +> - Prescalers available at 8/32/64/128/256/1024 on Timer 2 +> - One/two 16-bit Timer/Counter with Separate Prescaler, Compare Mode, and Capture Mode +> - Timers 1 and 3 +> - Prescalers available at 8/64/256/1024 on Timers 1 and 3 +> - Timer 3 is only available on the 1284p + +[ATmega640/V-1280/V-1281/V-2560/V-2561/V](https://ww1.microchip.com/downloads/en/devicedoc/atmel-2549-8-bit-avr-microcontroller-atmega640-1280-1281-2560-2561_datasheet.pdf) + +> +> - Up to 16 MIPS Throughput at 16MHz +> – Two 8-bit Timer/Counters with Separate Prescaler and Compare Mode +> - Timers 0 and 2 +> - Prescalers available at 8/64/256/1024 on Timer 0 +> - Prescalers available at 8/32/64/128/256/1024 on Timer 2 +> – Four 16-bit Timer/Counters with Separate Prescaler, Compare- and Capture Mode +> - Timers 1, 3, 4, and 5 +> - Prescalers available at 8/64/256/1024 on Timer 1, 3, 4, and 5 + +#### ATtiny AVR Available Timers + +[ATtiny25/V / ATtiny45/V / ATtiny85/V](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf) + +> +> - Up to 20MIPS throughput at 20MHz +> - Most Arduino boards are run at 16 or 8 MHz with a few at 12 MHz +> – One 8-bit Timer/Counter with Prescaler and Two PWM Channels +> - Timer 0 +> - Prescalers available at 8/64/256/1024 +> – One 8-bit High Speed Timer/Counter with Separate Prescaler +> - Timer 1 +> - Prescalers available at 64/128/256/512/1024/2048/4096/8192/16384 + +#### ATmegaXU AVR Available Timers + +[ATmega16U4/ATmega32U4](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7766-8-bit-AVR-ATmega16U4-32U4_Datasheet.pdf) + +> +> – Up to 16 MIPS Throughput at 16MHz +> – One 8-bit Timer/Counter with Separate Prescaler and Compare Mode +> - Timer 0 +> - Prescalers available at 8/64/256/1024 on Timer 0 +> – Two 16-bit Timer/Counter with Separate Prescaler, Compare- and Capture Mode +> - Timers 1 and 3 +> - Prescalers available at 8/64/256/1024 on Timer 1 and 3 +> – One 10-bit High-Speed Timer/Counter with PLL (64MHz) and Compare Mode +> - Timer 4 +> - Prescalers available at 2/4/8/16/32/64/128/256/512/1024/2048/8192/169384 on Timer 4 + +> [!NOTE] +> There is no Timer 2 on the 16U4 or the 32U4 + +### Timers Used by Arduino AVR Core + +- Timer 0 + - [The primary clock (millis)](https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c) + - Fast hardware PWM +- Timer 1 + - Phase-correct hardware PWM + - [AltSoftSerial](https://github.com/PaulStoffregen/AltSoftSerial/) + - Primary for [Servo](https://github.com/arduino-libraries/Servo) +- Timer 2 + - Primary for [Tone](https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Tone.cpp) (except on the 16U4/32U4) + - Phase-correct hardware PWM + - [NeoSWSerial](https://github.com/SlashDevin/NeoSWSerial/) +- Timer 3 + - Primary for [Tone](https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Tone.cpp) (only on the 16U4/32U4) + - Optional for [Tone](https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Tone.cpp) on some boards + - Optional for [Servo](https://github.com/arduino-libraries/Servo) on some boards +- Timer 4 + - Optional for [Tone](https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Tone.cpp) on some boards + - Optional for [Servo](https://github.com/arduino-libraries/Servo) on some boards +- Timer 5 + - Optional for [Tone](https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Tone.cpp) on some boards + - Optional for [Servo](https://github.com/arduino-libraries/Servo) on some boards + +### Selected AVR Timers for SDI-12 + +#### ATmega AVR Selected Timers + +For simplicity, we use Timer/Counter 2 for both ATmega164A/PA/324A/PA/644A/PA/1284/P and ATmega640/V-1280/V-1281/V-2560/V-2561/V series boards. + +> +> Timer/Counter2 (TC2) is a general purpose, single channel, 8-bit Timer/Counter module. +> +> **Features of Timer/Counter 2** +> +> - Single Channel Counter +> - Clear Timer on Compare Match (Auto Reload) +> - Glitch-free, Phase Correct Pulse Width Modulator (PWM) +> - Frequency Generator +> - 10-bit Clock Prescaler +> - Overflow and Compare Match Interrupt Sources (TOV2, OCF2A, and OCF2B) +> - Allows Clocking from External 32kHz Watch Crystal Independent of the I/O Clock + +#### ATtiny AVR Selected Timers + +On the ATTiny series (ATtiny25/V / ATtiny45/V / ATtiny85/V) boards, we use Timer/Counter 1 + +> +> The Timer/Counter1 features a high resolution and a high accuracy usage with the lower prescaling opportunities. +> It can also support two accurate, high speed, 8-bit pulse width modulators using clock speeds up to 64MHz (or 32MHz in low speedmode). + +#### ATmegaXU Selected Timers + +On the AtMega16U4 and AtMega32U4, we use Timer/Counter 4 as an 8-bit timer. + +> +> Timer/Counter4 is a general purpose high speed Timer/Counter module, with three independent Output Compare Units, and with enhanced PWM support. +> +> **Features of Timer/Counter 4** +> +> - Up to 10-Bit Accuracy +> - Three Independent Output Compare Units +> - Clear Timer on Compare Match (Auto Reload) +> - Glitch Free, Phase and Frequency Correct Pulse Width Modulator (PWM) +> - Enhanced PWM mode: one optional additional accuracy bit without effect on output frequency +> - Variable PWM Period +> - Independent Dead Time Generators for each PWM channels +> - Synchronous update of PWM registers +> - Five Independent Interrupt Sources (TOV4, OCF4A, OCF4B, OCF4D, FPF4) +> - High Speed Asynchronous and Synchronous Clocking Modes +> - Separate Prescaler Unit + +> [!NOTE] +> We only utilize the low byte register of Timer 4, effectively using the 10-bit timer as an 8-bit timer. + +## SAMD Boards + +### SAMD21 + +#### Available Clocks and Timers on SAMD21 Boards + +##### SAMD21 Generic Clock Generators + +> +> The Generic Clock controller GCLK provides nine Generic Clock Generators that can provide a wide range of clock frequencies. +> Generators can be set to use different external and internal oscillators as source. +> The clock of each Generator can be divided. +> The outputs from the Generators are used as sources for the Generic Clock Multiplexers, which provide the Generic Clock (GCLK_PERIPHERAL) to the peripheral modules, as shown in Generic Clock Controller Block Diagram. +> +> **Features of the Generic Clock Generator** +> +> - Provides Generic Clocks +> - Wide frequency range +> - Clock source for the generator can be changed on the fly + +##### SAMD21 Timer Controllers + +> +> The TC consists of a counter, a prescaler, compare/capture channels and control logic. +> The counter can be set to count events, or it can be configured to count clock pulses. +> The counter, together with the compare/capture channels, can be configured to timestamp input events, allowing capture of frequency and pulse width. +> It can also perform waveform generation, such as frequency generation and pulse-width modulation (PWM). +> +> **Features of the Timer Controller** +> +> - Selectable configuration +> – Up to five 16-bit Timer/Counters (TC) including one low-power TC, each configurable as: +> - 8-bit TC with two compare/capture channels +> - 16-bit TC with two compare/capture channels +> - 32-bit TC with two compare/capture channels, by using two TCs +> - Waveform generation +> – Frequency generation +> – Single-slope pulse-width modulation +> - Input capture +> – Event capture +> – Frequency capture +> – Pulse-width capture +> - One input event +> - Interrupts/output events on: +> – Counter overflow/underflow +> – Compare match or capture +> - Internal prescaler +> - Can be used with DMA and to trigger DMA transactions + +#### Timers Used by Arduino SAMD21 Core + +The Adafruit Arduino core uses: + +- 0 as GENERIC_CLOCK_GENERATOR_MAIN (the main clock) + + +The Adafruit Arduino core uses: + +- TC5 for Tone +- TC4 for Servo + +#### Selected SAMD21 Timers for SDI-12 + +For SDI-12, we'll use Generic Clock Generator 4 and Timer Controller 3 + +### SAMD51/SAME51 + +#### Available Clocks and Timers on SAMD51 Boards + +##### SAMD51 Generic Clock Generators + +> +> Depending on the application, peripherals may require specific clock frequencies to operate correctly. +> The Generic Clock controller (GCLK) features 12 Generic Clock Generators [11:0] that can provide a wide range of clock frequencies. +> +> Generators can be set to use different external and internal oscillators as source. +> The clock of each Generator can be divided. +> The outputs from the Generators are used as sources for the Peripheral Channels, which provide the Generic Clock (GCLK_PERIPH) to the peripheral modules, as shown in Figure 14-2. +> The number of Peripheral Clocks depends on how many peripherals the device has. +> +> NOTE: The Generator 0 is always the direct source of the GCLK_MAIN signal. +> +> **Features of the Generic Clock Generators** +> +> - Provides a device-defined, configurable number of Peripheral Channel clocks +> - Wide frequency range +> - Various clock sources +> - Embedded dividers + +##### SAMD51 Timer Controllers + +> +> There are up to eight TC peripheral instances. +> +> Each TC consists of a counter, a prescaler, compare/capture channels and control logic. +> The counter can be set to count events, or clock pulses. +> The counter, together with the compare/capture channels, can be configured to timestamp input events or IO pin edges, allowing for capturing of frequency and/or pulse width. +> +> A TC can also perform waveform generation, such as frequency generation and pulse-width modulation. +> +> **Features of the Timer Controllers** +> +> - Selectable configuration +> - 8-, 16- or 32-bit TC operation, with compare/capture channels +> - 2 compare/capture channels (CC) with: +> - Double buffered timer period setting (in 8-bit mode only) +> - Double buffered compare channel +> - Waveform generation +> - Frequency generation +> - Single-slope pulse-width modulation +> - Input capture +> - Event / IO pin edge capture +> - Frequency capture +> - Pulse-width capture +> - Time-stamp capture +> - Minimum and maximum capture +> - One input event +> - Interrupts/output events on: +> - Counter overflow/underflow +> - Compare match or capture +> - Internal prescaler +> - DMA support + +#### Timers Used by Arduino SAMD51 Core + +The Adafruit Arduino core uses: + +- 0 as GENERIC_CLOCK_GENERATOR_MAIN (the main clock, sourced from MAIN_CLOCK_SOURCE = GCLK_GENCTRL_SRC_DPLL0) +- 1 as GENERIC_CLOCK_GENERATOR_48M (48MHz clock for USB and 'stuff', sourced from GCLK_GENCTRL_SRC_DPLL0) +- 2 as GENERIC_CLOCK_GENERATOR_100M (100MHz clock for other peripherals, sourced from GCLK_GENCTRL_SRC_DPLL1) +- 3 as GENERIC_CLOCK_GENERATOR_XOSC32K (32kHz oscillator, sourced from 32kHz external oscillator GCLK_GENCTRL_SRC_XOSC32K) +- 4 as GENERIC_CLOCK_GENERATOR_12M (12MHz clock for DAC, sourced from GCLK_GENCTRL_SRC_DPLL0) +- 5 as GENERIC_CLOCK_GENERATOR_1M (??, sourced from CLK_GENCTRL_SRC_DPLL0) + + +The Adafruit Arduino core uses: + +- TC0 for Tone (though any other timer may be used, if another pin is selected) +- TC1 for Servo (though any other timer may be used, if another pin is selected) + +#### Selected SAMD51 Timers for SDI-12 + +For SDI-12, we'll use Generic Clock Generator 6 and Timer Controller 2 + +## Other Boards + +For sufficiently fast boards, instead of using a dedicated processor timer, we can use the built-in `micros()` function as the timer. + +From calculations using https://github.com/SRGDamia1/avrcycle, the micros() function takes about 60 (!!) clock cycles on a Mayfly. +We're going to blindly assume that the micros() function takes up about the same number of clock cycles for all Arduino boards. +This is probably a huge assumption, but go with it. +If we're going to use micros() fortiming, lets set a minimum usable CPU speed of the micros() function being accurate to 1µs. +That means we need to get 60 ticks/1µs or 60MHz. +Ehh.. Maybe we'll be generous and allow it down to 48MHz in the code. +That will allow Rensas AVR processors to attempt SDI-12. + +I know from testing, that we *cannot* use micros on a board 8MHz AVR board, but that it does work on a 80MHz Espressif8266. + +> [!WARNING] +> I haven't actually tested the minimum speed that this will work at! + +@todo: Test 48MHz + +Both the ESP8266 and ESP32 are definitely fast enough that this works. + +- The ESP8266 runs at either 80 or 160 MHz +- The ESP32 runs at 160 or 240 MHz. + +All of the other processors using the Arduino core also have the micros function, but the rest are not fast enough to waste the processor cycles to use the micros function and must manually configure the processor timer and use the faster assembly macros to read that processor timer directly. diff --git a/docs/css/m-EnviroDIY+documentation.compiled.css b/docs/css/m-EnviroDIY+documentation.compiled.css deleted file mode 100644 index 1008522..0000000 --- a/docs/css/m-EnviroDIY+documentation.compiled.css +++ /dev/null @@ -1,4316 +0,0 @@ -/* Generated using `./postprocess.py m-EnviroDIY.css m-documentation.css -o m-EnviroDIY+documentation.compiled.css`. Do not edit. */ - -/* - This file is part of m.css. - - Copyright © 2017, 2018, 2019, 2020 Vladimír Vondruš - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -*/ - -*, ::before, ::after { box-sizing: border-box; } -body { margin: 0; } -.m-container { - width: 100%; - margin: auto; - padding-left: 1rem; - padding-right: 1rem; -} -.m-row { - margin-left: -1rem; - margin-right: -1rem; -} -.m-row:after { - content: ' '; - clear: both; - display: table; -} -.m-row > [class*='m-col-'] { - position: relative; - padding: 1rem; -} -[class*='m-clearfix-']::after { - display: block; - content: ' '; - clear: both; -} -[class*='m-show-'] { - display: none; -} -.m-container-inflate, :not(.m-row) > [class*='m-col-'] { - margin-bottom: 1rem; -} -.m-container-inflate:last-child, :not(.m-row) > [class*='m-col-']:last-child { - margin-bottom: 0; -} -.m-container.m-nopad, [class*='m-col-'].m-nopad, -.m-container.m-nopadx, [class*='m-col-'].m-nopadx, -.m-container.m-nopadl, [class*='m-col-'].m-nopadl { - padding-left: 0; -} -.m-container.m-nopad, [class*='m-col-'].m-nopad, -.m-container.m-nopadx, [class*='m-col-'].m-nopadx, -.m-container.m-nopadr, [class*='m-col-'].m-nopadr { - padding-right: 0; -} -[class*='m-col-'].m-nopad, [class*='m-col-'].m-nopady, [class*='m-col-'].m-nopadt { - padding-top: 0; -} -[class*='m-col-'].m-nopad, [class*='m-col-'].m-nopady, [class*='m-col-'].m-nopadb, -.m-container-inflate.m-nopadb { - padding-bottom: 0; -} -[class*='m-col-t-'] { float: left; } -.m-left-t { - padding-right: 1rem; - float: left; -} -.m-right-t, [class*='m-col-t-'].m-right-t { - padding-left: 1rem; - float: right; -} -.m-center-t, [class*='m-col-t-'].m-center-t { - float: none; -} -.m-center-t, [class*='m-col-t-'].m-center-t { - margin-left: auto; - margin-right: auto; - float: none; -} -.m-col-t-1 { width: calc(1 * 100% / 12); } -.m-col-t-2 { width: calc(2 * 100% / 12); } -.m-col-t-3 { width: calc(3 * 100% / 12); } -.m-col-t-4 { width: calc(4 * 100% / 12); } -.m-col-t-5 { width: calc(5 * 100% / 12); } -.m-col-t-6 { width: calc(6 * 100% / 12); } -.m-col-t-7 { width: calc(7 * 100% / 12); } -.m-col-t-8 { width: calc(8 * 100% / 12); } -.m-col-t-9 { width: calc(9 * 100% / 12); } -.m-col-t-10 { width: calc(10 * 100% / 12); } -.m-col-t-11 { width: calc(11 * 100% / 12); } -.m-col-t-12 { width: calc(12 * 100% / 12); } -.m-push-t-1 { left: calc(1 * 100% / 12); } -.m-push-t-2 { left: calc(2 * 100% / 12); } -.m-push-t-3 { left: calc(3 * 100% / 12); } -.m-push-t-4 { left: calc(4 * 100% / 12); } -.m-push-t-5 { left: calc(5 * 100% / 12); } -.m-push-t-6 { left: calc(6 * 100% / 12); } -.m-push-t-7 { left: calc(7 * 100% / 12); } -.m-push-t-8 { left: calc(8 * 100% / 12); } -.m-push-t-9 { left: calc(9 * 100% / 12); } -.m-push-t-10 { left: calc(10 * 100% / 12); } -.m-push-t-11 { left: calc(11 * 100% / 12); } -.m-pull-t-1 { right: calc(1 * 100% / 12); } -.m-pull-t-2 { right: calc(2 * 100% / 12); } -.m-pull-t-3 { right: calc(3 * 100% / 12); } -.m-pull-t-4 { right: calc(4 * 100% / 12); } -.m-pull-t-5 { right: calc(5 * 100% / 12); } -.m-pull-t-6 { right: calc(6 * 100% / 12); } -.m-pull-t-7 { right: calc(7 * 100% / 12); } -.m-pull-t-8 { right: calc(8 * 100% / 12); } -.m-pull-t-9 { right: calc(9 * 100% / 12); } -.m-pull-t-10 { right: calc(10 * 100% / 12); } -.m-pull-t-11 { right: calc(11 * 100% / 12); } -@media screen and (min-width: 576px) { - .m-container { width: 560px; } - .m-container-inflatable .m-col-s-10 .m-container-inflate:not([class*='m-left-']):not([class*='m-right-']) { - margin-left: -10%; - margin-right: -10%; - } - .m-container-inflatable .m-col-s-10 .m-container-inflate.m-left-s { - margin-left: -10%; - } - .m-container-inflatable .m-col-s-10 .m-container-inflate.m-right-s { - margin-right: -10%; - } - [class*='m-col-s-'] { float: left; } - .m-left-s { - padding-right: 1rem; - float: left; - } - .m-right-s, [class*='m-col-s-'].m-right-s { - padding-left: 1rem; - float: right; - } - .m-center-s, [class*='m-col-s-'].m-center-s { - margin-left: auto; - margin-right: auto; - float: none; - } - .m-col-s-1 { width: calc(1 * 100% / 12); } - .m-col-s-2 { width: calc(2 * 100% / 12); } - .m-col-s-3 { width: calc(3 * 100% / 12); } - .m-col-s-4 { width: calc(4 * 100% / 12); } - .m-col-s-5 { width: calc(5 * 100% / 12); } - .m-col-s-6 { width: calc(6 * 100% / 12); } - .m-col-s-7 { width: calc(7 * 100% / 12); } - .m-col-s-8 { width: calc(8 * 100% / 12); } - .m-col-s-9 { width: calc(9 * 100% / 12); } - .m-col-s-10 { width: calc(10 * 100% / 12); } - .m-col-s-11 { width: calc(11 * 100% / 12); } - .m-col-s-12 { width: calc(12 * 100% / 12); } - .m-push-s-0 { left: calc(0 * 100% / 12); } - .m-push-s-1 { left: calc(1 * 100% / 12); } - .m-push-s-2 { left: calc(2 * 100% / 12); } - .m-push-s-3 { left: calc(3 * 100% / 12); } - .m-push-s-4 { left: calc(4 * 100% / 12); } - .m-push-s-5 { left: calc(5 * 100% / 12); } - .m-push-s-6 { left: calc(6 * 100% / 12); } - .m-push-s-7 { left: calc(7 * 100% / 12); } - .m-push-s-8 { left: calc(8 * 100% / 12); } - .m-push-s-9 { left: calc(9 * 100% / 12); } - .m-push-s-10 { left: calc(10 * 100% / 12); } - .m-push-s-11 { left: calc(11 * 100% / 12); } - .m-pull-s-0 { right: calc(0 * 100% / 12); } - .m-pull-s-1 { right: calc(1 * 100% / 12); } - .m-pull-s-2 { right: calc(2 * 100% / 12); } - .m-pull-s-3 { right: calc(3 * 100% / 12); } - .m-pull-s-4 { right: calc(4 * 100% / 12); } - .m-pull-s-5 { right: calc(5 * 100% / 12); } - .m-pull-s-6 { right: calc(6 * 100% / 12); } - .m-pull-s-7 { right: calc(7 * 100% / 12); } - .m-pull-s-8 { right: calc(8 * 100% / 12); } - .m-pull-s-9 { right: calc(9 * 100% / 12); } - .m-pull-s-10 { right: calc(10 * 100% / 12); } - .m-pull-s-11 { right: calc(11 * 100% / 12); } - .m-clearfix-t::after { display: none; } - .m-hide-s { display: none; } - .m-show-s { display: block; } - .m-col-s-none { - width: auto; - float: none; - } -} -@media screen and (min-width: 768px) { - .m-container { width: 750px; } - .m-container-inflatable .m-col-m-10 .m-container-inflate:not([class*='m-left-']):not([class*='m-right-']) { - margin-left: -10%; - margin-right: -10%; - } - .m-container-inflatable .m-col-m-10 .m-container-inflate.m-left-m { - margin-left: -10%; - } - .m-container-inflatable .m-col-m-10 .m-container-inflate.m-right-m { - margin-right: -10%; - } - [class*='m-col-m-'] { float: left; } - .m-left-m { - padding-right: 1rem; - float: left; - } - .m-right-m, [class*='m-col-m-'].m-right-m { - padding-left: 1rem; - float: right; - } - .m-center-m, [class*='m-col-m-'].m-center-m { - margin-left: auto; - margin-right: auto; - float: none; - } - .m-col-m-1 { width: calc(1 * 100% / 12); } - .m-col-m-2 { width: calc(2 * 100% / 12); } - .m-col-m-3 { width: calc(3 * 100% / 12); } - .m-col-m-4 { width: calc(4 * 100% / 12); } - .m-col-m-5 { width: calc(5 * 100% / 12); } - .m-col-m-6 { width: calc(6 * 100% / 12); } - .m-col-m-7 { width: calc(7 * 100% / 12); } - .m-col-m-8 { width: calc(8 * 100% / 12); } - .m-col-m-9 { width: calc(9 * 100% / 12); } - .m-col-m-10 { width: calc(10 * 100% / 12); } - .m-col-m-11 { width: calc(11 * 100% / 12); } - .m-col-m-12 { width: calc(12 * 100% / 12); } - .m-push-m-0 { left: calc(0 * 100% / 12); } - .m-push-m-1 { left: calc(1 * 100% / 12); } - .m-push-m-2 { left: calc(2 * 100% / 12); } - .m-push-m-3 { left: calc(3 * 100% / 12); } - .m-push-m-4 { left: calc(4 * 100% / 12); } - .m-push-m-5 { left: calc(5 * 100% / 12); } - .m-push-m-6 { left: calc(6 * 100% / 12); } - .m-push-m-7 { left: calc(7 * 100% / 12); } - .m-push-m-8 { left: calc(8 * 100% / 12); } - .m-push-m-9 { left: calc(9 * 100% / 12); } - .m-push-m-10 { left: calc(10 * 100% / 12); } - .m-push-m-11 { left: calc(11 * 100% / 12); } - .m-pull-m-0 { right: calc(0 * 100% / 12); } - .m-pull-m-1 { right: calc(1 * 100% / 12); } - .m-pull-m-2 { right: calc(2 * 100% / 12); } - .m-pull-m-3 { right: calc(3 * 100% / 12); } - .m-pull-m-4 { right: calc(4 * 100% / 12); } - .m-pull-m-5 { right: calc(5 * 100% / 12); } - .m-pull-m-6 { right: calc(6 * 100% / 12); } - .m-pull-m-7 { right: calc(7 * 100% / 12); } - .m-pull-m-8 { right: calc(8 * 100% / 12); } - .m-pull-m-9 { right: calc(9 * 100% / 12); } - .m-pull-m-10 { right: calc(10 * 100% / 12); } - .m-pull-m-11 { right: calc(11 * 100% / 12); } - .m-clearfix-s::after { display: none; } - .m-hide-m { display: none; } - .m-show-m { display: block; } - .m-col-m-none { - width: auto; - float: none; - } -} -@media screen and (min-width: 992px) { - .m-container { width: 960px; } - .m-container-inflatable .m-col-l-10 .m-container-inflate:not([class*='m-left-']):not([class*='m-right-']) { - margin-left: -10%; - margin-right: -10%; - } - .m-container-inflatable .m-col-l-10 .m-container-inflate.m-left-l { - margin-left: -10%; - } - .m-container-inflatable .m-col-l-10 .m-container-inflate.m-right-l { - margin-right: -10%; - } - [class*='m-col-l-'] { float: left; } - .m-left-l { - padding-right: 1rem; - float: left; - } - .m-right-l, [class*='m-col-l-'].m-right-l { - padding-left: 1rem; - float: right; - } - .m-center-l, [class*='m-col-l-'].m-center-l { - margin-left: auto; - margin-right: auto; - float: none; - } - .m-col-l-1 { width: calc(1 * 100% / 12); } - .m-col-l-2 { width: calc(2 * 100% / 12); } - .m-col-l-3 { width: calc(3 * 100% / 12); } - .m-col-l-4 { width: calc(4 * 100% / 12); } - .m-col-l-5 { width: calc(5 * 100% / 12); } - .m-col-l-6 { width: calc(6 * 100% / 12); } - .m-col-l-7 { width: calc(7 * 100% / 12); } - .m-col-l-8 { width: calc(8 * 100% / 12); } - .m-col-l-9 { width: calc(9 * 100% / 12); } - .m-col-l-10 { width: calc(10 * 100% / 12); } - .m-col-l-11 { width: calc(11 * 100% / 12); } - .m-col-l-12 { width: calc(12 * 100% / 12); } - .m-push-l-0 { left: calc(0 * 100% / 12); } - .m-push-l-1 { left: calc(1 * 100% / 12); } - .m-push-l-2 { left: calc(2 * 100% / 12); } - .m-push-l-3 { left: calc(3 * 100% / 12); } - .m-push-l-4 { left: calc(4 * 100% / 12); } - .m-push-l-5 { left: calc(5 * 100% / 12); } - .m-push-l-6 { left: calc(6 * 100% / 12); } - .m-push-l-7 { left: calc(7 * 100% / 12); } - .m-push-l-8 { left: calc(8 * 100% / 12); } - .m-push-l-9 { left: calc(9 * 100% / 12); } - .m-push-l-10 { left: calc(10 * 100% / 12); } - .m-push-l-11 { left: calc(11 * 100% / 12); } - .m-pull-l-0 { right: calc(0 * 100% / 12); } - .m-pull-l-1 { right: calc(1 * 100% / 12); } - .m-pull-l-2 { right: calc(2 * 100% / 12); } - .m-pull-l-3 { right: calc(3 * 100% / 12); } - .m-pull-l-4 { right: calc(4 * 100% / 12); } - .m-pull-l-5 { right: calc(5 * 100% / 12); } - .m-pull-l-6 { right: calc(6 * 100% / 12); } - .m-pull-l-7 { right: calc(7 * 100% / 12); } - .m-pull-l-8 { right: calc(8 * 100% / 12); } - .m-pull-l-9 { right: calc(9 * 100% / 12); } - .m-pull-l-10 { right: calc(10 * 100% / 12); } - .m-pull-l-11 { right: calc(11 * 100% / 12); } - .m-clearfix-m::after { display: none; } - .m-hide-l { display: none; } - .m-show-l { display: block; } - .m-col-l-none { - width: auto; - float: none; - } -} - -html { - font-size: 14px; - background-color: #e8e8e8; -} -body { - font-family: 'Roboto', sans-serif; - font-size: 14px; - line-height: normal; - color: #000000; -} -h1, -h2, -h3, -h4, -h5, -h6 { - margin-top: 0; - font-weight: 300; -} -h1 { - margin-bottom: 1rem; -} -h2, -h3, -h4, -h5, -h6 { - margin-bottom: 0.5rem; -} -p, -ul, -ol, -dl { - margin-top: 0; -} -ul, -ol { - padding-left: 2rem; -} -ul ol, -ul ul, -ol ol, -ol ul { - margin-bottom: 0; -} -main p { - text-indent: 1.5rem; - text-align: justify; -} -main p.m-noindent, -li > p, -dd > p, -table.m-table td > p { - text-indent: 0; - text-align: left; -} -blockquote { - margin-top: 0; - margin-left: 1rem; - margin-right: 1rem; - padding: 1rem; - border-left-style: solid; - border-left-width: 0.25rem; -} -hr { - width: 75%; - border-width: 0.0625rem; - border-style: solid; -} -blockquote, -hr { - border-color: #92d050; -} -strong, -.m-text.m-strong { - font-weight: bold; -} -em, -.m-text.m-em { - font-style: italic; -} -s, -.m-text.m-s { - text-decoration: line-through; -} -sub, -sup, -.m-text.m-sub, -.m-text.m-sup { - font-size: 0.75rem; - line-height: 0; - position: relative; - vertical-align: baseline; -} -sup, -.m-text.m-sup { - top: -0.35rem; -} -sub, -.m-text.m-sub { - bottom: -0.2rem; -} -abbr { - cursor: help; - text-decoration: underline dotted; -} -a { - color: #26a9e0; -} -a.m-flat { - text-decoration: none; -} -a:hover, -a:focus, -a:active { - color: #26a9e0; -} -a img { - border: 0; -} -svg a { - cursor: pointer; -} -mark { - padding: 0.0625rem; - background-color: #e6e69c; - color: #4c93d3; -} -.m-link-wrap { - word-break: break-all; -} -pre, -code { - font-family: 'Monaco', monospace, monospace, monospace; - font-size: 1em; - color: #000000; - background-color: #f7f7f7; -} -pre.m-console, -code.m-console { - color: #000000; - background-color: #f7f7f7; -} -pre { - padding: 0.5rem 1rem; - border-radius: 0px; - overflow-x: auto; - margin-top: 0; -} -pre.m-console-wrap { - white-space: pre-wrap; - word-break: break-all; -} -code { - padding: 0.125rem; -} -*:focus { - outline-color: #ffffff; -} -div.m-scroll { - max-width: 100%; - overflow-x: auto; -} -.m-fullwidth { - width: 100%; -} -.m-spacing-150 { - line-height: 1.5rem; -} -.m-text-center, -.m-text-center.m-noindent, -table.m-table th.m-text-center, -.m-text-center p { - text-align: center; -} -.m-text-left, -.m-text-left.m-noindent, -table.m-table th.m-text-left, -.m-text-right p { - text-align: left; -} -.m-text-right, -.m-text-right.m-noindent, -table.m-table th.m-text-right, -.m-text-right p { - text-align: right; -} -.m-text-top, -table.m-table th.m-text-top, -table.m-table td.m-text-top { - vertical-align: top; -} -.m-text-middle, -table.m-table th.m-text-middle, -table.m-table td.m-text-middle { - vertical-align: middle; -} -.m-text-bottom, -table.m-table th.m-text-bottom, -table.m-table td.m-text-bottom { - vertical-align: bottom; -} -.m-text.m-tiny { - font-size: 50%; -} -.m-text.m-small { - font-size: 85.4%; -} -.m-text.m-big { - font-size: 117%; -} -h1 .m-thin, -h2 .m-thin, -h3 .m-thin, -h4 .m-thin, -h5 .m-thin, -h6 .m-thin { - font-weight: normal; - font-size: 75%; - color: #bdbdbd; -} -ul.m-unstyled, -ol.m-unstyled { - list-style-type: none; - padding-left: 0; -} -ul[class*="m-block-"], -ol[class*="m-block-"] { - padding-left: 0; -} -ul[class*="m-block-"] li, -ol[class*="m-block-"] li { - display: inline; -} -ul[class*="m-block-bar-"] li:not(:last-child)::after, -ol[class*="m-block-bar-"] li:not(:last-child)::after { - content: " | "; -} -ul[class*="m-block-dot-"] li:not(:last-child)::after, -ol[class*="m-block-dot-"] li:not(:last-child)::after { - content: " • "; -} -@media screen and (min-width: 576px) { - ul.m-block-bar-s, - ol.m-block-bar-s, - ul.m-block-dot-s, - ol.m-block-dot-s { - padding-left: 2rem; - } - ul.m-block-bar-s li, - ol.m-block-bar-s li, - ul.m-block-dot-s li, - ol.m-block-dot-s li { - display: list-item; - } - ul.m-block-bar-s li:not(:last-child)::after, - ol.m-block-bar-s li:not(:last-child)::after, - ul.m-block-dot-s li:not(:last-child)::after, - ol.m-block-dot-s li:not(:last-child)::after { - content: ""; - } -} -@media screen and (min-width: 768px) { - ul.m-block-bar-m, - ol.m-block-bar-m, - ul.m-block-dot-m, - ol.m-block-dot-m { - padding-left: 2rem; - } - ul.m-block-bar-m li, - ol.m-block-bar-m li, - ul.m-block-dot-m li, - ol.m-block-dot-m li { - display: list-item; - } - ul.m-block-bar-m li:not(:last-child)::after, - ol.m-block-bar-m li:not(:last-child)::after, - ul.m-block-dot-m li:not(:last-child)::after, - ol.m-block-dot-m li:not(:last-child)::after { - content: ""; - } -} -@media screen and (min-width: 992px) { - ul.m-block-bar-l, - ol.m-block-bar-l, - ul.m-block-dot-l, - ol.m-block-dot-l { - padding-left: 2rem; - } - ul.m-block-bar-l li, - ol.m-block-bar-l li, - ul.m-block-dot-l li, - ol.m-block-dot-l li { - display: list-item; - } - ul.m-block-bar-l li:not(:last-child)::after, - ol.m-block-bar-l li:not(:last-child)::after, - ul.m-block-dot-l li:not(:last-child)::after, - ol.m-block-dot-l li:not(:last-child)::after { - content: ""; - } -} -p.m-poem { - text-indent: 0; - text-align: left; - margin-left: 1.5rem; -} -p.m-transition { - color: #ddd; - text-indent: 0; - text-align: center; - font-size: 2rem; -} -dl.m-diary { - margin-bottom: 1.25rem; -} -dl.m-diary:last-child { - margin-bottom: 0.25rem; -} -dl.m-diary dt { - font-weight: bold; - width: 6rem; - float: left; - clear: both; - padding-top: 0.25rem; -} -dl.m-diary dd { - padding-top: 0.25rem; - padding-left: 6rem; - margin-left: 0; -} -a.m-footnote, -dl.m-footnote dd span.m-footnote { - top: -0.35rem; - font-size: 0.75rem; - line-height: 0; - position: relative; - vertical-align: baseline; -} -a.m-footnote, -dl.m-footnote dd span.m-footnote a { - text-decoration: none; -} -a.m-footnote::before { - content: "["; -} -a.m-footnote::after { - content: "]"; -} -dl.m-footnote dt { - width: 1.5rem; - float: left; - clear: both; -} -dl.m-footnote dd { - margin-left: 1.5rem; -} -dl.m-footnote { - font-size: 85.4%; -} -dl.m-footnote dd span.m-footnote a { - font-weight: bold; - font-style: italic; -} -.m-container-inflatable { - background-color: #ffffff; -} -.m-note { - border-radius: 0px; - padding: 1rem; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-frame { - background-color: #e8e8e8; - border-style: solid; - border-width: 0.125rem; - border-radius: 0px; - border-color: #ddd; - padding: 0.875rem; -} -.m-block { - border-style: solid; - border-width: 0.0625rem; - border-left-width: 0.25rem; - border-radius: 0px; - border-color: #ddd; - padding: 0.9375rem 0.9375rem 0.9375rem 0.75rem; -} -.m-block.hr { - width: 75%; - border-width: 0.0625rem; - border-style: solid; - border-color: #92d050; -} -.m-block.m-row.hr { - width: 75%; - border-width: 0.0625rem; - border-style: solid; - border-color: #92d050; -} -.m-block.m-badge::after { - content: " "; - display: block; - clear: both; -} -.m-block.m-badge h3 { - margin-left: 5rem; -} -.m-block.m-badge p { - margin-left: 5rem; - text-indent: 0; -} -.m-block.m-badge img { - width: 4rem; - height: 4rem; - border-radius: 0.5rem; - float: left; -} -div.m-button { - text-align: center; -} -div.m-button a { - display: inline-block; - border-radius: 0.5rem; - padding-top: 0.75rem; - padding-bottom: 0.75rem; - padding-left: 1.5rem; - padding-right: 1.5rem; - text-decoration: none; - font-size: 1.17rem; -} -div.m-button.m-fullwidth a { - display: block; - padding-left: 0.5rem; - padding-right: 0.5rem; -} -div.m-button a .m-big:first-child { - font-size: 1.37rem; - font-weight: bold; -} -div.m-button a .m-small:last-child { - font-size: 0.854rem; -} -.m-label { - border-radius: 0.5rem; - font-size: 75%; - font-weight: normal; - padding: 0.125rem 0.25rem; - vertical-align: 7.5%; -} -.m-label.m-flat { - border-width: 0.0625rem; - border-style: solid; - border-color: #bdbdbd; - padding: 0.0625rem 0.1875rem; -} -table.m-table { - border-collapse: collapse; - margin-left: auto; - margin-right: auto; -} -table.m-table.m-big { - margin-top: 1.75rem; -} -div.m-scroll > table.m-table:last-child { - margin-bottom: 0.0625rem; -} -table.m-table:not(.m-flat) tbody tr:hover { - background-color: #ddd; -} -table.m-table tr, -table.m-table th, -table.m-table td { - vertical-align: top; - border-style: solid; - border-top-width: 0.0625rem; - border-left-width: 0; - border-right-width: 0; - border-bottom-width: 0; - border-color: #ddd; -} -table.m-table tr:first-child th, -table.m-table tr:first-child td { - vertical-align: top; - border-style: solid; - border-top-width: 0.0625rem; - border-left-width: 0; - border-right-width: 0; - border-bottom-width: 0; - border-color: #ddd; -} -table.m-table tr:last-child th, -table.m-table tr:last-child td { - vertical-align: top; - border-style: solid; - border-top-width: 0.0625rem; - border-bottom-width: 0.0625rem; - border-left-width: 0; - border-right-width: 0; - border-color: #ddd; -} -table.m-table caption { - padding-bottom: 0.5rem; -} -table.m-table thead tr:first-child th, -table.m-table thead tr:first-child td { - border-top-width: 0.125rem; - border-left-width: 0; - border-right-width: 0; -} -table.m-table thead th, -table.m-table thead td { - border-bottom-width: 0.125rem; - vertical-align: bottom; - border-left-width: 0; - border-right-width: 0; -} -table.m-table tfoot th, -table.m-table tfoot td { - border-top-width: 0.125rem; -} -table.m-table th, -table.m-table td { - padding: 0.5rem; -} -table.m-table.m-big th, -table.m-table.m-big td { - padding: 0.75rem 1rem; -} -table.m-table th { - text-align: left; -} -table.m-table th.m-thin { - font-weight: normal; -} -table.m-table td.m-default, -table.m-table th.m-default, -table.m-table td.m-primary, -table.m-table th.m-primary, -table.m-table td.m-success, -table.m-table th.m-success, -table.m-table td.m-warning, -table.m-table th.m-warning, -table.m-table td.m-danger, -table.m-table th.m-danger, -table.m-table td.m-info, -table.m-table th.m-info, -table.m-table td.m-dim, -table.m-table th.m-dim, -table.m-table td.m-type, -table.m-table th.m-type { - padding-left: 0.4375rem; - padding-right: 0.4375rem; - border-left-width: 0.0625rem; -} -table.m-table.m-big td.m-default, -table.m-table.m-big th.m-default, -table.m-table.m-big td.m-primary, -table.m-table.m-big th.m-primary, -table.m-table.m-big td.m-success, -table.m-table.m-big th.m-success, -table.m-table.m-big td.m-warning, -table.m-table.m-big th.m-warning, -table.m-table.m-big td.m-danger, -table.m-table.m-big th.m-danger, -table.m-table.m-big td.m-info, -table.m-table.m-big th.m-info, -table.m-table.m-big td.m-dim, -table.m-table.m-big th.m-dim, -table.m-table.m-big td.m-type, -table.m-table.m-big th.m-type { - padding-left: 0.9375rem; - padding-right: 0.9375rem; - border-left-width: 0.0625rem; -} -table.m-table tr.m-default td, -table.m-table td.m-default, -table.m-table tr.m-default th, -table.m-table th.m-default, -table.m-table tr.m-primary td, -table.m-table td.m-primary, -table.m-table tr.m-primary th, -table.m-table th.m-primary, -table.m-table tr.m-success td, -table.m-table td.m-success, -table.m-table tr.m-success th, -table.m-table th.m-success, -table.m-table tr.m-warning td, -table.m-table td.m-warning, -table.m-table tr.m-warning th, -table.m-table th.m-warning, -table.m-table tr.m-danger td, -table.m-table td.m-danger, -table.m-table tr.m-danger th, -table.m-table th.m-danger, -table.m-table tr.m-info td, -table.m-table td.m-info, -table.m-table tr.m-info th, -table.m-table th.m-info, -table.m-table tr.m-dim td, -table.m-table td.m-dim, -table.m-table tr.m-dim th, -table.m-table th.m-dim, -table.m-table tr.m-type td, -table.m-table td.m-type, -table.m-table tr.m-type th, -table.m-table th.m-type { - border-color: #e8e8e8; -} -.m-note pre, -.m-note code, -table.m-table tr.m-default pre, -table.m-table tr.m-default code, -table.m-table td.m-default pre, -table.m-table td.m-default code, -table.m-table th.m-default pre, -table.m-table th.m-default code, -table.m-table tr.m-primary pre, -table.m-table tr.m-primary code, -table.m-table td.m-primary pre, -table.m-table td.m-primary code, -table.m-table th.m-primary pre, -table.m-table th.m-primary code, -table.m-table tr.m-success pre, -table.m-table tr.m-success code, -table.m-table td.m-success pre, -table.m-table td.m-success code, -table.m-table th.m-success pre, -table.m-table th.m-success code, -table.m-table tr.m-warning pre, -table.m-table tr.m-warning code, -table.m-table td.m-warning pre, -table.m-table td.m-warning code, -table.m-table th.m-warning pre, -table.m-table th.m-warning code, -table.m-table tr.m-danger pre, -table.m-table tr.m-danger code, -table.m-table td.m-danger pre, -table.m-table td.m-danger code, -table.m-table th.m-danger pre, -table.m-table th.m-danger code, -table.m-table tr.m-info pre, -table.m-table tr.m-info code, -table.m-table td.m-info pre, -table.m-table td.m-info code, -table.m-table th.m-info pre, -table.m-table th.m-info code, -table.m-table tr.m-dim pre, -table.m-table tr.m-dim code, -table.m-table td.m-dim pre, -table.m-table td.m-dim code, -table.m-table th.m-dim pre, -table.m-table th.m-dim code, -table.m-table tr.m-type pre, -table.m-table tr.m-type code, -table.m-table td.m-type pre, -table.m-table td.m-type code, -table.m-table th.m-type pre, -table.m-table th.m-type code { - background-color: rgba(251, 240, 236, 0.5); -} -img.m-image, -svg.m-image { - display: block; - margin-left: auto; - margin-right: auto; -} -div.m-image { - text-align: center; -} -img.m-image, -svg.m-image, -div.m-image img, -div.m-image svg { - max-width: 100%; - border-radius: 0px; -} -div.m-image.m-fullwidth img, -div.m-image.m-fullwidth svg { - width: 100%; -} -img.m-image.m-badge, -div.m-image.m-badge img { - border-radius: 50%; -} -figure.m-figure { - max-width: 100%; - margin-top: 0; - margin-left: auto; - margin-right: auto; - position: relative; - display: table; -} -figure.m-figure:before { - position: absolute; - content: " "; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: -1; - border-style: solid; - border-width: 0.125rem; - border-radius: 0px; - border-color: #ddd; -} -figure.m-figure.m-flat:before { - border-color: transparent; -} -figure.m-figure > * { - margin-left: 1rem; - margin-right: 1rem; - display: table-caption; - caption-side: bottom; -} -figure.m-figure > *:first-child { - display: inline; -} -figure.m-figure > *:last-child { - margin-bottom: 1rem !important; -} -figure.m-figure img, -figure.m-figure svg { - position: relative; - margin-left: 0; - margin-right: 0; - margin-bottom: 0; - border-top-left-radius: 0px; - border-top-right-radius: 0px; - max-width: 100%; -} -figure.m-figure.m-flat img, -figure.m-figure.m-flat svg { - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; -} -figure.m-figure a img, -figure.m-figure a svg { - margin-left: -1rem; - margin-right: -1rem; -} -figure.m-figure.m-fullwidth, -figure.m-figure.m-fullwidth > * { - display: block; -} -figure.m-figure.m-fullwidth > *:first-child { - display: inline; -} -figure.m-figure.m-fullwidth img, -figure.m-figure.m-fullwidth svg { - width: 100%; -} -figure.m-figure.m-fullwidth:after { - content: " "; - display: block; - margin-top: 1rem; - height: 1px; -} -.m-code-figure, -.m-console-figure { - margin-top: 0; - margin-left: 0; - margin-right: 0; - position: relative; - padding: 1rem; -} -.m-code-figure:before, -.m-console-figure:before { - position: absolute; - content: " "; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: -1; - border-style: solid; - border-width: 0.125rem; - border-radius: 0px; -} -.m-code-figure:before { - border-color: #f7f7f7; -} -.m-console-figure:before { - border-color: #f7f7f7; -} -.m-code-figure.m-flat:before, -.m-console-figure.m-flat:before { - border-color: transparent; -} -.m-code-figure > pre:first-child, -.m-console-figure > pre:first-child { - position: relative; - margin: -1rem -1rem 1rem -1rem; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} -.m-code-figure > pre.m-nopad, -.m-console-figure > pre.m-nopad { - margin-left: -0.875rem; - margin-right: -0.875rem; - margin-top: -1rem; - margin-bottom: -0.875rem; - padding-left: 0.875rem; -} -figure.m-figure figcaption, -.m-code-figure figcaption, -.m-console-figure figcaption { - margin-top: 0.5rem; - margin-bottom: 0.5rem; - font-weight: 300; - font-size: 1.17rem; -} -.m-imagegrid > div { - background-color: var( - --background-color - ); -} -.m-imagegrid > div > figure { - display: block; - float: left; - position: relative; - margin: 0; -} -.m-imagegrid > div > figure > div, -.m-imagegrid > div > figure > figcaption, -.m-imagegrid > div > figure > a > div, -.m-imagegrid > div > figure > a > figcaption { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border-color: #e8e8e8; - border-style: solid; - border-width: 0.25rem; - padding: 0.5rem; -} -.m-imagegrid > div > figure:first-child > div, -.m-imagegrid > div > figure:first-child > figcaption, -.m-imagegrid > div > figure:first-child > a > div, -.m-imagegrid > div > figure:first-child > a > figcaption { - border-left-width: 0; -} -.m-imagegrid > div > figure:last-child > div, -.m-imagegrid > div > figure:last-child > figcaption, -.m-imagegrid > div > figure:last-child > a > div, -.m-imagegrid > div > figure:last-child > a > figcaption { - border-right-width: 0; -} -.m-imagegrid > div > figure > figcaption, -.m-imagegrid > div > figure > a > figcaption { - color: transparent; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.75rem; -} -.m-imagegrid > div > figure > div::before, -.m-imagegrid > div > figure > figcaption::before, -.m-imagegrid > div > figure > a > div::before, -.m-imagegrid > div > figure > a > figcaption::before { - content: ""; - display: inline-block; - height: 100%; - vertical-align: bottom; - width: 0; -} -.m-imagegrid > div > figure:hover > figcaption, -.m-imagegrid > div > figure:hover > a > figcaption { - background: linear-gradient( - transparent 0%, - transparent 75%, - rgba(0, 0, 0, 0.85) 100% - ); - color: #ffffff; -} -.m-imagegrid > div > figure > img, -.m-imagegrid > div > figure > a > img { - width: 100%; - height: 100%; -} -.m-imagegrid > div::after { - display: block; - content: " "; - clear: both; -} -@media screen and (max-width: 767px) { - .m-imagegrid > div > figure { - float: none; - width: 100% !important; - } - .m-imagegrid > div > figure > div, - .m-imagegrid > div > figure > figcaption, - .m-imagegrid > div > figure > a > div, - .m-imagegrid > div > figure > a > figcaption { - border-left-width: 0; - border-right-width: 0; - } -} -.m-container-inflatable > .m-row > [class*="m-col-"] > .m-note, -.m-container-inflatable > .m-row > [class*="m-col-"] > .m-frame, -.m-container-inflatable > .m-row > [class*="m-col-"] > .m-block, -.m-container-inflatable > .m-row > [class*="m-col-"] > .m-imagegrid, -.m-container-inflatable > .m-row > [class*="m-col-"] > pre, -.m-container-inflatable > .m-row > [class*="m-col-"] > .m-code-figure, -.m-container-inflatable > .m-row > [class*="m-col-"] > .m-console-figure, -.m-container-inflatable > .m-row > [class*="m-col-"] section > .m-note, -.m-container-inflatable > .m-row > [class*="m-col-"] section > .m-frame, -.m-container-inflatable > .m-row > [class*="m-col-"] section > .m-block, -.m-container-inflatable > .m-row > [class*="m-col-"] section > .m-imagegrid, -.m-container-inflatable > .m-row > [class*="m-col-"] section > pre, -.m-container-inflatable > .m-row > [class*="m-col-"] section > .m-code-figure, -.m-container-inflatable - > .m-row - > [class*="m-col-"] - section - > .m-console-figure, -.m-container-inflatable [class*="m-center-"] > .m-note, -.m-container-inflatable [class*="m-center-"] > .m-frame, -.m-container-inflatable [class*="m-center-"] > .m-block, -.m-container-inflatable [class*="m-center-"] > .m-imagegrid, -.m-container-inflatable [class*="m-center-"] > pre, -.m-container-inflatable [class*="m-center-"] > .m-code-figure, -.m-container-inflatable [class*="m-center-"] > .m-console-figure, -.m-container-inflatable [class*="m-left-"] > .m-note, -.m-container-inflatable [class*="m-left-"] > .m-frame, -.m-container-inflatable [class*="m-left-"] > .m-block, -.m-container-inflatable [class*="m-left-"] > .m-imagegrid, -.m-container-inflatable [class*="m-left-"] > pre, -.m-container-inflatable [class*="m-left-"] > .m-code-figure, -.m-container-inflatable [class*="m-left-"] > .m-console-figure, -.m-container-inflatable [class*="m-right-"] > .m-note, -.m-container-inflatable [class*="m-right-"] > .m-frame, -.m-container-inflatable [class*="m-right-"] > .m-block, -.m-container-inflatable [class*="m-right-"] > .m-imagegrid, -.m-container-inflatable [class*="m-right-"] > pre, -.m-container-inflatable [class*="m-right-"] > .m-code-figure, -.m-container-inflatable [class*="m-right-"] > .m-console-figure, -.m-container-inflatable .m-container-inflate > .m-note, -.m-container-inflatable .m-container-inflate > .m-frame, -.m-container-inflatable .m-container-inflate > .m-block, -.m-container-inflatable .m-container-inflate > .m-imagegrid, -.m-container-inflatable .m-container-inflate > pre, -.m-container-inflatable .m-container-inflate > .m-code-figure, -.m-container-inflatable .m-container-inflate > .m-console-figure { - margin-left: -1rem; - margin-right: -1rem; -} -@media screen and (min-width: 576px) { - .m-container-inflatable .m-center-s > .m-note, - .m-container-inflatable .m-center-s > .m-frame, - .m-container-inflatable .m-center-s > .m-block, - .m-container-inflatable .m-center-s > .m-imagegrid, - .m-container-inflatable .m-center-s > pre, - .m-container-inflatable .m-center-s > .m-code-figure, - .m-container-inflatable .m-center-s > .m-console-figure { - margin-left: -1rem; - margin-right: -1rem; - } - .m-container-inflatable .m-left-s > .m-note, - .m-container-inflatable .m-left-s > .m-frame, - .m-container-inflatable .m-left-s > .m-block, - .m-container-inflatable .m-left-s > .m-imagegrid, - .m-container-inflatable .m-left-s > pre, - .m-container-inflatable .m-left-s > .m-code-figure, - .m-container-inflatable .m-left-s > .m-console-figure { - margin-left: -1rem; - margin-right: 0; - } - .m-container-inflatable .m-right-s > .m-note, - .m-container-inflatable .m-right-s > .m-frame, - .m-container-inflatable .m-right-s > .m-block, - .m-container-inflatable .m-right-s > .m-imagegrid, - .m-container-inflatable .m-right-s > pre, - .m-container-inflatable .m-right-s > .m-code-figure, - .m-container-inflatable .m-right-s > .m-console-figure { - margin-left: 0; - margin-right: -1rem; - } - .m-container-inflatable - > .m-row - > .m-col-s-10 - > .m-imagegrid.m-container-inflate, - .m-container-inflatable - > .m-row - > .m-col-s-10 - section - > .m-imagegrid.m-container-inflate { - margin-left: -10%; - margin-right: -10%; - } -} -@media screen and (min-width: 768px) { - .m-container-inflatable .m-center-m > .m-note, - .m-container-inflatable .m-center-m > .m-frame, - .m-container-inflatable .m-center-m > .m-block, - .m-container-inflatable .m-center-m > .m-imagegrid, - .m-container-inflatable .m-center-m > pre, - .m-container-inflatable .m-center-m > .m-code-figure, - .m-container-inflatable .m-center-m > .m-console-figure { - margin-left: -1rem; - margin-right: -1rem; - } - .m-container-inflatable .m-left-m > .m-note, - .m-container-inflatable .m-left-m > .m-frame, - .m-container-inflatable .m-left-m > .m-block, - .m-container-inflatable .m-left-m > .m-imagegrid, - .m-container-inflatable .m-left-m > pre, - .m-container-inflatable .m-left-m > .m-code-figure, - .m-container-inflatable .m-left-m > .m-console-figure { - margin-left: -1rem; - margin-right: 0; - } - .m-container-inflatable .m-right-m > .m-note, - .m-container-inflatable .m-right-m > .m-frame, - .m-container-inflatable .m-right-m > .m-block, - .m-container-inflatable .m-right-m > .m-imagegrid, - .m-container-inflatable .m-right-m > pre, - .m-container-inflatable .m-right-m > .m-code-figure, - .m-container-inflatable .m-right-m > .m-console-figure { - margin-left: 0; - margin-right: -1rem; - } - .m-container-inflatable - > .m-row - > .m-col-m-10 - > .m-imagegrid.m-container-inflate, - .m-container-inflatable - > .m-row - > .m-col-m-10 - section - > .m-imagegrid.m-container-inflate { - margin-left: -10%; - margin-right: -10%; - } -} -@media screen and (min-width: 992px) { - .m-container-inflatable .m-center-l > .m-note, - .m-container-inflatable .m-center-l > .m-frame, - .m-container-inflatable .m-center-l > .m-block, - .m-container-inflatable .m-center-l > .m-imagegrid, - .m-container-inflatable .m-center-l > pre, - .m-container-inflatable .m-center-l > .m-code-figure, - .m-container-inflatable .m-center-l > .m-console-figure { - margin-left: -1rem; - margin-right: -1rem; - } - .m-container-inflatable .m-left-l > .m-note, - .m-container-inflatable .m-left-l > .m-frame, - .m-container-inflatable .m-left-l > .m-block, - .m-container-inflatable .m-left-l > .m-imagegrid, - .m-container-inflatable .m-left-l > pre, - .m-container-inflatable .m-left-l > .m-code-figure, - .m-container-inflatable .m-left-l > .m-console-figure { - margin-left: -1rem; - margin-right: 0; - } - .m-container-inflatable .m-right-l > .m-note, - .m-container-inflatable .m-right-l > .m-frame, - .m-container-inflatable .m-right-l > .m-block, - .m-container-inflatable .m-right-l > .m-imagegrid, - .m-container-inflatable .m-right-l > pre, - .m-container-inflatable .m-right-l > .m-code-figure, - .m-container-inflatable .m-right-l > .m-console-figure { - margin-left: 0; - margin-right: -1rem; - } - .m-container-inflatable - > .m-row - > .m-col-l-10 - > .m-imagegrid.m-container-inflate, - .m-container-inflatable - > .m-row - > .m-col-l-10 - section - > .m-imagegrid.m-container-inflate { - margin-left: -10%; - margin-right: -10%; - } -} -pre.m-code span.hll { - margin-left: -1rem; - margin-right: -1rem; - padding-left: 1rem; -} -.m-code.m-inverted > span, -.m-console.m-inverted > span { - opacity: 0.3333; -} -.m-code.m-inverted > span.hll, -.m-console.m-inverted > span.hll { - opacity: 1; - background-color: transparent; - border-color: transparent; -} -.m-code.m-inverted { - color: rgba(91, 91, 91, 0.33); -} -.m-console.m-inverted { - color: rgba(91, 91, 91, 0.33); -} -.m-code.m-inverted > span.hll { - color: #000000; -} -.m-cosole.m-inverted > span.hll { - color: #000000; -} -.m-code-color { - display: inline-block; - width: 0.75rem; - height: 0.75rem; - vertical-align: -0.05rem; - margin-left: 0.2rem; - margin-right: 0.1rem; - border-radius: 0px; -} -div.m-math { - overflow-x: auto; - overflow-y: hidden; -} -div.m-math svg { - margin-left: auto; - margin-right: auto; - display: block; -} -div.m-button a svg.m-math { - fill: #ffffff; -} -div.m-button.m-flat a svg.m-math { - fill: #000000; -} -div.m-button.m-flat a:hover svg.m-math, -div.m-button.m-default a:focus svg.m-math, -div.m-button.m-default a:active svg.m-math { - fill: #26a9e0; -} -.m-graph { - font-size: 14px; -} -div.m-plot svg, -div.m-graph svg { - max-width: 100%; - margin-left: auto; - margin-right: auto; - display: block; -} -div.m-plot .m-background { - fill: #fbf0ec; -} -div.m-plot svg .m-label { - font-size: 11px; -} -div.m-plot svg .m-title { - font-size: 13px; -} -div.m-plot svg .m-label, -div.m-plot svg .m-title { - fill: #000000; -} -div.m-plot svg .m-line { - stroke: #000000; - stroke-width: 0.8; -} -div.m-plot svg .m-error { - stroke: #000000; - stroke-width: 1.5; -} -div.m-plot svg .m-label.m-dim { - fill: #bdbdbd; -} -.m-graph g.m-edge path, -.m-graph g.m-node.m-flat ellipse, -.m-graph g.m-node.m-flat polygon { - fill: none; -} -.m-graph g.m-node:not(.m-flat) text { - fill: #ffffff; -} -figure.m-figure > svg.m-math:first-child, -figure.m-figure > svg.m-graph:first-child { - padding: 1rem; - box-sizing: content-box; -} -figure.m-figure:not(.m-flat) > svg.m-math:first-child, -figure.m-figure:not(.m-flat) > svg.m-graph:first-child { - background-color: #ddd; -} -.m-block.m-default { - border-left-color: #ddd; -} -.m-block.m-default h3, -.m-block.m-default h4, -.m-block.m-default h5, -.m-block.m-default h6, -.m-text.m-default, -.m-label.m-flat.m-default { - color: #000000; -} -.m-block.m-default h3 a, -.m-block.m-default h4 a, -.m-block.m-default h5 a, -.m-block.m-default h6 a { - color: #26a9e0; -} -.m-block.m-primary { - border-left-color: #31708f; -} -.m-block.m-primary h3, -.m-block.m-primary h4, -.m-block.m-primary h5, -.m-block.m-primary h6, -.m-block.m-primary h3 a, -.m-block.m-primary h4 a, -.m-block.m-primary h5 a, -.m-block.m-primary h6 a, -.m-text.m-primary, -.m-label.m-flat.m-primary { - color: #31708f; -} -.m-block.m-success { - border-left-color: #9ad36a; -} -.m-block.m-success h3, -.m-block.m-success h4, -.m-block.m-success h5, -.m-block.m-success h6, -.m-block.m-success h3 a, -.m-block.m-success h4 a, -.m-block.m-success h5 a, -.m-block.m-success h6 a, -.m-text.m-success, -.m-label.m-flat.m-success { - color: #9ad36a; -} -.m-block.m-warning { - border-left-color: #f9cf79; -} -.m-block.m-warning h3, -.m-block.m-warning h4, -.m-block.m-warning h5, -.m-block.m-warning h6, -.m-block.m-warning h3 a, -.m-block.m-warning h4 a, -.m-block.m-warning h5 a, -.m-block.m-warning h6 a, -.m-text.m-warning, -.m-label.m-flat.m-warning { - color: #f9cf79; -} -.m-block.m-danger { - border-left-color: #f60000; -} -.m-block.m-danger h3, -.m-block.m-danger h4, -.m-block.m-danger h5, -.m-block.m-danger h6, -.m-block.m-danger h3 a, -.m-block.m-danger h4 a, -.m-block.m-danger h5 a, -.m-block.m-danger h6 a, -.m-text.m-danger, -.m-label.m-flat.m-danger { - color: #f60000; -} -.m-block.m-info { - border-left-color: #31708f; -} -.m-block.m-info h3, -.m-block.m-info h4, -.m-block.m-info h5, -.m-block.m-info h6, -.m-block.m-info h3 a, -.m-block.m-info h4 a, -.m-block.m-info h5 a, -.m-block.m-info h6 a, -.m-text.m-info, -.m-label.m-flat.m-info { - color: #31708f; -} -.m-block.m-type h3, -.m-block.m-type h4, -.m-block.m-type h5, -.m-block.m-type h6, -.m-block.m-type h3 a, -.m-block.m-type h4 a, -.m-block.m-type h5 a, -.m-block.m-type h6 a, -.m-text.m-type, -.m-label.m-flat.m-type { - color: #9ad36a; -} -.m-block.m-dim { - border-left-color: #bdbdbd; -} -span.m-default { - color: #000000; - background-color: transparent; -} -span.m-default a { - color: var(--default-link-color); - background-color: transparent; -} -span.m-default a:hover, -span.m-default a:focus, -span.m-default a:active { - color: #26a9e0; - background-color: transparent; -} -span.m-primary { - color: #31708f; - background-color: transparent; -} -span.m-primary a { - color: var(--primary-link-color); - background-color: transparent; -} -span.m-primary a:hover, -span.m-primary a:focus, -span.m-primary a:active { - color: #31708f; - background-color: transparent; -} -span.m-success { - color: #9ad36a; - background-color: transparent; -} -span.m-success a { - color: var(--success-link-color); - background-color: transparent; -} -span.m-success a:hover, -span.m-success a:focus, -span.m-success a:active { - color: #9ad36a; - background-color: transparent; -} -span.m-warning { - color: #f9cf79; - background-color: transparent; -} -span.m-warning a { - color: var(--warning-link-color); - background-color: transparent; -} -span.m-warning a:hover, -span.m-warning a:focus, -span.m-warning a:active { - color: #f9cf79; - background-color: transparent; -} -span.m-danger { - color: #f60000; - background-color: transparent; -} -span.m-danger a { - color: var(--danger-link-color); - background-color: transparent; -} -span.m-danger a:hover, -span.m-danger a:focus, -span.m-danger a:active { - color: #f60000; - background-color: transparent; -} -span.m-info { - color: #31708f; - background-color: transparent; -} -span.m-info a { - color: var(--info-link-color); - background-color: transparent; -} -span.m-info a:hover, -span.m-info a:focus, -span.m-info a:active { - color: #67cce0; - background-color: transparent; -} -span.m-type { - color: #9ad36a; - background-color: transparent; -} -span.m-type a { - color: var(--type-link-color); - background-color: transparent; -} -span.m-type a:hover, -span.m-type a:focus, -span.m-type a:active { - color: #9ad36a; - background-color: transparent; -} -span.m-dim { - color: #bdbdbd; - background-color: transparent; -} -span.m-dim a { - color: #c0c0c0; - background-color: transparent; -} -span.m-dim a:hover, -span.m-dim a:focus, -span.m-dim a:active { - color: #949494; - background-color: transparent; -} -.m-block.m-dim, -.m-text.m-dim, -.m-label.m-flat.m-dim { - color: #bdbdbd; -} -.m-block.m-dim a, -.m-text.m-dim a { - color: #c0c0c0; -} -.m-block.m-dim a:hover, -.m-block.m-dim a:focus, -.m-block.m-dim a:active, -.m-text.m-dim a:hover, -.m-text.m-dim a:focus, -.m-text.m-dim a:active { - color: #949494; -} -.m-block.m-type { - border-left-color: #9ad36a; -} -.m-block.m-type h3, -.m-block.m-type h4, -.m-block.m-type h5, -.m-block.m-type h6, -.m-block.m-type h3 a, -.m-block.m-type h4 a, -.m-block.m-type h5 a, -.m-block.m-type h6 a, -.m-text.m-type, -.m-label.m-flat.m-type { - color: #9ad36a; -} -.m-block.m-flat { - border-color: transparent; -} -.m-block.m-flat h3, -.m-block.m-flat h4, -.m-block.m-flat h5, -.m-block.m-flat h6 { - color: #000000; -} -.m-block.m-default h3 a:hover, -.m-block.m-default h3 a:focus, -.m-block.m-default h3 a:active, -.m-block.m-default h4 a:hover, -.m-block.m-default h4 a:focus, -.m-block.m-default h4 a:active, -.m-block.m-default h5 a:hover, -.m-block.m-default h5 a:focus, -.m-block.m-default h5 a:active, -.m-block.m-default h6 a:hover, -.m-block.m-default h6 a:focus, -.m-block.m-default h6 a:active { - color: #26a9e0; -} -.m-block.m-primary h3 a:hover, -.m-block.m-primary h3 a:focus, -.m-block.m-primary h3 a:active, -.m-block.m-primary h4 a:hover, -.m-block.m-primary h4 a:focus, -.m-block.m-primary h4 a:active, -.m-block.m-primary h5 a:hover, -.m-block.m-primary h5 a:focus, -.m-block.m-primary h5 a:active, -.m-block.m-primary h6 a:hover, -.m-block.m-primary h6 a:focus, -.m-block.m-primary h6 a:active { - color: #31708f; -} -.m-block.m-success h3 a:hover, -.m-block.m-success h3 a:focus, -.m-block.m-success h3 a:active, -.m-block.m-success h4 a:hover, -.m-block.m-success h4 a:focus, -.m-block.m-success h4 a:active, -.m-block.m-success h5 a:hover, -.m-block.m-success h5 a:focus, -.m-block.m-success h5 a:active, -.m-block.m-success h6 a:hover, -.m-block.m-success h6 a:focus, -.m-block.m-success h6 a:active { - color: #9ad36a; -} -.m-block.m-warning h3 a:hover, -.m-block.m-warning h3 a:focus, -.m-block.m-warning h3 a:active, -.m-block.m-warning h4 a:hover, -.m-block.m-warning h4 a:focus, -.m-block.m-warning h4 a:active, -.m-block.m-warning h5 a:hover, -.m-block.m-warning h5 a:focus, -.m-block.m-warning h5 a:active, -.m-block.m-warning h6 a:hover, -.m-block.m-warning h6 a:focus, -.m-block.m-warning h6 a:active { - color: #f9cf79; -} -.m-block.m-danger h3 a:hover, -.m-block.m-danger h3 a:focus, -.m-block.m-danger h3 a:active, -.m-block.m-danger h4 a:hover, -.m-block.m-danger h4 a:focus, -.m-block.m-danger h4 a:active, -.m-block.m-danger h5 a:hover, -.m-block.m-danger h5 a:focus, -.m-block.m-danger h5 a:active, -.m-block.m-danger h6 a:hover, -.m-block.m-danger h6 a:focus, -.m-block.m-danger h6 a:active { - color: #f60000; -} -.m-block.m-info h3 a:hover, -.m-block.m-info h3 a:focus, -.m-block.m-info h3 a:active, -.m-block.m-info h4 a:hover, -.m-block.m-info h4 a:focus, -.m-block.m-info h4 a:active, -.m-block.m-info h5 a:hover, -.m-block.m-info h5 a:focus, -.m-block.m-info h5 a:active, -.m-block.m-info h6 a:hover, -.m-block.m-info h6 a:focus, -.m-block.m-info h6 a:active { - color: #67cce0; -} -.m-block.m-type h3 a:hover, -.m-block.m-type h3 a:focus, -.m-block.m-type h3 a:active, -.m-block.m-type h4 a:hover, -.m-block.m-type h4 a:focus, -.m-block.m-type h4 a:active, -.m-block.m-type h5 a:hover, -.m-block.m-type h5 a:focus, -.m-block.m-type h5 a:active, -.m-block.m-type h6 a:hover, -.m-block.m-type h6 a:focus, -.m-block.m-type h6 a:active { - color: #9ad36a; -} -div.m-button a, -.m-label { - color: #ffffff; -} -div.m-button.m-flat a { - color: #000000; -} -div.m-button.m-flat a:hover, -div.m-button.m-default a:focus, -div.m-button.m-default a:active { - color: #26a9e0; -} -div.m-button.m-default a, -.m-label:not(.m-flat).m-default { - background-color: #000000; - color: #000000; -} -div.m-button.m-primary a, -.m-label:not(.m-flat).m-primary { - background-color: #31708f; - color: #67cce0; -} -div.m-button.m-success a, -.m-label:not(.m-flat).m-success { - background-color: #9ad36a; - color: #3c763d; -} -div.m-button.m-warning a, -.m-label:not(.m-flat).m-warning { - background-color: #f9cf79; - color: #8a6d3b; -} -div.m-button.m-danger a, -.m-label:not(.m-flat).m-danger { - background-color: #f60000; - color: #920000; -} -div.m-button.m-info a, -.m-label:not(.m-flat).m-info { - background-color: #31708f; - color: #81bcda; -} -div.m-button.m-dim a, -.m-label:not(.m-flat).m-dim { - background-color: #bdbdbd; - color: #7c7c7c; -} -div.m-button.m-type a, -.m-label:not(.m-flat).m-type { - background-color: #9ad36a; - color: #3c763d; -} -div.m-button.m-default a:hover, -div.m-button.m-default a:focus, -div.m-button.m-default a:active { - background-color: #26a9e0; -} -div.m-button.m-primary a:hover, -div.m-button.m-primary a:focus, -div.m-button.m-primary a:active { - background-color: #31708f; -} -div.m-button.m-success a:hover, -div.m-button.m-success a:focus, -div.m-button.m-success a:active { - background-color: #9ad36a; -} -div.m-button.m-warning a:hover, -div.m-button.m-warning a:focus, -div.m-button.m-warning a:active { - background-color: #f9cf79; -} -div.m-button.m-danger a:hover, -div.m-button.m-danger a:focus, -div.m-button.m-danger a:active { - background-color: #f60000; -} -div.m-button.m-info a:hover, -div.m-button.m-info a:focus, -div.m-button.m-info a:active { - background-color: #67cce0; -} -div.m-button.m-dim a:hover, -div.m-button.m-dim a:focus, -div.m-button.m-dim a:active { - background-color: #c0c0c0; -} -div.m-button.m-type a:hover, -div.m-button.m-type a:focus, -div.m-button.m-type a:active { - background-color: #9ad36a; -} -.m-note.m-default { - background-color: transparent; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-default, -table.m-table tr.m-default td, -table.m-table td.m-default, -table.m-table tr.m-default th, -table.m-table th.m-default, -table.m-table tr.m-default strong, -table.m-table strong.m-default, -table.m-table tr.m-default em, -table.m-table em.m-default { - color: #000000; -} -.m-note.m-default a:hover, -table.m-table tr.m-default td a:hover, -table.m-table td.m-default a:hover, -table.m-table tr.m-default th a:hover, -table.m-table th.m-default a:hover, -.m-note.m-default a:focus, -table.m-table tr.m-default td a:focus, -table.m-table td.m-default a:focus, -table.m-table tr.m-default th a:focus, -table.m-table th.m-default a:focus, -.m-note.m-default a:active, -table.m-table tr.m-default td a:active, -table.m-table td.m-default a:active, -table.m-table tr.m-default th a:active, -table.m-table th.m-default a:active { - color: #26a9e0; -} -.m-note.m-primary { - border-color: #31708f; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-primary a, -table.m-table tr.m-primary td a, -table.m-table td.m-primary a, -table.m-table tr.m-primary th a, -table.m-table th.m-primary a { - color: #26a9e0; -} -.m-note.m-primary, -table.m-table tr.m-primary td, -table.m-table td.m-primary, -table.m-table tr.m-primary th, -table.m-table th.m-primary, -table.m-table tr.m-primary strong, -table.m-table strong.m-primary, -table.m-table tr.m-primary em, -table.m-table em.m-primary { - background-color: transparent; - color: #67cce0; -} -.m-note.m-primary a, -table.m-table tr.m-primary td a, -table.m-table td.m-primary a, -table.m-table tr.m-primary th a, -table.m-table th.m-primary a { - color: #31708f; -} -.m-note.m-primary a:hover, -table.m-table tr.m-primary td a:hover, -table.m-table td.m-primary a:hover, -table.m-table tr.m-primary th a:hover, -table.m-table th.m-primary a:hover, -.m-note.m-primary a:focus, -table.m-table tr.m-primary td a:focus, -table.m-table td.m-primary a:focus, -table.m-table tr.m-primary th a:focus, -table.m-table th.m-primary a:focus, -.m-note.m-primary a:active, -table.m-table tr.m-primary td a:active, -table.m-table td.m-primary a:active, -table.m-table tr.m-primary th a:active, -table.m-table th.m-primary a:active { - color: #31708f; -} -.m-note.m-success { - border-color: #9ad36a; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-success, -table.m-table tr.m-success td, -table.m-table td.m-success, -table.m-table tr.m-success th, -table.m-table th.m-success, -table.m-table tr.m-success strong, -table.m-table strong.m-success, -table.m-table tr.m-success em, -table.m-table em.m-success { - background-color: transparent; - color: #3c763d; -} -.m-note.m-success a, -table.m-table tr.m-success td a, -table.m-table td.m-success a, -table.m-table tr.m-success th a, -table.m-table th.m-success a { - color: #9ad36a; -} -.m-note.m-success a:hover, -table.m-table tr.m-success td a:hover, -table.m-table td.m-success a:hover, -table.m-table tr.m-success th a:hover, -table.m-table th.m-success a:hover, -.m-note.m-success a:focus, -table.m-table tr.m-success td a:focus, -table.m-table td.m-success a:focus, -table.m-table tr.m-success th a:focus, -table.m-table th.m-success a:focus, -.m-note.m-success a:active, -table.m-table tr.m-success td a:active, -table.m-table td.m-success a:active, -table.m-table tr.m-success th a:active, -table.m-table th.m-success a:active { - color: #9ad36a; -} -.m-note.m-warning { - border-color: #f9cf79; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-warning, -table.m-table tr.m-warning td, -table.m-table td.m-warning, -table.m-table tr.m-warning th, -table.m-table th.m-warning, -table.m-table tr.m-warning strong, -table.m-table strong.m-warning, -table.m-table tr.m-warning em, -table.m-table em.m-warning { - background-color: transparent; - color: #8a6d3b; -} -.m-note.m-warning a, -table.m-table tr.m-warning td a, -table.m-table td.m-warning a, -table.m-table tr.m-warning th a, -table.m-table th.m-warning a { - color: #f9cf79; -} -.m-note.m-warning a:hover, -table.m-table tr.m-warning td a:hover, -table.m-table td.m-warning a:hover, -table.m-table tr.m-warning th a:hover, -table.m-table th.m-warning a:hover, -.m-note.m-warning a:focus, -table.m-table tr.m-warning td a:focus, -table.m-table td.m-warning a:focus, -table.m-table tr.m-warning th a:focus, -table.m-table th.m-warning a:focus, -.m-note.m-warning a:active, -table.m-table tr.m-warning td a:active, -table.m-table td.m-warning a:active, -table.m-table tr.m-warning th a:active, -table.m-table th.m-warning a:active { - color: #f9cf79; -} -.m-note.m-danger { - border-color: #f60000; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-danger, -table.m-table tr.m-danger td, -table.m-table td.m-danger, -table.m-table tr.m-danger th, -table.m-table th.m-danger, -table.m-table tr.m-danger strong, -table.m-table strong.m-danger, -table.m-table tr.m-danger em, -table.m-table em.m-danger { - background-color: transparent; - color: #920000; -} -.m-note.m-danger a, -table.m-table tr.m-danger td a, -table.m-table td.m-danger a, -table.m-table tr.m-danger th a, -table.m-table th.m-danger a { - color: #f60000; -} -.m-note.m-danger a:hover, -table.m-table tr.m-danger td a:hover, -table.m-table td.m-danger a:hover, -table.m-table tr.m-danger th a:hover, -table.m-table th.m-danger a:hover, -.m-note.m-danger a:focus, -table.m-table tr.m-danger td a:focus, -table.m-table td.m-danger a:focus, -table.m-table tr.m-danger th a:focus, -table.m-table th.m-danger a:focus, -.m-note.m-danger a:active, -table.m-table tr.m-danger td a:active, -table.m-table td.m-danger a:active, -table.m-table tr.m-danger th a:active, -table.m-table th.m-danger a:active { - color: #f60000; -} -.m-note.m-info { - border-color: #31708f; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-info, -table.m-table tr.m-info td, -table.m-table td.m-info, -table.m-table tr.m-info th, -table.m-table th.m-info, -table.m-table tr.m-info strong, -table.m-table strong.m-info, -table.m-table tr.m-info em, -table.m-table em.m-info { - background-color: transparent; - color: #81bcda; -} -.m-note.m-info a, -table.m-table tr.m-info td a, -table.m-table td.m-info a, -table.m-table tr.m-info th a, -table.m-table th.m-info a { - color: #67cce0; -} -.m-note.m-info a:hover, -table.m-table tr.m-info td a:hover, -table.m-table td.m-info a:hover, -table.m-table tr.m-info th a:hover, -table.m-table th.m-info a:hover, -.m-note.m-info a:focus, -table.m-table tr.m-info td a:focus, -table.m-table td.m-info a:focus, -table.m-table tr.m-info th a:focus, -table.m-table th.m-info a:focus, -.m-note.m-info a:active, -table.m-table tr.m-info td a:active, -table.m-table td.m-info a:active, -table.m-table tr.m-info th a:active, -table.m-table th.m-info a:active { - color: #67cce0; -} -.m-note.m-dim { - border-color: #bdbdbd; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-dim, -table.m-table tr.m-dim td, -table.m-table td.m-dim, -table.m-table tr.m-dim th, -table.m-table th.m-dim, -table.m-table tr.m-dim strong, -table.m-table strong.m-dim, -table.m-table tr.m-dim em, -table.m-table em.m-dim { - background-color: transparent; - color: #7c7c7c; -} -.m-note.m-dim a, -table.m-table tr.m-dim td a, -table.m-table td.m-dim a, -table.m-table tr.m-dim th a, -table.m-table th.m-dim a { - color: #c0c0c0; -} -.m-note.m-dim a:hover, -table.m-table tr.m-dim td a:hover, -table.m-table td.m-dim a:hover, -table.m-table tr.m-dim th a:hover, -table.m-table th.m-dim a:hover, -.m-note.m-dim a:focus, -table.m-table tr.m-dim td a:focus, -table.m-table td.m-dim a:focus, -table.m-table tr.m-dim th a:focus, -table.m-table th.m-dim a:focus, -.m-note.m-dim a:active, -table.m-table tr.m-dim td a:active, -table.m-table td.m-dim a:active, -table.m-table tr.m-dim th a:active, -table.m-table th.m-dim a:active { - color: #949494; -} -.m-note.m-type { - border-color: #9ad36a; - border-width: 2px; - border-style: solid; - padding-top: 0; -} -.m-note.m-type, -table.m-table tr.m-type td, -table.m-table td.m-type, -table.m-table tr.m-type th, -table.m-table th.m-type, -table.m-table tr.m-type strong, -table.m-table strong.m-type, -table.m-table tr.m-type em, -table.m-table em.m-type { - background-color: transparent; - color: #3c763d; -} -.m-note.m-type a, -table.m-table tr.m-type td a, -table.m-table td.m-type a, -table.m-table tr.m-type th a, -table.m-table th.m-type a { - color: #9ad36a; -} -.m-note.m-type a:hover, -table.m-table tr.m-type td a:hover, -table.m-table td.m-type a:hover, -table.m-table tr.m-type th a:hover, -table.m-table th.m-type a:hover, -.m-note.m-type a:focus, -table.m-table tr.m-type td a:focus, -table.m-table td.m-type a:focus, -table.m-table tr.m-type th a:focus, -table.m-table th.m-type a:focus, -.m-note.m-type a:active, -table.m-table tr.m-type td a:active, -table.m-table td.m-type a:active, -table.m-table tr.m-type th a:active, -table.m-table th.m-type a:active { - color: #9ad36a; -} -figure.m-figure.m-default:before { - border-color: transparent; -} -figure.m-figure.m-default figcaption { - color: #000000; -} -figure.m-figure.m-primary:before { - border-color: transparent; -} -figure.m-figure.m-primary figcaption { - color: #31708f; -} -figure.m-figure.m-success:before { - border-color: transparent; -} -figure.m-figure.m-success figcaption { - color: #9ad36a; -} -figure.m-figure.m-warning:before { - border-color: transparent; -} -figure.m-figure.m-warning figcaption { - color: #f9cf79; -} -figure.m-figure.m-danger:before { - border-color: transparent; -} -figure.m-figure.m-danger figcaption { - color: #f60000; -} -figure.m-figure.m-info:before { - border-color: transparent; -} -figure.m-figure.m-info figcaption { - color: #31708f; -} -figure.m-figure.m-type:before { - border-color: transparent; -} -figure.m-figure.m-type figcaption { - color: #9ad36a; -} -figure.m-figure.m-dim:before { - border-color: transparent; -} -figure.m-figure.m-dim { - color: #bdbdbd; -} -figure.m-figure.m-dim a { - color: #c0c0c0; -} -figure.m-figure.m-dim a:hover, -figure.m-figure.m-dim a:focus, -figure.m-figure.m-dim a:active { - color: #949494; -} -.m-math { - fill: #000000; -} -.m-math.m-default, -.m-math g.m-default, -.m-math rect.m-default, -div.m-plot svg .m-bar.m-default, -.m-graph g.m-edge polygon, -.m-graph g.m-node:not(.m-flat) ellipse, -.m-graph g.m-node:not(.m-flat) polygon, -.m-graph g.m-edge text, -.m-graph g.m-node.m-flat text, -.m-graph.m-default g.m-edge polygon, -.m-graph.m-default g.m-node:not(.m-flat) ellipse, -.m-graph.m-default g.m-node:not(.m-flat) polygon, -.m-graph.m-default g.m-edge text, -.m-graph.m-default g.m-node.m-flat text { - fill: #000000; -} -.m-graph g.m-edge polygon, -.m-graph g.m-edge path, -.m-graph g.m-node ellipse, -.m-graph g.m-node polygon, -.m-graph g.m-node polyline, -.m-graph.m-default g.m-edge polygon, -.m-graph.m-default g.m-edge path, -.m-graph.m-default g.m-node ellipse, -.m-graph.m-default g.m-node polygon, -.m-graph.m-default g.m-node polyline { - stroke: #000000; -} -.m-math.m-primary, -.m-math g.m-primary, -.m-math rect.m-primary, -div.m-plot svg .m-bar.m-primary, -.m-graph.m-primary g.m-edge polygon, -.m-graph.m-primary g.m-node:not(.m-flat) ellipse, -.m-graph.m-primary g.m-node:not(.m-flat) polygon, -.m-graph.m-primary g.m-edge text, -.m-graph.m-primary g.m-node.m-flat text { - fill: #31708f; -} -.m-graph.m-primary g.m-edge polygon, -.m-graph.m-primary g.m-edge path, -.m-graph.m-primary g.m-node ellipse, -.m-graph.m-primary g.m-node polygon, -.m-graph.m-primary g.m-node polyline { - stroke: #31708f; -} -.m-math.m-success, -.m-math g.m-success, -.m-math rect.m-success, -div.m-plot svg .m-bar.m-success, -.m-graph.m-success g.m-edge polygon, -.m-graph.m-success g.m-node:not(.m-flat) ellipse, -.m-graph.m-success g.m-node:not(.m-flat) polygon, -.m-graph.m-success g.m-edge text, -.m-graph.m-success g.m-node.m-flat text { - fill: #9ad36a; -} -.m-graph.m-success g.m-edge polygon, -.m-graph.m-success g.m-edge path, -.m-graph.m-success g.m-node ellipse, -.m-graph.m-success g.m-node polygon, -.m-graph.m-success g.m-node polyline { - stroke: #9ad36a; -} -.m-math.m-warning, -.m-math g.m-warning, -.m-math rect.m-warning, -div.m-plot svg .m-bar.m-warning, -.m-graph.m-warning g.m-edge polygon, -.m-graph.m-warning g.m-node:not(.m-flat) ellipse, -.m-graph.m-warning g.m-node:not(.m-flat) polygon, -.m-graph.m-warning g.m-edge text, -.m-graph.m-warning g.m-node.m-flat text { - fill: #f9cf79; -} -.m-graph.m-warning g.m-edge polygon, -.m-graph.m-warning g.m-edge path, -.m-graph.m-warning g.m-node ellipse, -.m-graph.m-warning g.m-node polygon, -.m-graph.m-warning g.m-node polyline { - stroke: #f9cf79; -} -.m-math.m-danger, -.m-math g.m-danger, -.m-math rect.m-danger, -div.m-plot svg .m-bar.m-danger, -.m-graph.m-danger g.m-edge polygon, -.m-graph.m-danger g.m-node:not(.m-flat) ellipse, -.m-graph.m-danger g.m-node:not(.m-flat) polygon, -.m-graph.m-danger g.m-edge text, -.m-graph.m-danger g.m-node.m-flat text { - fill: #f60000; -} -.m-graph.m-danger g.m-edge polygon, -.m-graph.m-danger g.m-edge path, -.m-graph.m-danger g.m-node ellipse, -.m-graph.m-danger g.m-node polygon, -.m-graph.m-danger g.m-node polyline { - stroke: #f60000; -} -.m-math.m-info, -.m-math g.m-info, -.m-math rect.m-info, -div.m-plot svg .m-bar.m-info, -.m-graph.m-info g.m-edge polygon, -.m-graph.m-info g.m-node:not(.m-flat) ellipse, -.m-graph.m-info g.m-node:not(.m-flat) polygon, -.m-graph.m-info g.m-edge text, -.m-graph.m-info g.m-node.m-flat text { - fill: #31708f; -} -.m-graph.m-info g.m-edge polygon, -.m-graph.m-info g.m-edge path, -.m-graph.m-info g.m-node ellipse, -.m-graph.m-info g.m-node polygon, -.m-graph.m-info g.m-node polyline { - stroke: #31708f; -} -.m-math.m-dim, -.m-math g.m-dim, -.m-math rect.m-dim, -div.m-plot svg .m-bar.m-dim, -.m-graph.m-dim g.m-edge polygon, -.m-graph.m-dim g.m-node:not(.m-flat) ellipse, -.m-graph.m-dim g.m-node:not(.m-flat) polygon, -.m-graph.m-dim g.m-edge text, -.m-graph.m-dim g.m-node.m-flat text { - fill: #bdbdbd; -} -.m-graph.m-dim g.m-edge polygon, -.m-graph.m-dim g.m-edge path, -.m-graph.m-dim g.m-node ellipse, -.m-graph.m-dim g.m-node polygon, -.m-graph.m-dim g.m-node polyline { - stroke: #bdbdbd; -} -.m-math.m-type, -.m-math g.m-type, -.m-math rect.m-type, -div.m-plot svg .m-bar.m-type, -.m-graph.m-type g.m-edge polygon, -.m-graph.m-type g.m-node:not(.m-flat) ellipse, -.m-graph.m-type g.m-node:not(.m-flat) polygon, -.m-graph.m-type g.m-edge text, -.m-graph.m-type g.m-node.m-flat text { - fill: #9ad36a; -} -.m-graph.m-type g.m-edge polygon, -.m-graph.m-type g.m-edge path, -.m-graph.m-type g.m-node ellipse, -.m-graph.m-type g.m-node polygon, -.m-graph.m-type g.m-node polyline { - stroke: #9ad36a; -} -.m-graph g.m-edge.m-default polygon, -.m-graph g.m-node.m-default:not(.m-flat) ellipse, -.m-graph g.m-node.m-default:not(.m-flat) polygon, -.m-graph g.m-edge.m-default text, -.m-graph g.m-node.m-default.m-flat text { - fill: #000000; -} -.m-graph g.m-edge.m-default polygon, -.m-graph g.m-edge.m-default path, -.m-graph g.m-node.m-default ellipse, -.m-graph g.m-node.m-default polygon, -.m-graph g.m-node.m-default polyline { - stroke: #000000; -} -.m-graph g.m-edge.m-primary polygon, -.m-graph g.m-node.m-primary:not(.m-flat) ellipse, -.m-graph g.m-node.m-primary:not(.m-flat) polygon, -.m-graph g.m-edge.m-primary text, -.m-graph g.m-node.m-primary.m-flat text { - fill: #31708f; -} -.m-graph g.m-edge.m-primary polygon, -.m-graph g.m-edge.m-primary path, -.m-graph g.m-node.m-primary ellipse, -.m-graph g.m-node.m-primary polygon, -.m-graph g.m-node.m-primary polyline { - stroke: #31708f; -} -.m-graph g.m-edge.m-success polygon, -.m-graph g.m-node.m-success:not(.m-flat) ellipse, -.m-graph g.m-node.m-success:not(.m-flat) polygon, -.m-graph g.m-edge.m-success text, -.m-graph g.m-node.m-success.m-flat text { - fill: #9ad36a; -} -.m-graph g.m-edge.m-success polygon, -.m-graph g.m-edge.m-success path, -.m-graph g.m-node.m-success ellipse, -.m-graph g.m-node.m-success polygon, -.m-graph g.m-node.m-success polyline { - stroke: #9ad36a; -} -.m-graph g.m-edge.m-warning polygon, -.m-graph g.m-node.m-warning:not(.m-flat) ellipse, -.m-graph g.m-node.m-warning:not(.m-flat) polygon, -.m-graph g.m-edge.m-warning text, -.m-graph g.m-node.m-warning.m-flat text { - fill: #f9cf79; -} -.m-graph g.m-edge.m-warning polygon, -.m-graph g.m-edge.m-warning path, -.m-graph g.m-node.m-warning ellipse, -.m-graph g.m-node.m-warning polygon, -.m-graph g.m-node.m-warning polyline { - stroke: #f9cf79; -} -.m-graph g.m-edge.m-danger polygon, -.m-graph g.m-node.m-danger:not(.m-flat) ellipse, -.m-graph g.m-node.m-danger:not(.m-flat) polygon, -.m-graph g.m-edge.m-danger text, -.m-graph g.m-node.m-danger.m-flat text { - fill: #f60000; -} -.m-graph g.m-edge.m-danger polygon, -.m-graph g.m-edge.m-danger path, -.m-graph g.m-node.m-danger ellipse, -.m-graph g.m-node.m-danger polygon, -.m-graph g.m-node.m-danger polyline { - stroke: #f60000; -} -.m-graph g.m-edge.m-info polygon, -.m-graph g.m-node.m-info:not(.m-flat) ellipse, -.m-graph g.m-node.m-info:not(.m-flat) polygon, -.m-graph g.m-edge.m-info text, -.m-graph g.m-node.m-info.m-flat text { - fill: #31708f; -} -.m-graph g.m-edge.m-info polygon, -.m-graph g.m-edge.m-info path, -.m-graph g.m-node.m-info ellipse, -.m-graph g.m-node.m-info polygon, -.m-graph g.m-node.m-info polyline { - stroke: #31708f; -} -.m-graph g.m-edge.m-dim polygon, -.m-graph g.m-node.m-dim:not(.m-flat) ellipse, -.m-graph g.m-node.m-dim:not(.m-flat) polygon, -.m-graph g.m-edge.m-dim text, -.m-graph g.m-node.m-dim.m-flat text { - fill: #bdbdbd; -} -.m-graph g.m-edge.m-dim polygon, -.m-graph g.m-edge.m-dim path, -.m-graph g.m-node.m-dim ellipse, -.m-graph g.m-node.m-dim polygon, -.m-graph g.m-node.m-dim polyline { - stroke: #bdbdbd; -} -.m-graph g.m-edge.m-type polygon, -.m-graph g.m-node.m-type:not(.m-flat) ellipse, -.m-graph g.m-node.m-type:not(.m-flat) polygon, -.m-graph g.m-edge.m-type text, -.m-graph g.m-node.m-type.m-flat text { - fill: #9ad36a; -} -.m-graph g.m-edge.m-type polygon, -.m-graph g.m-edge.m-type path, -.m-graph g.m-node.m-type ellipse, -.m-graph g.m-node.m-type polygon, -.m-graph g.m-node.m-type polyline { - stroke: #9ad36a; -} -p, -ul, -ol, -dl, -blockquote, -pre, -.m-code-figure, -.m-console-figure, -hr, -.m-note, -.m-frame, -.m-block, -div.m-button, -div.m-scroll, -table.m-table, -div.m-image, -img.m-image, -svg.m-image, -figure.m-figure, -.m-imagegrid, -div.m-math, -div.m-graph, -div.m-plot { - margin-bottom: 1rem; -} -p:last-child, -p.m-nopadb, -ul:last-child, -ul.m-nopadb, -ol:last-child, -ol.m-nopadb, -dl:last-child, -dl.m-nopadb, -blockquote:last-child, -blockquote.m-nopadb, -pre:last-child, -pre.m-nopadb, -.m-code-figure:last-child, -.m-code-figure.m-nopadb, -.m-console-figure:last-child, -.m-console-figure.m-nopadb, -hr:last-child, -hr.m-nopadb, -.m-note:last-child, -.m-note.m-nopadb, -.m-frame:last-child, -.m-frame.m-nopadb, -.m-block:last-child, -.m-block.m-nopadb, -div.m-button:last-child, -div.m-button.m-nopadb, -div.m-scroll:last-child, -div.m-scroll.m-nopadb, -table.m-table:last-child, -table.m-table.m-nopadb, -img.m-image:last-child, -img.m-image.m-nopadb, -svg.m-image:last-child, -svg.m-image.m-nopadb, -div.m-image:last-child, -div.m-image.m-nopadb, -figure.m-figure:last-child, -figure.m-figure.m-nopadb, -.m-imagegrid:last-child, -.m-imagegrid.m-nopadb, -div.m-math:last-child, -div.m-math.m-nopadb, -div.m-graph:last-child, -div.m-graph.m-nopadb, -div.m-plot:last-child, -div.m-plot.m-nopadb { - margin-bottom: 0; -} -li > p:last-child, -li > blockquote:last-child, -li > pre:last-child, -li > .m-code-figure:last-child, -li > .m-console-figure:last-child, -li > .m-note:last-child, -li > .m-frame:last-child, -li > .m-block:last-child, -li > div.m-button:last-child, -li > div.m-scroll:last-child, -li > table.m-table:last-child, -li > img.m-image:last-child, -li > svg.m-image:last-child, -li > div.m-image:last-child, -li > figure.m-figure:last-child, -li > div.m-math:last-child, -li > div.m-graph:last-child, -li > div.m-plot:last-child { - margin-bottom: 1rem; -} -li:last-child > p:last-child, -li:last-child > p.m-nopadb, -li:last-child > blockquote:last-child, -li:last-child > blockquote.m-nopadb, -li:last-child > pre:last-child, -li:last-child > pre.m-nopadb, -li:last-child > .m-code-figure:last-child, -li:last-child > .m-code-figure.m-nopadb, -li:last-child > .m-console-figure:last-child, -li:last-child > .m-console-figure.m-nopadb, -li:last-child > .m-note:last-child, -li:last-child > .m-note.m-nopadb, -li:last-child > .m-frame:last-child, -li:last-child > .m-frame.m-nopadb, -li:last-child > .m-block:last-child, -li:last-child > .m-block.m-nopadb, -li:last-child > div.m-button:last-child, -li:last-child > div.m-button.m-nopadb, -li:last-child > div.m-scroll:last-child, -li:last-child > div.m-scroll.m-nopadb, -li:last-child > table.m-table:last-child, -li:last-child > table.m-table.m-nopadb, -li:last-child > img.m-image:last-child, -li:last-child > img.m-image.m-nopadb, -li:last-child > svg.m-image:last-child, -li:last-child > svg.m-image.m-nopadb, -li:last-child > div.m-image:last-child, -li:last-child > div.m-image.m-nopadb, -li:last-child > figure.m-figure:last-child, -li:last-child > figure.m-figure.m-nopadb, -li:last-child > div.m-math:last-child, -li:last-child > div.m-math.m-nopadb, -li:last-child > div.m-graph:last-child, -li:last-child > div.m-graph.m-nopadb, -li:last-child > div.m-plot:last-child, -li:last-child > div.m-plot.m-nopadb { - margin-bottom: 0; -} - -body > header > nav { - width: 100%; - background-color: #000000; - min-height: 3rem; - font-size: 16px; -} -body > header > nav.m-navbar-landing, -body > header > nav.m-navbar-cover { - background-color: transparent; - position: relative; -} -body > header > nav.m-navbar-landing { - opacity: 0.8; -} -body > header > nav.m-navbar-cover { - background-color: #000000; - opacity: 1; -} -body > header > nav.m-navbar-landing:hover, -body > header > nav.m-navbar-cover:hover { - background-color: #000000; - opacity: 1; -} -body > header > nav.m-navbar-landing:target, -body > header > nav.m-navbar-cover:target { - background-color: #000000; - opacity: 1; -} -body > header > nav.m-navbar-landing #m-navbar-brand.m-navbar-brand-hidden { - visibility: hidden; -} -body - > header - > nav.m-navbar-landing:target - #m-navbar-brand.m-navbar-brand-hidden { - visibility: visible; -} -body > header > nav { - margin-left: auto; - margin-right: auto; - color: #ffffff; -} -body > header > nav a { - text-decoration: none; - text-transform: none; - display: inline-block; - vertical-align: middle; - line-height: 2.75rem; - color: #ffffff; -} -body > header > nav #m-navbar-brand, -body > header > nav a#m-navbar-show, -body > header > nav a#m-navbar-hide { - font-weight: 300; - font-size: 1.125rem; - padding-left: 1rem; - padding-right: 1rem; -} -body > header > nav a#m-navbar-brand, -body > header > nav #m-navbar-brand a { - text-transform: none; -} -body > header > nav a#m-navbar-brand img, -body > header > nav #m-navbar-brand a img { - height: 5rem; - vertical-align: -22.5%; - margin-right: 0.5rem; -} -body > header > nav #m-navbar-brand a { - padding-left: 0; - padding-right: 0; -} -body > header > nav #m-navbar-brand .m-thin { - font-weight: normal; -} -body > header > nav #m-navbar-brand .m-breadcrumb { - color: #bdbdbd; -} -body > header > nav a#m-navbar-show:before, -body > header > nav a#m-navbar-hide:before { - content: "\2630"; -} -body > header > nav #m-navbar-collapse { - padding-bottom: 1rem; -} -body > header > nav #m-navbar-collapse li { - border-style: solid; - border-color: transparent; - border-width: 0 0 0 0.25rem; - margin-left: -1rem; -} -body > header > nav #m-navbar-collapse li a { - border-style: solid; - border-color: transparent; - line-height: 1.5rem; - margin-left: -0.25rem; - padding-left: 0.75rem; - border-width: 0 1px 1px 1px; - width: 100%; -} -body > header > nav #m-navbar-collapse li ol { - border-color: #ddd; -} -body > header > nav #m-navbar-collapse li a#m-navbar-current { - color: #ffffff; - background-color: #000000; - border-color: #000000; -} -body > header > nav #m-navbar-collapse li ol li a#m-navbar-current { - color: #000000; - background-color: #ffffff; - border-color: #ddd; -} -body > header > nav ol { - list-style-type: none; - margin: 0; -} -body > header > nav ol ol { - padding-left: 1.5rem; -} -body > header > nav .m-row > [class*="m-col-"] { - padding-top: 0; - padding-bottom: 0; -} -body > header > nav a:hover, -body > header > nav a:focus, -body > header > nav a:active { - color: #ffffff; -} -body > header > nav #m-navbar-collapse li:hover { - border-color: #ffffff; -} -body > header > nav #m-navbar-collapse li a:hover, -body > header > nav #m-navbar-collapse li a:focus, -body > header > nav #m-navbar-collapse li a:active { - border-color: #ffffff; - background-color: #000000; -} -body > header > nav.m-navbar-landing #m-navbar-collapse li a:hover, -body > header > nav.m-navbar-cover #m-navbar-collapse li a:hover, -body > header > nav.m-navbar-landing #m-navbar-collapse li a:focus, -body > header > nav.m-navbar-cover #m-navbar-collapse li a:focus, -body > header > nav.m-navbar-landing #m-navbar-collapse li a:active, -body > header > nav.m-navbar-cover #m-navbar-collapse li a:active { - background-color: var(--header-link-active-background-color-semi); -} -body > header > nav #m-navbar-hide { - display: none; -} -body > header > nav:target #m-navbar-collapse { - display: block; -} -body > header > nav:target #m-navbar-show { - display: none; -} -body > header > nav:target #m-navbar-hide { - display: inline-block; -} -@media screen and (min-width: 768px) { - body > header > nav #m-navbar-show, - body > header > nav #m-navbar-hide, - body > header > nav:target #m-navbar-show, - body > header > nav:target #m-navbar-hide { - display: none; - } - body > header > nav #m-navbar-collapse li a { - line-height: 2.75rem; - } - body > header > nav a, - body > header > nav #m-navbar-collapse li a { - margin-left: 0; - padding-left: 1rem; - padding-right: 1rem; - white-space: nowrap; - } - body > header > nav #m-navbar-collapse { - padding-bottom: 0; - } - body > header > nav #m-navbar-collapse li ol { - background-color: #000000; - } - body > header > nav #m-navbar-collapse ol ol li { - margin-left: 0; - padding-left: 0; - border-left-width: 0; - } - body > header > nav #m-navbar-collapse ol ol li a { - padding-left: 0.75rem; - color: #000000; - background-color: #ffffff; - border-color: #ddd; - } - body > header > nav #m-navbar-collapse ol ol li a:active { - padding-left: 0.75rem; - color: #ffffff; - background-color: #353535; - border-color: #ddd; - } - body > header > nav #m-navbar-collapse ol ol li a:hover { - padding-left: 0.75rem; - color: #ffffff; - background-color: #353535; - border-color: #ddd; - } - body > header > nav #m-navbar-collapse > .m-row > ol > li { - margin-left: 0; - border-left-width: 0; - } - body > header > nav #m-navbar-collapse > .m-row > ol > li > a { - border-width: 0.25rem 0 0 0; - } - body > header > nav #m-navbar-collapse ol { - padding-left: 0; - padding-right: 0; - } - body > header > nav #m-navbar-collapse > .m-row > ol, - body > header > nav #m-navbar-collapse > .m-row > ol > li { - float: left; - } - body > header > nav #m-navbar-collapse ol ol { - z-index: 99999; - position: absolute; - visibility: hidden; - } - body > header > nav #m-navbar-collapse li:hover ol { - visibility: visible; - } -} -body > footer { - width: 100%; -} -body > footer > nav { - padding-top: 1rem; - padding-bottom: 1rem; - font-size: 0.85rem; - text-align: center; - color: #777777; - background-color: #353535; -} -body > footer > nav h3, -body > footer > nav h3 a { - text-transform: capitalize; - font-weight: normal; -} -body > footer > nav ul { - list-style-type: none; - padding: 0; - margin: 0; -} -body > footer > nav a { - text-decoration: none; - text-transform: none; - color: #999; -} -body > footer > nav a:hover, -body > footer > nav a:focus, -body > footer > nav a:active { - color: #494949; -} -body > main { - padding-top: 1rem; - padding-bottom: 1rem; -} -article h1 { - font-size: 1.75rem; -} -article h1 .m-breadcrumb { - color: #bdbdbd; - font-weight: normal; - font-size: 16px; -} -article h1 .m-breadcrumb a { - color: #26a9e0; -} -article h1 .m-breadcrumb a:hover, -article h1 a:focus, -article h1 a:active { - color: #26a9e0; -} -article hr { - width: 75%; - border-width: 2px 0 0 0 ; - border-style: solid; - border-color: #92d050; - margin: auto; -} -article section hr { - width: 50%; - border-width: 1px 0 0 0; - border-style: solid; - border-color: #ddd; - margin: auto; - padding-top: 5px; - padding-bottom: 10px; -} -article > header h1 { - font-size: 2rem; - margin-bottom: 0.5rem; -} -article h1 a, -article > header h1, -article > header h1 a, -article section > h2, -article section > h2 a, -article section > h3, -article section > h3 a, -article section > h4, -article section > h4 a, -article section > h5, -article section > h5 a, -article section > h6, -article section > h6 a { - color: #000000; -} -article h1 a:hover, -article > header h1 a:hover, -article > header h1 a:focus, -article > header h1 a:active, -article section > h2 a:hover, -article section > h2 a:focus, -article section > h2 a:active, -article section > h3 a:hover, -article section > h3 a:focus, -article section > h3 a:active, -article section > h4 a:hover, -article section > h4 a:focus, -article section > h4 a:active, -article section > h5 a:hover, -article section > h5 a:focus, -article section > h5 a:active, -article section > h6 a:hover, -article section > h6 a:focus, -article section > h6 a:active { - color: #000000; -} -article > header .m-date { - display: block; - width: 2.5rem; - float: left; - text-align: center; - line-height: 95%; - font-size: 0.75rem; - font-weight: normal; - white-space: nowrap; - border-right-style: solid; - border-right-width: 0.125rem; - border-color: #000000; - padding-right: 0.75rem; - margin-top: -0.1rem; - margin-right: 0.75rem; - margin-bottom: 0.25rem; -} -article > header .m-date-day { - display: block; - font-weight: bold; - padding-top: 0.2rem; - padding-bottom: 0.15rem; - font-size: 1.25rem; -} -article > header p { - color: #7a7a7a; - font-size: 1.125rem; -} -article > header h1::after { - content: " "; - clear: both; - display: table; -} -article > footer { - color: #969696; -} -article > footer p { - font-style: italic; - font-size: 0.85rem; - text-indent: 0; -} -article h1 a, -article > header h1 a, -article section > h2 a, -article section > h3 a, -article section > h4 a, -article section > h5 a, -article section > h6 a { - text-decoration: none; -} -#m-landing-image, -#m-cover-image, -article#m-jumbo > header #m-jumbo-image { - background-size: cover; - background-color: #666666; - background-position: center center; - background-repeat: no-repeat; - margin-top: -4rem; - padding-top: 5rem; -} -#m-landing-image { - color: #ffffff; -} -#m-cover-image { - height: 30rem; - margin-bottom: -26rem; -} -#m-landing-cover h1 { - font-size: 2.8rem; - margin-top: -0.5rem; - padding-left: 1.5rem; - padding-bottom: 1rem; - text-transform: capitalize; -} -#m-landing-cover { - padding-bottom: 10rem; - margin-bottom: -6rem; -} -article#m-jumbo { - margin-top: -1rem; -} -#m-landing-cover, -#m-cover-image > div, -article#m-jumbo > header #m-jumbo-cover { - background: linear-gradient( - transparent 0%, - transparent 50%, - #e8e8e8 100% - ); - width: 100%; - height: 100%; -} -article#m-jumbo > header h1, -article#m-jumbo > header h2 { - text-align: center; - font-weight: bold; -} -article#m-jumbo > header a { - text-decoration: none; -} -article#m-jumbo > header #m-jumbo-cover { - padding-bottom: 5rem; -} -article#m-jumbo > header #m-jumbo-image { - font-size: 2.5vmin; - margin-bottom: -3rem; -} -article#m-jumbo > header h1 { - font-size: 10vmin; -} -article#m-jumbo > header h2 { - font-size: 3vmin; -} -@media screen and (max-height: 640px), screen and (max-width: 640px) { - article#m-jumbo > header h1 { - font-size: 3rem; - } - article#m-jumbo > header #m-jumbo-image, - article#m-jumbo > header h2 { - font-size: 1rem; - } -} -article#m-jumbo > header, -article#m-jumbo > header h1, -article#m-jumbo > header a { - color: #ffffff; -} -article#m-jumbo > header a:hover, -article#m-jumbo > header a:focus, -article#m-jumbo > header a:active { - color: #f0f0f0; -} -article#m-jumbo.m-inverted > header, -article#m-jumbo.m-inverted > header h1, -article#m-jumbo.m-inverted > header a { - color: #000000; -} -article#m-jumbo.m-inverted > header a:hover, -article#m-jumbo.m-inverted > header a:focus, -article#m-jumbo.m-inverted > header a:active { - color: #0f0f0f; -} -.m-landing-news h3 a { - color: #000000; - text-decoration: none; - text-transform: capitalize; -} -.m-landing-news h3 a:hover, -.m-landing-news h3 a:hover, -.m-landing-news h3 a:focus, -.m-landing-news h3 a:active { - color: #000000; -} -.m-landing-news time { - display: inline-block; - margin-left: 1rem; - float: right; -} -.m-article-pagination { - text-align: center; - padding: 1rem; -} -nav.m-navpanel { - text-align: center; -} -nav.m-navpanel h3 { - text-transform: capitalize; - font-weight: normal; -} -nav.m-navpanel ol { - text-transform: capitalize; -} -nav.m-navpanel ol, -nav.m-navpanel ul { - list-style-type: none; - padding: 0; -} -nav.m-navpanel a { - color: #292929; - text-decoration: none; -} -nav.m-navpanel a:hover, -nav.m-navpanel a:focus, -nav.m-navpanel a:active { - color: #cb4b16; -} -ul.m-tagcloud li { - display: inline; -} -ul.m-tagcloud li.m-tag-1 { - font-size: 0.75rem; -} -ul.m-tagcloud li.m-tag-2 { - font-size: 0.825rem; -} -ul.m-tagcloud li.m-tag-3 { - font-size: 1rem; -} -ul.m-tagcloud li.m-tag-4 { - font-size: 1.25rem; -} -ul.m-tagcloud li.m-tag-5 { - font-size: 1.5rem; -} -article section:target figure.m-code-figure, -article section:target figure.m-console-figure { - z-index: 1; -} -article, -article > header, -article section { - margin-bottom: 1rem; -} -article:last-child, -article section:last-child { - margin-bottom: 0; -} -.m-container-inflatable section:target > .m-note, -.m-container-inflatable section:target > .m-frame, -.m-container-inflatable section:target > .m-block, -.m-container-inflatable section:target > pre, -.m-container-inflatable section:target > .m-code-figure > pre:first-child, -.m-container-inflatable section:target > .m-console-figure > pre:first-child, -.m-container-inflatable section:target section > .m-note, -.m-container-inflatable section:target section > .m-frame, -.m-container-inflatable section:target section > .m-block, -.m-container-inflatable section:target section > pre, -.m-container-inflatable - section:target - section - > .m-code-figure - > pre:first-child, -.m-container-inflatable - section:target - section - > .m-console-figure - > pre:first-child, -.m-container-inflatable section:target [class*="m-center-"] > .m-note, -.m-container-inflatable section:target [class*="m-center-"] > .m-frame, -.m-container-inflatable section:target [class*="m-center-"] > .m-block, -.m-container-inflatable section:target [class*="m-center-"] > pre, -.m-container-inflatable - section:target - [class*="m-center-"] - > .m-code-figure - > pre:first-child, -.m-container-inflatable - section:target - [class*="m-center-"] - > .m-console-figure - > pre:first-child, -.m-container-inflatable section:target [class*="m-left-"] > .m-note, -.m-container-inflatable section:target [class*="m-left-"] > .m-frame, -.m-container-inflatable section:target [class*="m-left-"] > .m-block, -.m-container-inflatable section:target [class*="m-left-"] > pre, -.m-container-inflatable - section:target - [class*="m-left-"] - > .m-code-figure - > pre:first-child, -.m-container-inflatable - section:target - [class*="m-left-"] - > .m-console-figure - > pre:first-child, -.m-container-inflatable section:target [class*="m-right-"] > .m-note, -.m-container-inflatable section:target [class*="m-right-"] > .m-frame, -.m-container-inflatable section:target [class*="m-right-"] > .m-block, -.m-container-inflatable section:target [class*="m-right-"] > pre, -.m-container-inflatable - section:target - [class*="m-right-"] - > .m-code-figure - > pre:first-child, -.m-container-inflatable - section:target - [class*="m-right-"] - > .m-console-figure - > pre:first-child, -.m-container-inflatable section:target .m-container-inflate > .m-note, -.m-container-inflatable section:target .m-container-inflate > .m-frame, -.m-container-inflatable section:target .m-container-inflate > .m-block, -.m-container-inflatable section:target .m-container-inflate > pre, -.m-container-inflatable - section:target - .m-container-inflate - > .m-code-figure - > pre:first-child, -.m-container-inflatable - section:target - .m-container-inflate - > .m-console-figure - > pre:first-child { - margin-left: -1rem; - border-left-style: solid; - border-left-width: 0.25rem; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - padding-left: 0.75rem; -} -.m-container-inflatable section:target > .m-code-figure::before, -.m-container-inflatable section:target > .m-console-figure::before, -.m-container-inflatable section:target section > .m-code-figure::before, -.m-container-inflatable section:target section > .m-console-figure::before, -.m-container-inflatable - section:target - [class*="m-center-"] - > .m-code-figure::before, -.m-container-inflatable - section:target - [class*="m-center-"] - > .m-console-figure::before, -.m-container-inflatable - section:target - [class*="m-left-"] - > .m-code-figure::before, -.m-container-inflatable - section:target - [class*="m-left-"] - > .m-console-figure::before, -.m-container-inflatable - section:target - [class*="m-right-"] - > .m-code-figure::before, -.m-container-inflatable - section:target - [class*="m-right-"] - > .m-console-figure::before, -.m-container-inflatable - section:target - .m-container-inflate - > .m-code-figure::before, -.m-container-inflatable - section:target - .m-container-inflate - > .m-console-figure::before { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-width: 0.25rem; -} -.m-container-inflatable section:target > .m-code-figure > pre.m-nopad, -.m-container-inflatable section:target > .m-console-figure > pre.m-nopad { - margin-left: -0.75rem; - padding-left: -0.75rem; -} -@media screen and (min-width: 576px) { - .m-container-inflatable section:target .m-center-s > .m-note, - .m-container-inflatable section:target .m-center-s > pre, - .m-container-inflatable - section:target - .m-center-s - > figure.m-code-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-center-s - > figure.m-console-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-right-s - > figure.m-code-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-right-s - > figure.m-console-figure - > pre:first-child { - border-left-width: 0; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - padding-left: 1rem; - } - .m-container-inflatable section:target .m-center-s > .m-block, - .m-container-inflatable section:target .m-right-s > .m-block { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - } - .m-container-inflatable section:target .m-center-s > .m-frame, - .m-container-inflatable section:target .m-right-s > .m-frame { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0.125rem; - padding-left: 0.875rem; - } - .m-container-inflatable section:target .m-right-s > .m-block, - .m-container-inflatable section:target .m-right-s > .m-frame { - margin-left: 0; - } - .m-container-inflatable section:target .m-right-s > .m-note, - .m-container-inflatable section:target .m-right-s > pre { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - margin-left: 0; - border-left-width: 0; - padding-left: 1rem; - } - .m-container-inflatable - section:target - .m-center-s - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-center-s - > figure.m-console-figure::before, - .m-container-inflatable - section:target - .m-right-s - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-right-s - > figure.m-console-figure::before { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0.125rem; - } -} -@media screen and (min-width: 768px) { - .m-container-inflatable section:target .m-center-m > .m-note, - .m-container-inflatable section:target .m-center-m > pre, - .m-container-inflatable - section:target - .m-center-m - > figure.m-code-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-center-m - > figure.m-console-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-right-m - > figure.m-code-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-right-m - > figure.m-console-figure - > pre:first-child { - border-left-width: 0; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - padding-left: 1rem; - } - .m-container-inflatable section:target .m-center-m > .m-block, - .m-container-inflatable section:target .m-right-m > .m-block { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - } - .m-container-inflatable section:target .m-center-m > .m-frame, - .m-container-inflatable section:target .m-right-m > .m-frame { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0.125rem; - padding-left: 0.875rem; - } - .m-container-inflatable section:target .m-right-m > .m-block, - .m-container-inflatable section:target .m-right-m > .m-frame { - margin-left: 0; - } - .m-container-inflatable section:target .m-right-m > .m-note, - .m-container-inflatable section:target .m-right-m > pre { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - margin-left: 0; - border-left-width: 0; - padding-left: 1rem; - } - .m-container-inflatable - section:target - .m-center-m - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-center-m - > figure.m-console-figure::before, - .m-container-inflatable - section:target - .m-right-m - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-right-m - > figure.m-console-figure::before { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0.125rem; - } -} -@media screen and (min-width: 992px) { - .m-container-inflatable section:target .m-center-l > .m-note, - .m-container-inflatable section:target .m-center-l > pre, - .m-container-inflatable - section:target - .m-center-l - > figure.m-code-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-center-l - > figure.m-console-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-right-l - > figure.m-code-figure - > pre:first-child, - .m-container-inflatable - section:target - .m-right-l - > figure.m-console-figure - > pre:first-child { - border-left-width: 0; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - padding-left: 1rem; - } - .m-container-inflatable section:target .m-center-l > .m-block, - .m-container-inflatable section:target .m-right-l > .m-block { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - } - .m-container-inflatable section:target .m-center-l > .m-frame, - .m-container-inflatable section:target .m-right-l > .m-frame { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0.125rem; - padding-left: 0.875rem; - } - .m-container-inflatable section:target .m-right-l > .m-block, - .m-container-inflatable section:target .m-right-l > .m-frame { - margin-left: 0; - } - .m-container-inflatable section:target .m-right-l > .m-note, - .m-container-inflatable section:target .m-right-l > pre { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - margin-left: 0; - border-left-width: 0; - padding-left: 1rem; - } - .m-container-inflatable - section:target - .m-center-l - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-center-l - > figure.m-console-figure::before, - .m-container-inflatable - section:target - .m-right-l - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-right-l - > figure.m-console-figure::before { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0.125rem; - } -} -.m-container-inflatable section:target > figure.m-code-figure::before, -.m-container-inflatable section:target > figure.m-console-figure::before, -.m-container-inflatable section:target section > figure.m-code-figure::before, -.m-container-inflatable - section:target - section - > figure.m-console-figure::before, -.m-container-inflatable - section:target - [class*="m-center-"] - > figure.m-code-figure::before, -.m-container-inflatable - section:target - [class*="m-center-"] - > figure.m-console-figure::before, -.m-container-inflatable - section:target - [class*="m-left-"] - > figure.m-code-figure::before, -.m-container-inflatable - section:target - [class*="m-left-"] - > figure.m-console-figure::before, -.m-container-inflatable - section:target - [class*="m-right-"] - > figure.m-code-figure::before, -.m-container-inflatable - section:target - [class*="m-right-"] - > figure.m-console-figure::before, -.m-container-inflatable - section:target - .m-container-inflatable - > figure.m-code-figure::before, -.m-container-inflatable - section:target - .m-container-inflatable - > figure.m-console-figure::before { - border-left-color: #ddd; -} -@media screen and (min-width: 576px) { - .m-container-inflatable - section:target - .m-center-s - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-right-s - > figure.m-code-figure::before { - border-color: #f7f7f7; - } - .m-container-inflatable - section:target - .m-center-s - > figure.m-console-figure::before, - .m-container-inflatable - section:target - .m-right-s - > figure.m-console-figure::before { - border-color: #f7f7f7; - } -} -@media screen and (min-width: 768px) { - .m-container-inflatable - section:target - .m-center-m - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-right-m - > figure.m-code-figure::before { - border-color: #f7f7f7; - } - .m-container-inflatable - section:target - .m-center-m - > figure.m-console-figure::before, - .m-container-inflatable - section:target - .m-right-m - > figure.m-console-figure::before { - border-color: #f7f7f7; - } -} -@media screen and (min-width: 992px) { - .m-container-inflatable - section:target - .m-center-l - > figure.m-code-figure::before, - .m-container-inflatable - section:target - .m-right-l - > figure.m-code-figure::before { - border-color: #f7f7f7; - } - .m-container-inflatable - section:target - .m-center-l - > figure.m-console-figure::before, - .m-container-inflatable - section:target - .m-right-l - > figure.m-console-figure::before { - border-color: #f7f7f7; - } -} -.m-container-inflatable section:target pre, -.m-container-inflatable section:target figure.m-code-figure > pre:first-child, -.m-container-inflatable - section:target - figure.m-console-figure - > pre:first-child { - border-color: #ddd; -} -.m-container-inflatable section:target .m-note.m-default { - border-color: #ddd; -} -.m-container-inflatable section:target .m-note.m-primary { - border-color: #31708f; -} -.m-container-inflatable section:target .m-note.m-success { - border-color: #9ad36a; -} -.m-container-inflatable section:target .m-note.m-warning { - border-color: #f9cf79; -} -.m-container-inflatable section:target .m-note.m-danger { - border-color: #f60000; -} -.m-container-inflatable section:target .m-note.m-info { - border-color: #31708f; -} -.m-container-inflatable section:target .m-note.m-dim { - border-color: #bdbdbd; -} - -.m-code .c { - color: #95a5a6; -} -.m-code .err { - color: #a61717; -} -.m-code .k { - color: #728e00; -} -.m-code .n { - color: #434f54; -} -.m-code .o { - color: #728e00; -} -.m-code .ch { - color: #95a5a6; -} -.m-code .cm { - color: #95a5a6; -} -.m-code .cp { - color: #728e00; -} -.m-code .cpf { - color: #95a5a6; -} -.m-code .c1 { - color: #95a5a6; -} -.m-code .cs { - color: #95a5a6; -} -.m-code .kc { - color: #00979d; -} -.m-code .kd { - color: #728e00; -} -.m-code .kn { - color: #728e00; -} -.m-code .kp { - color: #00979d; -} -.m-code .kr { - color: #00979d; -} -.m-code .kt { - color: #00979d; -} -.m-code .m { - color: #8a7b52; -} -.m-code .s { - color: #7f8c8d; -} -.m-code .na { - color: #434f54; -} -.m-code .nb { - color: #728e00; -} -.m-code .nc { - color: #434f54; -} -.m-code .no { - color: #434f54; -} -.m-code .nd { - color: #434f54; -} -.m-code .ni { - color: #434f54; -} -.m-code .ne { - color: #434f54; -} -.m-code .nf { - color: #d35400; -} -.m-code .nl { - color: #434f54; -} -.m-code .nn { - color: #434f54; -} -.m-code .nx { - color: #728e00; -} -.m-code .py { - color: #434f54; -} -.m-code .nt { - color: #434f54; -} -.m-code .nv { - color: #434f54; -} -.m-code .ow { - color: #728e00; -} -.m-code .mb { - color: #8a7b52; -} -.m-code .mf { - color: #8a7b52; -} -.m-code .mh { - color: #8a7b52; -} -.m-code .mi { - color: #8a7b52; -} -.m-code .mo { - color: #8a7b52; -} -.m-code .sa { - color: #7f8c8d; -} -.m-code .sb { - color: #7f8c8d; -} -.m-code .sc { - color: #7f8c8d; -} -.m-code .dl { - color: #7f8c8d; -} -.m-code .sd { - color: #7f8c8d; -} -.m-code .s2 { - color: #7f8c8d; -} -.m-code .se { - color: #7f8c8d; -} -.m-code .sh { - color: #7f8c8d; -} -.m-code .si { - color: #7f8c8d; -} -.m-code .sx { - color: #7f8c8d; -} -.m-code .sr { - color: #7f8c8d; -} -.m-code .s1 { - color: #7f8c8d; -} -.m-code .ss { - color: #7f8c8d; -} -.m-code .bp { - color: #728e00; -} -.m-code .fm { - color: #d35400; -} -.m-code .vc { - color: #434f54; -} -.m-code .vg { - color: #434f54; -} -.m-code .vi { - color: #434f54; -} -.m-code .vm { - color: #434f54; -} -.m-code .il { - color: #8a7b52; -} - -.m-console .hll { background-color: #ffffcc } -.m-console .g-AnsiBackgroundBlack { background-color: #232627 } -.m-console .g-AnsiBackgroundBlue { background-color: #1d99f3 } -.m-console .g-AnsiBackgroundBrightBlack { background-color: #7f8c8d } -.m-console .g-AnsiBackgroundBrightBlue { background-color: #3daee9 } -.m-console .g-AnsiBackgroundBrightCyan { background-color: #16a085 } -.m-console .g-AnsiBackgroundBrightGreen { background-color: #1cdc9a } -.m-console .g-AnsiBackgroundBrightMagenta { background-color: #8e44ad } -.m-console .g-AnsiBackgroundBrightRed { background-color: #c0392b } -.m-console .g-AnsiBackgroundBrightWhite { background-color: #ffffff } -.m-console .g-AnsiBackgroundBrightYellow { background-color: #fdbc4b } -.m-console .g-AnsiBackgroundCyan { background-color: #1abc9c } -.m-console .g-AnsiBackgroundDefault { background-color: #fcfcfc } -.m-console .g-AnsiBackgroundGreen { background-color: #11d116 } -.m-console .g-AnsiBackgroundMagenta { background-color: #9b59b6 } -.m-console .g-AnsiBackgroundRed { background-color: #ed1515 } -.m-console .g-AnsiBackgroundWhite { background-color: #fcfcfc } -.m-console .g-AnsiBackgroundYellow { background-color: #f67400 } -.m-console .g-AnsiBlack { color: #232627 } -.m-console .g-AnsiBlue { color: #1d99f3 } -.m-console .g-AnsiBrightBlack { color: #7f8c8d; font-weight: bold } -.m-console .g-AnsiBrightBlue { color: #3daee9; font-weight: bold } -.m-console .g-AnsiBrightCyan { color: #16a085; font-weight: bold } -.m-console .g-AnsiBrightDefault { color: #ffffff; font-weight: bold } -.m-console .g-AnsiBrightGreen { color: #1cdc9a; font-weight: bold } -.m-console .g-AnsiBrightMagenta { color: #8e44ad; font-weight: bold } -.m-console .g-AnsiBrightRed { color: #c0392b; font-weight: bold } -.m-console .g-AnsiBrightWhite { color: #ffffff; font-weight: bold } -.m-console .g-AnsiBrightYellow { color: #fdbc4b; font-weight: bold } -.m-console .g-AnsiCyan { color: #1abc9c } -.m-console .g-AnsiDefault { color: #fcfcfc } -.m-console .g-AnsiGreen { color: #11d116 } -.m-console .g-AnsiMagenta { color: #9b59b6 } -.m-console .g-AnsiRed { color: #ed1515 } -.m-console .g-AnsiWhite { color: #fcfcfc } -.m-console .g-AnsiYellow { color: #f67400 } -.m-console .go { color: #fcfcfc } -.m-console .gp { color: #16a085; font-weight: bold } -.m-console .w { color: #fcfcfc } - -a.m-doc, a.m-doc-self, a.m-doc-external, -ul.m-doc li.m-doc-expansible > a:first-child, ul.m-doc li.m-doc-collapsible > a:first-child, -.m-code.m-inverted.m-doc-include > a { - text-decoration: none; -} -a.m-doc, a.m-doc-self { - font-weight: bold; -} -.m-thin a.m-doc, .m-thin a.m-doc-self { - font-weight: normal; -} -ul.m-doc li.m-doc-expansible > a:first-child, -ul.m-doc li.m-doc-collapsible > a:first-child, -ul.m-doc li.m-doc-expansible > a:first-child:hover, -ul.m-doc li.m-doc-expansible > a:first-child:focus, -ul.m-doc li.m-doc-expansible > a:first-child:active, -ul.m-doc li.m-doc-collapsible > a:first-child:hover, -ul.m-doc li.m-doc-collapsible > a:first-child:focus, -ul.m-doc li.m-doc-collapsible > a:first-child:active { - color: #000000; -} -a.m-doc-self, -ul.m-doc li.m-doc-expansible > a:first-child:before, -ul.m-doc li.m-doc-collapsible > a:first-child:before { - color: #26a9e0; -} -a.m-doc-self:hover, a.m-doc-self:focus, a.m-doc-self:active, -ul.m-doc li.m-doc-expansible > a:first-child:hover::before, -ul.m-doc li.m-doc-expansible > a:first-child:focus::before, -ul.m-doc li.m-doc-expansible > a:first-child:active::before, -ul.m-doc li.m-doc-collapsible > a:first-child:hover::before, -ul.m-doc li.m-doc-collapsible > a:first-child:focus::before, -ul.m-doc li.m-doc-collapsible > a:first-child:active::before { - color: #26a9e0; -} -h3 a.m-doc-external { - font-weight: normal; -} -span.m-doc-wrap-bumper { - margin-right: -1rem; - display: inline-block; - vertical-align: text-top; -} -span.m-doc-wrap { - padding-left: 1rem; - display: inline-block; - vertical-align: text-top; - white-space: pre-line; - max-width: 100%; -} -dl.m-doc dd { - margin-bottom: 0.5rem; -} -dl.m-doc dd { - margin-left: 0; - padding-left: 2.5rem; -} -dl.m-doc dt:target, dl.m-doc dt:target + dd { - margin-left: -1.0rem; - border-left-style: solid; - border-left-width: 0.25rem; - border-color: #000000; -} -dl.m-doc dt:target { padding-left: 0.75rem; } -dl.m-doc dt:target + dd { padding-left: 3.25rem; } -ul.m-doc { - list-style: none; - margin-left: 1.0375rem; - padding-left: 0.9rem; - border-left-color: #ddd; - border-left-width: 0.0625rem; - border-left-style: solid; -} -ul.m-doc li { - text-indent: -1rem; - padding-left: 1rem; -} -ul.m-doc li.m-doc-expansible > ul { - display: none; -} -ul.m-doc li.m-doc-expansible, ul.m-doc li.m-doc-collapsible { - padding-left: 0.6rem; -} -ul.m-doc li.m-doc-expansible > ul.m-doc, ul.m-doc li.m-doc-collapsible > ul.m-doc { - margin-left: 0.5rem; -} -ul.m-doc li.m-doc-expansible > a:first-child:before, ul.m-doc li.m-doc-collapsible > a:first-child:before { - background-color: #e8e8e8; - display: inline-block; - width: 0.4rem; - font-weight: bold; -} -ul.m-doc li.m-doc-expansible > a:first-child:before { content: '⊕'; } -ul.m-doc li.m-doc-collapsible > a:first-child:before { content: '⊖'; } -h1 .m-doc-template, h1 .m-doc-include { - font-size: 1.3rem; - font-weight: normal; - float: right; -} -h1 .m-doc-include:last-child { - margin-bottom: -0.5rem; -} -h3 .m-doc-template, h3 .m-doc-include { - font-size: 1rem; - font-weight: normal; -} -.m-doc-template, dl.m-doc dd, ul.m-doc li > span.m-doc { - color: #bdbdbd; -} -dl.m-doc dd svg.m-math, ul.m-doc li > span.m-doc svg.m-math { - fill: #bdbdbd; -} -.m-doc-template a, dl.m-doc dd a, ul.m-doc li > span.m-doc a { - color: #c0c0c0; -} -.m-doc-template a:hover, .m-doc-template a:focus, .m-doc-template a:active, -dl.m-doc dd a:hover, dl.m-doc dd a:focus, dl.m-doc dd a:active, -ul.m-doc li > span.m-doc a:hover, ul.m-doc li > span.m-doc a:focus, ul.m-doc li > span.m-doc a:active { - color: #949494; -} -.m-code.m-inverted.m-doc-include > a:link, -.m-code.m-inverted.m-doc-include > a:visited { - opacity: 0.6666; -} -.m-code.m-inverted.m-doc-include > a:hover, -.m-code.m-inverted.m-doc-include > a:focus, -.m-code.m-inverted.m-doc-include > a:active { - opacity: 1; -} -article section.m-doc-details > div { - margin-top: 0; - margin-left: 0; - margin-right: 0; - position: relative; - padding: 1rem; -} -article section.m-doc-details > div::before { - position: absolute; - content: ' '; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: -1; - border-style: solid; - border-width: 0.125rem; - border-radius: 0px; - border-color: #f7f7f7; -} -article section.m-doc-details > div > h3:first-child { - position: relative; - margin: -1rem -1rem 1rem -1rem; - padding: 0.5rem 1rem; - background-color: #f7f7f7; - border-top-left-radius: 0px; - border-top-right-radius: 0px; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} -article section.m-doc-details:target { - border-color: transparent; -} -article section.m-doc-details:target > div { - z-index: 1; -} -.m-container-inflatable > .m-row > [class*='m-col-'] section.m-doc-details > div { - margin-left: -1rem; - margin-right: -1rem; -} -.m-container-inflatable section.m-doc-details:target > div > h3:first-child, -.m-container-inflatable section.m-doc-details:target section > div > h3:first-child { - margin-left: -1.0rem; - border-left-style: solid; - border-left-color: #000000; - border-left-width: 0.25rem; - padding-left: 0.75rem; -} -.m-container-inflatable section.m-doc-details:target > div::before, -.m-container-inflatable section-dox-details:target section > div.m::before { - border-left-width: 0.25rem; - border-left-color: #000000; -} -a.m-doc-search-icon { - padding-left: 1rem; - padding-right: 1rem; -} -a.m-doc-search-icon svg { - fill: #ffffff; -} -body > header > nav #m-navbar-collapse a.m-doc-search-icon svg { - vertical-align: -5%; -} -a.m-doc-search-icon:focus svg, a.m-doc-search-icon:hover svg, a.m-doc-search-icon:active svg { - fill: #ffffff; -} -.m-doc-search { - display: none; - z-index: 10; - position: fixed; - left: 0; - right: 0; - top: 0; - bottom: 0; - background-color: #000000; -} -.m-doc-search:target { - display: block; -} -.m-doc-search > a { - display: block; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; -} -.m-doc-search-header { - margin-top: 2.5rem; - padding: 0.5rem 1rem; - height: 2rem; -} -.m-doc-search-header > div:first-child { - float: right; -} -.m-doc-search-content { - background-color: #000000; - border-radius: 0px; - padding: 1rem; -} -.m-doc-search input { - width: 100%; - height: 3rem; - font-size: 1.2rem; - border-width: 0; - color: #000000; - background-color: transparent; - border-radius: 0px; - margin-bottom: 1rem; - padding: 0 1rem; -} -.m-doc-search #search-notfound { - display: none; -} -.m-doc-search ul#search-results { - list-style-type: none; - padding-left: 0; - max-height: calc(100vh - 12.5rem); - overflow-y: auto; - display: none; -} -.m-doc-search ul#search-results li a { - display: block; - padding-left: 1rem; - padding-right: 1rem; - text-decoration: none; - width: 100%; - line-height: 1.5rem; - color: #000000; -} -.m-doc-search ul#search-results li a > div { - white-space: nowrap; - overflow: hidden; -} -.m-doc-search ul#search-results li a > div:not(.m-doc-search-alias) { - direction: rtl; -} -.m-doc-search ul#search-results li a .m-label { - float: right; - line-height: 1rem; - margin-top: 0.1rem; - margin-left: 0.25rem; -} -.m-doc-search ul#search-results li a .m-label.m-flat { - margin-right: -0.75rem; -} -.m-doc-search ul#search-results li#search-current a { - background-color: transparent; -} -.m-doc-search ul#search-results li#search-current.m-doc-search-copied a { - background-color: transparent; -} -.m-doc-search-typed { - color: #26a9e0; -} -.m-doc-search input[type="search"] { -webkit-appearance: textfield; } -.m-doc-search input[type="search"]::-webkit-search-decoration, -.m-doc-search input[type="search"]::-webkit-search-cancel-button, -.m-doc-search input[type="search"]::-webkit-search-results-button, -.m-doc-search input[type="search"]::-webkit-search-results-decoration { - display: none; -} diff --git a/docs/documentExamples.py b/docs/documentExamples.py deleted file mode 100644 index ddce971..0000000 --- a/docs/documentExamples.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python -import fileinput -import re -import os -import glob - -fileDir = os.path.dirname(os.path.realpath("__file__")) -# print(fileDir) - -output_file = "examples.dox" -read_mes = [ - "../examples/ReadMe.md", - "../examples/a_wild_card/ReadMe.md", - "../examples/b_address_change/ReadMe.md", - "../examples/c_check_all_addresses/ReadMe.md", - "../examples/d_simple_logger/ReadMe.md", - "../examples/e_continuous_measurement/ReadMe.md", - "../examples/f_basic_data_request/ReadMe.md", - "../examples/g_terminal_window/ReadMe.md", - "../examples/h_SDI-12_slave_implementation/ReadMe.md", - "../examples/i_SDI-12_interface/ReadMe.md", - "../examples/j_external_pcint_library/ReadMe.md", - "../examples/k_concurrent_logger/ReadMe.md", -] - -if not os.path.exists(os.path.join(fileDir, "examples")): - os.makedirs(os.path.join(fileDir, "examples")) - -for filename in read_mes: - out_path = os.path.join(fileDir, "examples") - out_dir = filename.split("/")[2] - out_name = out_dir + ".dox" - if out_name == "ReadMe.md.dox": - out_name = "examples.dox" - abs_out = os.path.join(out_path, out_name) - # print(abs_out) - # with open(output_file, 'w+') as out_file: - with open(abs_out, "w+") as out_file: - - abs_file_path = os.path.join(fileDir, filename) - abs_file_path = os.path.abspath(os.path.realpath(abs_file_path)) - # print(abs_file_path) - - with open(abs_file_path, "r") as in_file: # open in readonly mode - out_file.write("/**\n") - if out_name != "examples.dox": - # out_file.write( - # "@example{{lineno}} {} @m_examplenavigation{{examples_page,{}/}} @m_footernavigation \n\n".format( - # filename.replace("..\\examples\\", "").replace( - # "\\ReadMe.md", ".ino" - # ), out_dir - # ) - # ) - out_file.write( - "@example{{lineno}} {} @m_examplenavigation{{examples_page,}} @m_footernavigation \n\n".format( - filename.replace("../examples/", "").replace( - "/ReadMe.md", ".ino" - ) - ) - ) - # out_file.write( - # "@example{{lineno}} {} \n\n".format( - # filename.replace("..\\examples\\", "").replace( - # "\\ReadMe.md", ".ino" - # ) - # ) - # ) - # out_file.write('\n@tableofcontents\n\n') - - print_me = True - skip_me = False - i = 1 - lines = in_file.readlines() - for line in lines: - # print(i, print_me, skip_me, line) - - # Remove markdown comment tags from doxygen commands within the markdown - if print_me and not skip_me: - new_line = ( - re.sub(r"\[//\]: # \( @(\w+?.*) \)", r"@\1", line) - .replace("```ini", "@code{.ini}") - .replace("```cpp", "@code{.cpp}") - .replace("```", "@endcode") - ) - if out_name != "examples.dox": - new_line = new_line.replace("@page", "@section") - # .replace('@section', '') - # .replace('@subsection', '') - # .replace('@subsubsection', '') - # .replace('@paragraph', '') - # .replace('@par', '') - out_file.write(new_line) - - # using skip_me to skip single lines, so unset it after reading a line - if skip_me: - skip_me = False - - # a page, section, subsection, or subsubsection commands followed - # immediately with by a markdown header leads to that section appearing - # twice in the doxygen html table of contents. - # I'm putting the section markers right above the header and then will skip the header. - if re.match(r"\[//\]: # \( @mainpage", line) is not None: - skip_me = True - if re.match(r"\[//\]: # \( @page", line) is not None: - skip_me = True - if re.match(r"\[//\]: # \( @.*section", line) is not None: - skip_me = True - if re.match(r"\[//\]: # \( @paragraph", line) is not None: - skip_me = True - - # I'm using these comments to fence off content that is only intended for - # github mardown rendering - if "[//]: # ( Start GitHub Only )" in line: - print_me = False - - if "[//]: # ( End GitHub Only )" in line: - print_me = True - - i += 1 - - out_file.write("\n*/\n\n") diff --git a/docs/dotDiagrams.dox b/docs/dotDiagrams.dox new file mode 100644 index 0000000..5ea9af7 --- /dev/null +++ b/docs/dotDiagrams.dox @@ -0,0 +1,29 @@ +[//]: # ( @dot ) + +digraph xor { + graph[rankdir=BT]; + node[shape=record]; + + rxValue[label="{ {rxValue|0|0|0|0|0|0|0|1} }"]; + maskState[label=" {rxMask|rxState}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}"]; + + maskState:rxMask0 -> rxValue:rxValue0[label="bit-wise or (|=) puts the one \nfrom the rxMask into the rxValue"]; +} + +[//]: # ( @enddot ) + + + +[//]: # ( @dot ) + +digraph xor { + graph[rankdir=BT]; + node[shape=record]; + + rxValue[label="{ {rxValue|0|0|0|0|0|0|0|1} }"]; + maskState[label=" {rxMask|rxState}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}|{0|0}"]; + + maskState:rxMask0 -> rxValue:rxValue0[label="nothing happens",arrowhead="obox"]; +} + +[//]: # ( @enddot ) diff --git a/docs/fixXmlExampleSections.py b/docs/fixXmlExampleSections.py deleted file mode 100644 index 539bdc8..0000000 --- a/docs/fixXmlExampleSections.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -import fileinput -import re -import os -import glob -import xml.etree.ElementTree as ET - -fileDir = os.path.dirname(os.path.realpath("__file__")) -# print("Program Directory: {}".format(fileDir)) -relative_dir = "../../Arduino-SDI-12Doxygen/xml/" -abs_file_path = os.path.join(fileDir, relative_dir) -abs_file_path = os.path.abspath(os.path.realpath(abs_file_path)) -# print("XML Directory: {}".format(fileDir)) - -all_files = [ - f - for f in os.listdir(abs_file_path) - if os.path.isfile(os.path.join(abs_file_path, f)) and f.endswith("8ino-example.xml") -] - -for filename in all_files: - print(filename) - - tree = ET.parse(os.path.join(abs_file_path, filename)) - root = tree.getroot() - - needs_to_be_fixed = False - for definition in root.iter("compounddef"): - # print(definition.attrib) - compound_id = definition.attrib["id"] - # print(compound_id) - # print("---") - - for i in range(6): - for section in definition.iter("sect" + str(i)): - # print(section.attrib) - section_id = section.attrib["id"] - if not section_id.startswith(compound_id): - # print("problem!") - needs_to_be_fixed = True - dox_loc = section_id.find(".dox_") - section_suffix = section_id[dox_loc + 6 :] - # print(section_suffix) - corrected_id = compound_id + "_" + section_suffix - # print(corrected_id) - section.attrib["id"] = corrected_id - - if needs_to_be_fixed: - tree.write(os.path.join(abs_file_path, filename + "_fixed")) - os.rename( - os.path.join(abs_file_path, filename), - os.path.join(abs_file_path, filename + "_original"), - ) - os.rename( - os.path.join(abs_file_path, filename + "_fixed"), - os.path.join(abs_file_path, filename), - ) - # print() diff --git a/docs/footer.html b/docs/footer.html new file mode 100644 index 0000000..bbadb8a --- /dev/null +++ b/docs/footer.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/docs/header.html b/docs/header.html new file mode 100644 index 0000000..659e88d --- /dev/null +++ b/docs/header.html @@ -0,0 +1,81 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/docs/markdown_prefilter.py b/docs/markdown_prefilter.py deleted file mode 100644 index 6c2b4ee..0000000 --- a/docs/markdown_prefilter.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -import fileinput, re - -print_me = True -skip_me = False -i = 1 -# for line in fileinput.input(openhook=fileinput.hook_encoded("utf-8", "surrogateescape")): -for line in fileinput.input(): - # print(i, print_me, skip_me, line) - - # Remove markdown comment tags from doxygen commands within the markdown - if print_me and not skip_me: - print(re.sub(r'\[//\]: # \( @(\w+?.*) \)', r'@\1', line), end="") - - # using skip_me to skip single lines, so unset it after reading a line - if skip_me: - skip_me = False; - - # a page, section, subsection, or subsubsection commands followed - # immediately with by a markdown header leads to that section appearing - # twice in the doxygen html table of contents. - # I'm putting the section markers right above the header and then will skip the header. - if re.match(r'\[//\]: # \( @mainpage', line) is not None: - skip_me = True; - if re.match(r'\[//\]: # \( @page', line) is not None: - skip_me = True; - if re.match(r'\[//\]: # \( @.*section', line) is not None: - skip_me = True; - if re.match(r'\[//\]: # \( @paragraph', line) is not None: - skip_me = True; - - # I'm using these comments to fence off content that is only intended for - # github mardown rendering - if "[//]: # ( Start GitHub Only )" in line: - print_me = False - - if "[//]: # ( End GitHub Only )" in line: - print_me = True - - i += 1 diff --git a/docs/mcss-Doxyfile b/docs/mcss-Doxyfile index 107316a..2a08286 100644 --- a/docs/mcss-Doxyfile +++ b/docs/mcss-Doxyfile @@ -2,9 +2,9 @@ GENERATE_HTML = NO GENERATE_XML = YES XML_PROGRAMLISTING = NO -HTML_OUTPUT = ../Arduino-SDI-12Doxygen/m.css -SHOW_INCLUDE_FILES = YES -WARN_LOGFILE = mcssDoxygenOutput.log +HTML_OUTPUT = ../Arduino-SDI-12_Doxygen/m.css +SHOW_INCLUDE_FILES = YES +WARN_LOGFILE = output_mcss.log PROJECT_REPOSITORY = https://github.com/EnviroDIY/Arduino-SDI-12 ALIASES += \ "m_div{1}=@xmlonly@endxmlonly" \ @@ -18,4 +18,8 @@ ALIASES += \ "m_keyword{3}=@xmlonly@endxmlonly" \ "m_enum_values_as_keywords=@xmlonly@endxmlonly" \ "m_since{2}=@since @m_class{m-label m-success m-flat} @ref changelog-\1-\2 \"since v\1.\2\"" \ - "m_deprecated_since{2}=@since deprecated in v\1.\2 @deprecated" \ No newline at end of file + "m_deprecated_since{3}=@since deprecated in v\1.\2.\3 @deprecated" + +HAVE_DOT = NO +DOT_FONTNAME = Source Sans Pro +DOT_FONTSIZE = 16 diff --git a/docs/mcss-conf.py b/docs/mcss-conf.py index df2343a..d5b3875 100644 --- a/docs/mcss-conf.py +++ b/docs/mcss-conf.py @@ -1,38 +1,39 @@ DOXYFILE = "mcss-Doxyfile" THEME_COLOR = "#cb4b16" -# FAVICON = "https://3qzcxr28gq9vutx8scdn91zq-wpengine.netdna-ssl.com/wp-content/uploads/2016/05/cropped-EnviroDIY_LogoMaster_TrueSquare_V5_TwoTree_Trans_notext-192x192.png" -FAVICON = "SDI-12Text-Cropped.png" +FAVICON = "enviroDIY_Favicon.png" LINKS_NAVBAR1 = [ ( "Functions", "class_s_d_i12", [ - ('Constructor, Destructor, Begins, and Setters', ), - ('Waking Up and Talking To Sensors', ), - ('Reading from the SDI-12 Buffer', ), - ('Data Line States', ), - ('Using more than one SDI-12 Object', ), - ('Interrupt Service Routine', ), + ( + 'Constructor, Destructor, Begins, and Setters', + ), + ( + 'Waking Up and Talking To Sensors', + ), + ( + 'Reading from the SDI-12 Buffer', + ), + ('Data Line States',), + ( + 'Using more than one SDI-12 Object', + ), + ( + 'Interrupt Service Routine', + ), ], ), ( "Examples", "examples_page", - [ - ('Getting Sensor Information', ), - ('Address Change', ), - ('Checking all Addresses', ), - ('Logging Data', ), - ('Parsing Data', ), - ('Simple Data Request', ), - ('Terminal Emulator 1', ), - ('Slave Implementation',), - ('Terminal Emulator 2', ), - ('External Interrupts', ), - ('Concurrent Measurements', ), - ], + [], + ), + ( + "Classes", + "annotated", + [], ), - ("Classes", "annotated", [],), # ("Files", "files", []), ( "Notes", @@ -52,3 +53,12 @@ STYLESHEETS = [ "css/m-EnviroDIY+documentation.compiled.css", ] +EXTRA_FILES = [ + "gp-desktop-logo.png", + "gp-mobile-logo.png", + "gp-scrolling-logo.png", + "clipboard.js", +] +DESKTOP_LOGO = "gp-desktop-logo.png" +MOBILE_LOGO = "gp-mobile-logo.png" +SCROLLING_LOGO = "gp-scrolling-logo.png" diff --git a/docs/mermaidDiagrams.md b/docs/mermaidDiagrams.md new file mode 100644 index 0000000..3650ee8 --- /dev/null +++ b/docs/mermaidDiagrams.md @@ -0,0 +1,453 @@ + + +```mermaid +block-beta +columns 5 + rxValue + block:rxValueBits:3 + rxValue7["0"] rxValue6["0"] rxValue5["0"] rxValue4["0"] rxValue3["0"] rxValue2["0"] rxValue1["0"] rxValue0["1"] + end + space:6 + rxMask + block:rxMaskBits:3 + rxMask7["0"] rxMask6["0"] rxMask5["0"] rxMask4["0"] rxMask3["0"] rxMask2["0"] rxMask1["0"] rxMask0["1"] + end + space:1 + rxState + block:rxStateBits:3 + rxState7["0"] rxState6["0"] rxState5["0"] rxState4["0"] rxState3["0"] rxState2["0"] rxState1["0"] rxState0["0"] + end + space:1 + rxMask0-- "bit-wise or (|=) puts the one \nfrom the rxMask into the rxValue" -->rxValue0 + +classDef labels stroke:None,fill:None +class rxValue,rxMask,rxState labels + +classDef bits stroke-width:0.5,fill:None +class rxValue7,rxValue6,rxValue5,rxValue4,rxValue3,rxValue2,rxValue1,rxValue0 bits +class rxMask7,rxMask6,rxMask5,rxMask4,rxMask3,rxMask2,rxMask1,rxMask0 bits +class rxState7,rxState6,rxState5,rxState4,rxState3,rxState2,rxState1,rxState0 bits +``` + +```mermaid +block-beta + block:labels:2 + columns 1 + space + rxValue + space + rxMask + rxState + end + block:bit7 + columns 1 + bitLabel7["Bit\n7"] + rxValue7["0"] + space + rxMask7["0"] + rxState7["0"] + end + block:bit6 + columns 1 + bitLabel6["Bit\n6"] + rxValue6["0"] + space + rxMask6["0"] + rxState6["0"] + end + block:bit5 + columns 1 + bitLabel5["Bit\n5"] + rxValue5["0"] + space + rxMask5["0"] + rxState5["0"] + end + block:bit4 + columns 1 + bitLabel4["Bit\n4"] + rxValue4["0"] + space + rxMask4["0"] + rxState4["0"] + end + block:bit3 + columns 1 + bitLabel3["Bit\n3"] + rxValue3["0"] + space + rxMask3["0"] + rxState3["0"] + end + block:bit2 + columns 1 + bitLabel2["Bit\n2"] + rxValue2["0"] + space + rxMask2["0"] + rxState2["0"] + end + block:bit1 + columns 1 + bitLabel1["Bit\n1"] + rxValue1["0"] + space + rxMask1["0"] + rxState1["0"] + end + block:bit0 + columns 1 + bitLabel0["Bit\n0"] + rxValue0["0"] + space + rxMask0["1"] + rxState0["0"] + end + rxMask0-- "bit-wise or (|=) puts the one \nfrom the rxMask into the rxValue" -->rxValue0 + +classDef outerLabels stroke:None,fill:None +class labels,actionTop,actionBottom outerLabels +classDef innerLabels stroke:None,fill:None +class rxValue,rxMask,rxState innerLabels +classDef innerActions stroke:None,fill:None +class rxValueActionTop,rxValueActionBottom,rxMaskActionTop,rxMaskActionBottom,rxStateActionTop,rxStateActionBottom innerActions + +classDef bitLabels stroke-width:0.5,fill:None +class bitLabel7,bitLabel6,bitLabel5,bitLabel4,bitLabel3,bitLabel2,bitLabel1,bitLabel0 bitLabels + +classDef bits stroke-width:0.5,fill:None +class rxValue7,rxValue6,rxValue5,rxValue4,rxValue3,rxValue2,rxValue1,rxValue0 bits +class rxMask7,rxMask6,rxMask5,rxMask4,rxMask3,rxMask2,rxMask1,rxMask0 bits +class rxState7,rxState6,rxState5,rxState4,rxState3,rxState2,rxState1,rxState0 bits +``` + + + +```mermaid +block-beta +columns 5 + rxValue + block:rxValueBits:3 + rxValue7["0"] rxValue6["0"] rxValue5["0"] rxValue4["0"] rxValue3["0"] rxValue2["0"] rxValue1["0"] rxValue0["1"] + end + space:6 + rxMask + block:rxMaskBits:3 + rxMask7["0"] rxMask6["0"] rxMask5["0"] rxMask4["0"] rxMask3["0"] rxMask2["0"] rxMask1["0"] rxMask0["1"] + end + space:1 + rxState + block:rxStateBits:3 + rxState7["0"] rxState6["0"] rxState5["0"] rxState4["0"] rxState3["0"] rxState2["0"] rxState1["0"] rxState0["0"] + end + space:1 + rxMask0-- "nothing happens" --xrxValue0 + +classDef labels stroke:None,fill:None +class rxValue,rxMask,rxState labels + +classDef bits stroke-width:0.5,fill:None +class rxValue7,rxValue6,rxValue5,rxValue4,rxValue3,rxValue2,rxValue1,rxValue0 bits +class rxMask7,rxMask6,rxMask5,rxMask4,rxMask3,rxMask2,rxMask1,rxMask0 bits +class rxState7,rxState6,rxState5,rxState4,rxState3,rxState2,rxState1,rxState0 bits +``` + +```mermaid +block-beta + block:labels:2 + columns 1 + space + rxValue + space + rxMask + rxState + end + block:bit7 + columns 1 + bitLabel7["Bit\n7"] + rxValue7["0"] + space + rxMask7["0"] + rxState7["0"] + end + block:bit6 + columns 1 + bitLabel6["Bit\n6"] + rxValue6["0"] + space + rxMask6["0"] + rxState6["0"] + end + block:bit5 + columns 1 + bitLabel5["Bit\n5"] + rxValue5["0"] + space + rxMask5["0"] + rxState5["0"] + end + block:bit4 + columns 1 + bitLabel4["Bit\n4"] + rxValue4["0"] + space + rxMask4["0"] + rxState4["0"] + end + block:bit3 + columns 1 + bitLabel3["Bit\n3"] + rxValue3["0"] + space + rxMask3["0"] + rxState3["0"] + end + block:bit2 + columns 1 + bitLabel2["Bit\n2"] + rxValue2["0"] + space + rxMask2["0"] + rxState2["0"] + end + block:bit1 + columns 1 + bitLabel1["Bit\n1"] + rxValue1["0"] + space + rxMask1["0"] + rxState1["0"] + end + block:bit0 + columns 1 + bitLabel0["Bit\n0"] + rxValue0["0"] + space + rxMask0["1"] + rxState0["0"] + end + rxMask0-- "nothing happens" --xrxValue0 + +classDef outerLabels stroke:None,fill:None +class labels,actionTop,actionBottom outerLabels +classDef innerLabels stroke:None,fill:None +class rxValue,rxMask,rxState innerLabels +classDef innerActions stroke:None,fill:None +class rxValueActionTop,rxValueActionBottom,rxMaskActionTop,rxMaskActionBottom,rxStateActionTop,rxStateActionBottom innerActions + +classDef bitLabels stroke-width:0.5,fill:None +class bitLabel7,bitLabel6,bitLabel5,bitLabel4,bitLabel3,bitLabel2,bitLabel1,bitLabel0 bitLabels + +classDef bits stroke-width:0.5,fill:None +class rxValue7,rxValue6,rxValue5,rxValue4,rxValue3,rxValue2,rxValue1,rxValue0 bits +class rxMask7,rxMask6,rxMask5,rxMask4,rxMask3,rxMask2,rxMask1,rxMask0 bits +class rxState7,rxState6,rxState5,rxState4,rxState3,rxState2,rxState1,rxState0 bits +``` + + +```mermaid +block-beta + block:labels:2 + columns 1 + space + rxValue + space + rxMask + rxState + end + block:actionTop:2 + columns 1 + space + rxValueActionTop["falls off the top"] + space + rxMaskActionTop["falls off the top"] + rxStateActionTop["falls off the top"] + end + space + block:bit7 + columns 1 + bitLabel7["Bit\n7"] + rxValue7["0"] + space + rxMask7["0"] + rxState7["0"] + end + block:bit6 + columns 1 + bitLabel6["Bit\n6"] + rxValue6["0"] + space + rxMask6["0"] + rxState6["0"] + end + block:bit5 + columns 1 + bitLabel5["Bit\n5"] + rxValue5["0"] + space + rxMask5["0"] + rxState5["0"] + end + block:bit4 + columns 1 + bitLabel4["Bit\n4"] + rxValue4["0"] + space + rxMask4["0"] + rxState4["0"] + end + block:bit3 + columns 1 + bitLabel3["Bit\n3"] + rxValue3["0"] + space + rxMask3["0"] + rxState3["0"] + end + block:bit2 + columns 1 + bitLabel2["Bit\n2"] + rxValue2["0"] + space + rxMask2["0"] + rxState2["0"] + end + block:bit1 + columns 1 + bitLabel1["Bit\n1"] + rxValue1["?"] + space + rxMask1["1"] + rxState1["0"] + end + block:bit0 + columns 1 + bitLabel0["Bit\n0"] + rxValue0["0"] + space + rxMask0["0"] + rxState0["1"] + end + space + block:actionBottom:2 + columns 1 + space + rxValueActionBottom["add a zero"] + space + rxMaskActionBottom["add a zero"] + rxStateActionBottom["add a one"] + end + rxValue7-->rxValueActionTop + rxMask7-->rxMaskActionTop + rxState7-->rxStateActionTop + rxValueActionBottom-->rxValue0 + rxMaskActionBottom-->rxMask0 + rxStateActionBottom-->rxState0 + +classDef outerLabels stroke:None,fill:None +class labels,actionTop,actionBottom outerLabels +classDef innerLabels stroke:None,fill:None +class rxValue,rxMask,rxState innerLabels +classDef innerActions stroke:None,fill:None +class rxValueActionTop,rxValueActionBottom,rxMaskActionTop,rxMaskActionBottom,rxStateActionTop,rxStateActionBottom innerActions + +classDef bitLabels stroke-width:0.5,fill:None +class bitLabel7,bitLabel6,bitLabel5,bitLabel4,bitLabel3,bitLabel2,bitLabel1,bitLabel0 bitLabels + +classDef bits stroke-width:0.5,fill:None +class rxValue7,rxValue6,rxValue5,rxValue4,rxValue3,rxValue2,rxValue1,rxValue0 bits +class rxMask7,rxMask6,rxMask5,rxMask4,rxMask3,rxMask2,rxMask1,rxMask0 bits +class rxState7,rxState6,rxState5,rxState4,rxState3,rxState2,rxState1,rxState0 bits +``` + + +```mermaid +block-beta + block:labels:2 + columns 1 + space + rxValue + space + rxMask + rxState + end + block:bit7 + columns 1 + bitLabel7["Bit\n7"] + rxValue7["?"] + space + rxMask7["1"] + rxState7["1"] + end + block:bit6 + columns 1 + bitLabel6["Bit\n6"] + rxValue6["?"] + space + rxMask6["0"] + rxState6["1"] + end + block:bit5 + columns 1 + bitLabel5["Bit\n5"] + rxValue5["?"] + space + rxMask5["0"] + rxState5["1"] + end + block:bit4 + columns 1 + bitLabel4["Bit\n4"] + rxValue4["?"] + space + rxMask4["0"] + rxState4["1"] + end + block:bit3 + columns 1 + bitLabel3["Bit\n3"] + rxValue3["?"] + space + rxMask3["0"] + rxState3["1"] + end + block:bit2 + columns 1 + bitLabel2["Bit\n2"] + rxValue2["?"] + space + rxMask2["0"] + rxState2["1"] + end + block:bit1 + columns 1 + bitLabel1["Bit\n1"] + rxValue1["?"] + space + rxMask1["0"] + rxState1["1"] + end + block:bit0 + columns 1 + bitLabel0["Bit\n0"] + rxValue0["?"] + space + rxMask0["0"] + rxState0["1"] + end + +classDef outerLabels stroke:None,fill:None +class labels,actionTop,actionBottom outerLabels +classDef innerLabels stroke:None,fill:None +class rxValue,rxMask,rxState innerLabels +classDef innerActions stroke:None,fill:None +class rxValueActionTop,rxValueActionBottom,rxMaskActionTop,rxMaskActionBottom,rxStateActionTop,rxStateActionBottom innerActions + +classDef bitLabels stroke-width:0.5,fill:None +class bitLabel7,bitLabel6,bitLabel5,bitLabel4,bitLabel3,bitLabel2,bitLabel1,bitLabel0 bitLabels + +classDef bits stroke-width:0.5,fill:None +class rxValue7,rxValue6,rxValue5,rxValue4,rxValue3,rxValue2,rxValue1,rxValue0 bits +class rxMask7,rxMask6,rxMask5,rxMask4,rxMask3,rxMask2,rxMask1,rxMask0 bits +class rxState7,rxState6,rxState5,rxState4,rxState3,rxState2,rxState1,rxState0 bits +``` diff --git a/examples/ReadMe.md b/examples/ReadMe.md index b6acefc..c0c6907 100644 --- a/examples/ReadMe.md +++ b/examples/ReadMe.md @@ -1,75 +1,38 @@ -[//]: # ( @page examples_page Examples ) -# Examples using the SDI-12 Library +# Examples using the SDI-12 Library -[//]: # ( @brief Examples using the SDI-12 Library ) - -[//]: # ( Start GitHub Only ) -- [Example A](@ref a_wild_card.ino): +- Example A - Gets sensor information from a single attached sensor and prints it to the serial port - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/a_wild_card) -- [Example B](@ref b_address_change.ino): +- Example B - Allows you to change the address of your SDI-12 Sensor - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/b_address_change) -- [Example C](@ref c_check_all_addresses.ino): +- Example C - Checks all addresses for active sensors, and prints their status to the serial port - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/c_check_all_addresses) -- [Example D](@ref d_simple_logger.ino): +- Example D - Checks all addresses for active sensors, and logs data for each sensor every minute - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/d_simple_logger) -- [Example E](@ref e_simple_parsing.ino): - - Demonstrates the ability to parse integers and floats from the buffer. - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/e_simple_parsing) -- [Example F](@ref f_basic_data_request.ino): +- Example E + - Checks all addresses for active sensors, and requests continuous data output from all of the sensors + - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/e_continuous_measurement) +- Example F - Issues a data request to a single specified sensor - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/f_basic_data_request) -- [Example G](@ref g_terminal_window.ino): +- Example G - Demonstrates using the Arduino as a command terminal for SDI-12 sensors. - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/g_terminal_window) -- [Example H](@ref h_SDI-12_slave_implementation.ino): +- Example H - Demonstrates using SDI-12 in slave mode - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/h_SDI-12_slave_implementation) -- [Example I](@ref i_SDI-12_interface.ino): +- Example I - Shows code for an Arduino-based USB dongle to translate between SDI-12 and a PC - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/i_SDI-12_interface) -- [Example J](@ref j_external_pcint_library.ino): +- Example J - Shows how to use an external PCInt library to call the interrupts for this library. - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/j_external_pcint_library) -- [Example K](@ref k_concurrent_logger.ino): +- Example K - Shows how to request concurrent measurements - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/k_concurrent_logger) - -[//]: # ( End GitHub Only ) - -- [Example A](@ref a_wild_card.ino): - - Gets sensor information from a single attached sensor and prints it to the serial port - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/a_wild_card) -- [Example B](@ref b_address_change.ino): - - Allows you to change the address of your SDI-12 Sensor - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/b_address_change) -- [Example C](@ref c_check_all_addresses.ino): - - Checks all addresses for active sensors, and prints their status to the serial port - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/c_check_all_addresses) -- [Example D](@ref d_simple_logger.ino): - - Checks all addresses for active sensors, and logs data for each sensor every minute - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/d_simple_logger) -- [Example E](@ref e_simple_parsing.ino): - - Demonstrates the ability to parse integers and floats from the buffer. - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/e_simple_parsing) -- [Example F](@ref f_basic_data_request.ino): - - Issues a data request to a single specified sensor - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/f_basic_data_request) -- [Example G](@ref g_terminal_window.ino): - - Demonstrates using the Arduino as a command terminal for SDI-12 sensors. - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/g_terminal_window) -- [Example H](@ref h_SDI-12_slave_implementation.ino): - - Demonstrates using SDI-12 in slave mode - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/h_SDI-12_slave_implementation) -- [Example I](@ref i_SDI-12_interface.ino): - - Shows code for an Arduino-based USB dongle to translate between SDI-12 and a PC - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/i_SDI-12_interface) -- [Example J](@ref j_external_pcint_library.ino): - - Shows how to use an external PCInt library to call the interrupts for this library. - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/j_external_pcint_library) -- [Example K](@ref k_concurrent_logger.ino): - - Shows how to request concurrent measurements - - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/k_concurrent_logger) \ No newline at end of file +- Example L + - Shows how to verify the CRC of received data + - [GitHub](https://github.com/EnviroDIY/Arduino-SDI-12/tree/master/examples/l_verify_crc) diff --git a/examples/a_wild_card/ReadMe.md b/examples/a_wild_card/ReadMe.md index f8f87ca..87ca613 100644 --- a/examples/a_wild_card/ReadMe.md +++ b/examples/a_wild_card/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_a_page Example A: Using the Wildcard - Getting Single Sensor Information ) -# Example A: Using the Wildcard - Getting Single Sensor Information +# Example A: Using the Wildcard - Getting Single Sensor Information This is a simple demonstration of the SDI-12 library for Arduino. It requests information about a single attached sensor, including its address and manufacturer info, and prints it to the serial port @@ -9,3 +8,5 @@ It requests information about a single attached sensor, including its address an [//]: # ( @include{lineno} a_wild_card/platformio.ini ) [//]: # ( @section a_wild_card_code The Complete Example ) + +[//]: # ( @include{lineno} a_wild_card/a_wild_card.ino ) diff --git a/examples/a_wild_card/a_wild_card.ino b/examples/a_wild_card/a_wild_card.ino index 8add10a..f391f74 100644 --- a/examples/a_wild_card/a_wild_card.ino +++ b/examples/a_wild_card/a_wild_card.ino @@ -1,9 +1,7 @@ - /** - * @file a_wild_card.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} a_wild_card.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Kevin M.Smith * @date August 2013 * @@ -17,12 +15,12 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); /** '?' is a wildcard character which asks any and all sensors to respond @@ -32,7 +30,7 @@ SDI12 mySDI12(DATA_PIN); String myCommand = "?I!"; void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -41,10 +39,10 @@ void setup() { delay(500); // allow things to settle // Power the sensors; - if (POWER_PIN > 0) { + if (powerPin >= 0) { Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); delay(200); } } diff --git a/examples/b_address_change/ReadMe.md b/examples/b_address_change/ReadMe.md index f220b9b..dbc5bb1 100644 --- a/examples/b_address_change/ReadMe.md +++ b/examples/b_address_change/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_b_page Example B: Changing the Address of your SDI-12 Sensor ) -# Example B: Changing the Address of your SDI-12 Sensor +# Example B: Changing the Address of your SDI-12 Sensor 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. @@ -7,9 +6,9 @@ First, physically connect your SDI-12 sensor to your device. Some helpful hits Once your sensor is physically connected to your board, download this library and open this sketch. -Scroll to line 54 of the sketch (`#define DATA_PIN 7`). Change the `7` to the pin number that your sensor is attached to. +Scroll to line 54 of the sketch (`int8_t dataPin = 7;`). Change the `7` to the pin number that your sensor is attached to. -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. +Set the pin to provide power to your sensor in line 55 (`int8_t powerPin = 22;`). If your sensor is continuously powered, set the power pin to -1. 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. @@ -22,3 +21,5 @@ If you are using a Meter Group Hydros 21 CTD sensor, change the channel to 1 in [//]: # ( @include{lineno} b_address_change/platformio.ini ) [//]: # ( @section b_address_change_code The Complete Example ) + +[//]: # ( @include{lineno} b_address_change/b_address_change.ino ) diff --git a/examples/b_address_change/b_address_change.ino b/examples/b_address_change/b_address_change.ino index 2dd7096..3d0e641 100644 --- a/examples/b_address_change/b_address_change.ino +++ b/examples/b_address_change/b_address_change.ino @@ -1,8 +1,7 @@ /** - * @file b_address_change.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} b_address_change.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Kevin M.Smith * @date August 2013 * @@ -14,16 +13,47 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t serialBaud = 57600; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t wake_delay = 0; /*!< Extra time needed for the sensor to wake (0-100ms) */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); String myCommand = ""; // empty to start char oldAddress = '!'; // invalid address as placeholder +/** + * @brief gets identification information from a sensor, and prints it to the serial + * port + * + * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. + */ +void printInfo(char i) { + String command = ""; + command += (char)i; + command += "I!"; + mySDI12.sendCommand(command, wake_delay); + delay(100); + + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + // allccccccccmmmmmmvvvxxx...xx + Serial.print(sdiResponse.substring(0, 1)); // address + Serial.print(", "); + Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number + Serial.print(", "); + Serial.print(sdiResponse.substring(3, 11)); // vendor id + Serial.print(", "); + Serial.print(sdiResponse.substring(11, 17)); // sensor model + Serial.print(", "); + Serial.print(sdiResponse.substring(17, 20)); // sensor version + Serial.print(", "); + Serial.print(sdiResponse.substring(20)); // sensor id + Serial.print(", "); +} + // this checks for activity at a particular address // expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' boolean checkActive(byte i) { // this checks for activity at a particular address @@ -35,8 +65,8 @@ boolean checkActive(byte i) { // this checks for activity at a particular addre myCommand += "!"; for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts - mySDI12.sendCommand(myCommand); - delay(30); + mySDI12.sendCommand(myCommand, wake_delay); + delay(100); if (mySDI12.available()) { // If we here anything, assume we have an active sensor Serial.println("Occupied"); mySDI12.clearBuffer(); @@ -50,7 +80,7 @@ boolean checkActive(byte i) { // this checks for activity at a particular addre } void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -58,12 +88,15 @@ void setup() { mySDI12.begin(); delay(500); // allow things to settle + Serial.println("Timeout value: "); + Serial.println(mySDI12.TIMEOUT); + // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); - delay(200); + if (powerPin >= 0) { + Serial.println("Powering up sensors, wait 30s..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(30000L); } } @@ -75,6 +108,7 @@ void loop() { if (checkActive(i)) { found = true; oldAddress = i; + printInfo(i); } } @@ -83,6 +117,7 @@ void loop() { if (checkActive(i)) { found = true; oldAddress = i; + printInfo(i); } } @@ -91,6 +126,7 @@ void loop() { if (checkActive(i)) { found = true; oldAddress = i; + printInfo(i); } } @@ -98,6 +134,8 @@ void loop() { Serial.println( "No sensor detected. Check physical connections."); // couldn't find a sensor. // check connections.. + while (1) // die + ; } else { Serial.print("Sensor active at address "); // found a sensor! Serial.print(oldAddress); diff --git a/examples/c_check_all_addresses/ReadMe.md b/examples/c_check_all_addresses/ReadMe.md index ed0247a..d59be48 100644 --- a/examples/c_check_all_addresses/ReadMe.md +++ b/examples/c_check_all_addresses/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_c_page Example C: Check all Addresses for Active Sensors and Print Status ) -# Example C: Check all Addresses for Active Sensors and Print Status +# Example C: Check all Addresses for Active Sensors and Print Status This is a simple demonstration of the SDI-12 library for Arduino. @@ -14,3 +13,5 @@ To address a sensor, please see Example B: b_address_change.ino [//]: # ( @include{lineno} c_check_all_addresses/platformio.ini ) [//]: # ( @section c_check_all_addresses_code The Complete Example ) + +[//]: # ( @include{lineno} c_check_all_addresses/c_check_all_addresses.ino ) diff --git a/examples/c_check_all_addresses/c_check_all_addresses.ino b/examples/c_check_all_addresses/c_check_all_addresses.ino index a5c0bde..eae5755 100644 --- a/examples/c_check_all_addresses/c_check_all_addresses.ino +++ b/examples/c_check_all_addresses/c_check_all_addresses.ino @@ -1,8 +1,7 @@ /** - * @file c_check_all_addresses.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} c_check_all_addresses.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Kevin M.Smith * @date August 2013 * @@ -22,10 +21,10 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ -#define FirstPin 5 /*! change to lowest pin number on your board */ -#define LastPin 24 /*! change to highest pin number on your board */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +#define FirstPin 5 /*! change to lowest pin number on your board */ +#define LastPin 24 /*! change to highest pin number on your board */ /** * @brief gets identification information from a sensor, and prints it to the serial @@ -85,19 +84,19 @@ void scanAddressSpace(SDI12 sdi) { } void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); Serial.println("//\n// Start Search for SDI-12 Devices \n// -----------------------"); // Power the sensors; - if (POWER_PIN > 0) { + if (powerPin >= 0) { Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); delay(200); } for (uint8_t pin = FirstPin; pin <= LastPin; pin++) { - if (pin != POWER_PIN) { + if (pin != powerPin) { pinMode(pin, INPUT); SDI12 mySDI12(pin); mySDI12.begin(); @@ -112,7 +111,7 @@ void setup() { Serial.println("\n//\n// End Search for SDI-12 Devices \n// ---------------------"); // Cut power - digitalWrite(POWER_PIN, LOW); + digitalWrite(powerPin, LOW); } void loop() {} diff --git a/examples/d_simple_logger/ReadMe.md b/examples/d_simple_logger/ReadMe.md index 5606c43..bb458ec 100644 --- a/examples/d_simple_logger/ReadMe.md +++ b/examples/d_simple_logger/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_d_page Example D: Check all Addresses for Active Sensors and Log Data ) -## Example D: Check all Addresses for Active Sensors and Log Data +## Example D: Check all Addresses for Active Sensors and Log Data This is a simple demonstration of the SDI-12 library for Arduino. @@ -16,3 +15,5 @@ To address a sensor, please see Example B: b_address_change.ino [//]: # ( @include{lineno} d_simple_logger/platformio.ini ) [//]: # ( @section d_simple_logger_code The Complete Example ) + +[//]: # ( @include{lineno} d_simple_logger/d_simple_logger.ino ) diff --git a/examples/d_simple_logger/d_simple_logger.ino b/examples/d_simple_logger/d_simple_logger.ino index 93b2797..8393fb2 100644 --- a/examples/d_simple_logger/d_simple_logger.ino +++ b/examples/d_simple_logger/d_simple_logger.ino @@ -1,8 +1,7 @@ /** - * @file d_simple_logger.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} d_simple_logger.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Kevin M.Smith * @date August 2013 * @@ -26,13 +25,15 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ -#define WAKE_DELAY 0 /*!< Extra time needed for the sensor to wake (0-100ms) */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t wake_delay = 0; /*!< Extra time needed for the sensor to wake (0-100ms) */ +int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */ +int8_t lastAddress = 62; /* The last address in the address space to check (62='z') */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); // keeps track of active addresses bool isActive[64] = { @@ -80,7 +81,7 @@ void printInfo(char i) { String command = ""; command += (char)i; command += "I!"; - mySDI12.sendCommand(command, WAKE_DELAY); + mySDI12.sendCommand(command, wake_delay); delay(100); String sdiResponse = mySDI12.readStringUntil('\n'); @@ -111,7 +112,7 @@ bool getResults(char i, int resultsExpected) { command += "D"; command += cmd_number; command += "!"; // SDI-12 command to get data [address][D][dataOption][!] - mySDI12.sendCommand(command, WAKE_DELAY); + mySDI12.sendCommand(command, wake_delay); uint32_t start = millis(); while (mySDI12.available() < 3 && (millis() - start) < 1500) {} @@ -148,7 +149,7 @@ bool takeMeasurement(char i, String meas_type = "") { command += "M"; command += meas_type; command += "!"; // SDI-12 measurement command format [address]['M'][!] - mySDI12.sendCommand(command, WAKE_DELAY); + mySDI12.sendCommand(command, wake_delay); delay(100); Serial.print(command); @@ -201,7 +202,7 @@ boolean checkActive(char i) { myCommand += "!"; for (int j = 0; j < 3; j++) { // goes through three rapid contact attempts - mySDI12.sendCommand(myCommand, WAKE_DELAY); + mySDI12.sendCommand(myCommand, wake_delay); delay(100); if (mySDI12.available()) { // If we here anything, assume we have an active sensor mySDI12.clearBuffer(); @@ -213,7 +214,7 @@ boolean checkActive(char i) { } void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -225,10 +226,10 @@ void setup() { Serial.println(mySDI12.TIMEOUT); // Power the sensors; - if (POWER_PIN > 0) { + if (powerPin >= 0) { Serial.println("Powering up sensors, wait..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); delay(15000L); // delay(200); } @@ -238,7 +239,7 @@ void setup() { Serial.println("Sensor Address, Protocol Version, Sensor Vendor, Sensor Model, " "Sensor Version, Sensor ID"); - for (byte i = 0; i < 62; i++) { + for (byte i = firstAddress; i < lastAddress; i++) { char addr = decToChar(i); if (checkActive(addr)) { numSensors++; @@ -268,7 +269,7 @@ void loop() { String commands[] = {"", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; for (uint8_t a = 0; a < 1; a++) { // measure one at a time - for (byte i = 0; i < 62; i++) { + for (byte i = firstAddress; i < lastAddress; i++) { char addr = decToChar(i); if (isActive[i]) { // Serial.print(millis() / 1000); diff --git a/examples/e_continuous_measurement/ReadMe.md b/examples/e_continuous_measurement/ReadMe.md index 43653d2..976b2c7 100644 --- a/examples/e_continuous_measurement/ReadMe.md +++ b/examples/e_continuous_measurement/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_e_page Example E: Check all Addresses for Active Sensors and Start Continuous Measurements ) -## Example E: Check all Addresses for Active Sensors and Start Continuous Measurements +## Example E: Check all Addresses for Active Sensors and Start Continuous Measurements This is a simple demonstration of the SDI-12 library for Arduino. @@ -16,3 +15,5 @@ To address a sensor, please see Example B: b_address_change.ino [//]: # ( @include{lineno} e_continuous_measurement/platformio.ini ) [//]: # ( @section e_continuous_measurement_code The Complete Example ) + +[//]: # ( @include{lineno} e_continuous_measurement/e_continuous_measurement.ino ) diff --git a/examples/e_continuous_measurement/e_continuous_measurement.ino b/examples/e_continuous_measurement/e_continuous_measurement.ino index c513220..f38bc29 100644 --- a/examples/e_continuous_measurement/e_continuous_measurement.ino +++ b/examples/e_continuous_measurement/e_continuous_measurement.ino @@ -1,8 +1,7 @@ /** - * @file d_simple_logger.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} e_continuous_measurement.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Kevin M.Smith * @date August 2013 * @@ -16,12 +15,14 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */ +int8_t lastAddress = 62; /* The last address in the address space to check (62='z') */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); // keeps track of active addresses bool isActive[64] = { @@ -151,7 +152,7 @@ boolean checkActive(char i) { } void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -163,11 +164,11 @@ void setup() { Serial.println(mySDI12.TIMEOUT); // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); - delay(200); + if (powerPin >= 0) { + Serial.println("Powering up sensors, wait..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(10000L); } // Quickly Scan the Address Space @@ -175,7 +176,7 @@ void setup() { Serial.println("Sensor Address, Protocol Version, Sensor Vendor, Sensor Model, " "Sensor Version, Sensor ID"); - for (byte i = 0; i < 62; i++) { + for (byte i = firstAddress; i < lastAddress; i++) { char addr = decToChar(i); if (checkActive(addr)) { numSensors++; @@ -203,7 +204,7 @@ void setup() { void loop() { // measure one at a time - for (byte i = 0; i < 62; i++) { + for (byte i = firstAddress; i < lastAddress; i++) { char addr = decToChar(i); if (isActive[i]) { // Serial.print(millis() / 1000); diff --git a/examples/e_continuous_measurement/platformio.ini b/examples/e_continuous_measurement/platformio.ini new file mode 100644 index 0000000..6060cd1 --- /dev/null +++ b/examples/e_continuous_measurement/platformio.ini @@ -0,0 +1,15 @@ +; PlatformIO Project Configuration File + +[platformio] +description = SDI-12 Library Example D: Getting Data from All Attached Sensors +src_dir = .piolibdeps/Arduino-SDI-12_ID1486/examples/d_simple_logger + +[env:mayfly] +monitor_speed = 115200 +board = mayfly +platform = atmelavr +framework = arduino +lib_ldf_mode = deep+ +lib_ignore = RTCZero +lib_deps = + SDI-12 diff --git a/examples/example_dependencies.json b/examples/example_dependencies.json new file mode 100644 index 0000000..3bfcbbe --- /dev/null +++ b/examples/example_dependencies.json @@ -0,0 +1,10 @@ +{ + "action_cache_version": 1, + "dependencies": [ + { + "name": "EnableInterrupt", + "owner": "greygnome", + "version": "~1.1.0" + } + ] +} diff --git a/examples/f_basic_data_request/ReadMe.md b/examples/f_basic_data_request/ReadMe.md index b8572a1..2304116 100644 --- a/examples/f_basic_data_request/ReadMe.md +++ b/examples/f_basic_data_request/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_f_page Example F: Basic Data Request to a Single Sensor ) -# Example F: Basic Data Request to a Single Sensor +# Example F: Basic Data Request to a Single Sensor This is a simple demonstration of the SDI-12 library for Arduino. @@ -10,3 +9,5 @@ This is a very basic (stripped down) example where the user initiates a measurem [//]: # ( @include{lineno} f_basic_data_request/platformio.ini ) [//]: # ( @section f_basic_data_request_code The Complete Example ) + +[//]: # ( @include{lineno} f_basic_data_request/f_basic_data_request.ino ) diff --git a/examples/f_basic_data_request/f_basic_data_request.ino b/examples/f_basic_data_request/f_basic_data_request.ino index 8f66b65..129e3a4 100644 --- a/examples/f_basic_data_request/f_basic_data_request.ino +++ b/examples/f_basic_data_request/f_basic_data_request.ino @@ -1,9 +1,8 @@ /** - * @file f_basic_data_request.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. - * @author Ruben Kertesz or @rinnamon on twitter + * @example{lineno} f_basic_data_request.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. + * @author Ruben Kertesz or \@rinnamon on twitter * @date 2/10/2016 * * @brief Example F: Basic Data Request to a Single Sensor @@ -17,19 +16,20 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ -#define SENSOR_ADDRESS 1 +/* connection information */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +char sensorAddress = '1'; /*!< The address of the SDI-12 sensor */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); String sdiResponse = ""; String myCommand = ""; void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -38,10 +38,10 @@ void setup() { delay(500); // allow things to settle // Power the sensors; - if (POWER_PIN > 0) { + if (powerPin >= 0) { Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); delay(200); } } @@ -50,12 +50,11 @@ void loop() { do { // wait for a response from the serial terminal to do anything delay(30); } while (!Serial.available()); - char nogo = - Serial.read(); // simply hit enter in the terminal window or press send and the - // characters get discarded but now the rest of the loop continues + Serial.read(); // simply hit enter in the terminal window or press send and the + // characters get discarded but now the rest of the loop continues // first command to take a measurement - myCommand = String(SENSOR_ADDRESS) + "M!"; + myCommand = String(sensorAddress) + "M!"; Serial.println(myCommand); // echo command to terminal mySDI12.sendCommand(myCommand); @@ -78,7 +77,7 @@ void loop() { // next command to request data from last measurement - myCommand = String(SENSOR_ADDRESS) + "D0!"; + myCommand = String(sensorAddress) + "D0!"; Serial.println(myCommand); // echo command to terminal mySDI12.sendCommand(myCommand); diff --git a/examples/g_terminal_window/ReadMe.md b/examples/g_terminal_window/ReadMe.md index c222be6..40a1a52 100644 --- a/examples/g_terminal_window/ReadMe.md +++ b/examples/g_terminal_window/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_g_page Example G: Using the Arduino as a Command Terminal for SDI-12 Sensors ) -# Example G: Using the Arduino as a Command Terminal for SDI-12 Sensors +# Example G: Using the Arduino as a Command Terminal for SDI-12 Sensors This is a simple demonstration of the SDI-12 library for Arduino. @@ -10,3 +9,5 @@ It's purpose is to allow a user to interact with an SDI-12 sensor directly, issu [//]: # ( @include{lineno} g_terminal_window/platformio.ini ) [//]: # ( @section g_terminal_window_code The Complete Example ) + +[//]: # ( @include{lineno} g_terminal_window/g_terminal_window.ino ) diff --git a/examples/g_terminal_window/g_terminal_window.ino b/examples/g_terminal_window/g_terminal_window.ino index 9957e7a..fef0aec 100644 --- a/examples/g_terminal_window/g_terminal_window.ino +++ b/examples/g_terminal_window/g_terminal_window.ino @@ -1,11 +1,10 @@ /** - * @file g_terminal_window.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} g_terminal_window.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Kevin M.Smith * @date August 2013 - * @author Ruben Kertesz or @rinnamon on twitter + * @author Ruben Kertesz or \@rinnamon on twitter * @date 2016 * * @brief Example G: Using the Arduino as a Command Terminal for SDI-12 Sensors @@ -19,19 +18,19 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); char inByte = 0; String sdiResponse = ""; String myCommand = ""; void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -40,10 +39,10 @@ void setup() { delay(500); // allow things to settle // Power the sensors; - if (POWER_PIN > 0) { + if (powerPin >= 0) { Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); delay(200); } } diff --git a/examples/h_SDI-12_slave_implementation/ReadMe.md b/examples/h_SDI-12_slave_implementation/ReadMe.md index 950d64d..7a5e33a 100644 --- a/examples/h_SDI-12_slave_implementation/ReadMe.md +++ b/examples/h_SDI-12_slave_implementation/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_h_page Example H: Using SDI-12 in Slave Mode ) -# Example H: Using SDI-12 in Slave Mode +# Example H: Using SDI-12 in Slave Mode 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. diff --git a/examples/h_SDI-12_slave_implementation/h_SDI-12_slave_implementation.ino b/examples/h_SDI-12_slave_implementation/h_SDI-12_slave_implementation.ino index 33f10b3..05a3042 100644 --- a/examples/h_SDI-12_slave_implementation/h_SDI-12_slave_implementation.ino +++ b/examples/h_SDI-12_slave_implementation/h_SDI-12_slave_implementation.ino @@ -1,8 +1,7 @@ /** - * @file h_SDI-12_slave_implementation.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} h_SDI-12_slave_implementation.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @date 2016 * @author D. Wasielewski * @@ -30,19 +29,18 @@ #include -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ - -char sensorAddress = '5'; -int state = 0; +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +char sensorAddress = '5'; /*!< The address of the SDI-12 sensor */ +int state = 0; #define WAIT 0 #define INITIATE_CONCURRENT 1 #define INITIATE_MEASUREMENT 2 -#define PROCESS_COMMAND 3 +#define PROCESS_COMMAND 3 // Create object by which to communicate with the SDI-12 bus on SDIPIN -SDI12 slaveSDI12(DATA_PIN); +SDI12 slaveSDI12(dataPin); void pollSensor(float* measurementValues) { measurementValues[0] = 1.111111; @@ -138,8 +136,9 @@ void parseSdi12Cmd(String command, String* dValues) { } } - // Issue the response speficied in the switch-case structure above. - slaveSDI12.sendResponse(String(sensorAddress) + responseStr + "\r\n"); + // Issue the response specified in the switch-case structure above. + String fullResponse = String(sensorAddress) + responseStr + "\r\n"; + slaveSDI12.sendResponse(fullResponse); } void formatOutputSDI(float* measurementValues, String* dValues, unsigned int maxChar) { @@ -197,7 +196,7 @@ void loop() { // Character '!' indicates the end of an SDI-12 command; if the current // character is '!', stop listening and respond to the command if (charReceived == '!') { - state = PROCESS_COMMAND; + state = PROCESS_COMMAND; // Command string is completed; do something with it parseSdi12Cmd(commandReceived, dValues); // Clear command string to reset for next command @@ -221,36 +220,46 @@ void loop() { // For aM! and aC! commands, parseSdi12Cmd will modify "state" to indicate that // a measurement should be taken switch (state) { - case WAIT: break; + case WAIT: + { + break; + } case INITIATE_CONCURRENT: - // Do whatever the sensor is supposed to do here - // For this example, we will just create arbitrary "simulated" sensor data - // NOTE: Your application might have a different data type (e.g. int) and - // number of values to report! - pollSensor(measurementValues); - // Populate the "dValues" String array with the values in SDI-12 format - formatOutputSDI(measurementValues, dValues, 75); - state = WAIT; - slaveSDI12.forceListen(); // sets SDI-12 pin as input to prepare for incoming - // message AGAIN - break; + { + // Do whatever the sensor is supposed to do here + // For this example, we will just create arbitrary "simulated" sensor data + // NOTE: Your application might have a different data type (e.g. int) and + // number of values to report! + pollSensor(measurementValues); + // Populate the "dValues" String array with the values in SDI-12 format + formatOutputSDI(measurementValues, dValues, 75); + state = WAIT; + slaveSDI12.forceListen(); // sets SDI-12 pin as input to prepare for incoming + // message AGAIN + break; + } case INITIATE_MEASUREMENT: - // Do whatever the sensor is supposed to do here - // For this example, we will just create arbitrary "simulated" sensor data - // NOTE: Your application might have a different data type (e.g. int) and - // number of values to report! - pollSensor(measurementValues); - // Populate the "dValues" String array with the values in SDI-12 format - formatOutputSDI(measurementValues, dValues, 35); - // For aM!, Send "service request" (
) when data is ready - slaveSDI12.sendResponse(String(sensorAddress) + "\r\n"); - state = WAIT; - slaveSDI12.forceListen(); // sets SDI-12 pin as input to prepare for incoming - // message AGAIN - break; + { + // Do whatever the sensor is supposed to do here + // For this example, we will just create arbitrary "simulated" sensor data + // NOTE: Your application might have a different data type (e.g. int) and + // number of values to report! + pollSensor(measurementValues); + // Populate the "dValues" String array with the values in SDI-12 format + formatOutputSDI(measurementValues, dValues, 35); + // For aM!, Send "service request" (
) when data is ready + String fullResponse = String(sensorAddress) + "\r\n"; + slaveSDI12.sendResponse(fullResponse); + state = WAIT; + slaveSDI12.forceListen(); // sets SDI-12 pin as input to prepare for incoming + // message AGAIN + break; + } case PROCESS_COMMAND: - state = WAIT; - slaveSDI12.forceListen(); - break; + { + state = WAIT; + slaveSDI12.forceListen(); + break; + } } } diff --git a/examples/i_SDI-12_interface/ReadMe.md b/examples/i_SDI-12_interface/ReadMe.md index 9b7dd5a..0926120 100644 --- a/examples/i_SDI-12_interface/ReadMe.md +++ b/examples/i_SDI-12_interface/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_i_page Example I: SDI-12 PC Interface ) -# Example I: SDI-12 PC Interface +# Example I: SDI-12 PC Interface Code for an Arduino-based USB dongle translates serial comm from PC to SDI-12 (electrical and timing) 1. Allows user to communicate to SDI-12 devices from a serial terminal emulator (e.g. PuTTY). diff --git a/examples/i_SDI-12_interface/i_SDI-12_interface.ino b/examples/i_SDI-12_interface/i_SDI-12_interface.ino index 8fc6b39..559c544 100644 --- a/examples/i_SDI-12_interface/i_SDI-12_interface.ino +++ b/examples/i_SDI-12_interface/i_SDI-12_interface.ino @@ -1,8 +1,7 @@ /** - * @file h_SDI-12_slave_implementation.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} i_SDI-12_interface.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @date 2016 * @author D. Wasielewski * @@ -46,24 +45,25 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ -#define SENSOR_ADDRESS 1 +/* connection information */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +char sensorAddress = '1'; /*!< The address of the SDI-12 sensor */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; // Power the sensors; - if (POWER_PIN > 0) { + if (powerPin >= 0) { Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); delay(200); } @@ -166,7 +166,8 @@ void loop() { mySDI12.sendCommand(serialMsgStr); } else { serialMsgStr.toUpperCase(); - mySDI12.sendCommand(serialMsgStr + "!"); + String fullCommand = serialMsgStr + "!"; + mySDI12.sendCommand(fullCommand); } } // Reset String for next serial message diff --git a/examples/j_external_pcint_library/ReadMe.md b/examples/j_external_pcint_library/ReadMe.md index 9d6daf1..15625c0 100644 --- a/examples/j_external_pcint_library/ReadMe.md +++ b/examples/j_external_pcint_library/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_j_page Example J: Using External Interrupts ) -# Example J: Using External Interrupts +# Example J: Using External Interrupts 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. @@ -10,3 +9,5 @@ To use this example, you must remove the comment braces around `#define SDI12_EX [//]: # ( @include{lineno} j_external_pcint_library/platformio.ini ) [//]: # ( @section j_external_pcint_library_code The Complete Example ) + +[//]: # ( @include{lineno} j_external_pcint_library/j_external_pcint_library.ino ) diff --git a/examples/j_external_pcint_library/j_external_pcint_library.ino b/examples/j_external_pcint_library/j_external_pcint_library.ino index fd90537..68e4924 100644 --- a/examples/j_external_pcint_library/j_external_pcint_library.ino +++ b/examples/j_external_pcint_library/j_external_pcint_library.ino @@ -1,8 +1,7 @@ /** - * @file j_external_pcint_library.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} j_external_pcint_library.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Kevin M.Smith * * @brief Example J: Using External Interrupts @@ -19,12 +18,15 @@ #include #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ +/* connection information */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */ +int8_t lastAddress = 62; /* The last address in the address space to check (62='z') */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); // keeps track of active addresses bool isActive[64] = { @@ -202,7 +204,7 @@ boolean checkActive(char i) { } void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -214,23 +216,23 @@ void setup() { Serial.println(mySDI12.TIMEOUT); // Power the sensors; - if (POWER_PIN > 0) { + if (powerPin >= 0) { Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); delay(200); } // Enable interrupts for the recieve pin - pinMode(DATA_PIN, INPUT_PULLUP); - enableInterrupt(DATA_PIN, SDI12::handleInterrupt, CHANGE); + pinMode(dataPin, INPUT_PULLUP); + enableInterrupt(dataPin, SDI12::handleInterrupt, CHANGE); // Quickly Scan the Address Space Serial.println("Scanning all addresses, please wait..."); Serial.println("Protocol Version, Sensor Address, Sensor Vendor, Sensor Model, " "Sensor Version, Sensor ID"); - for (byte i = 0; i < 62; i++) { + for (byte i = firstAddress; i < lastAddress; i++) { char addr = decToChar(i); if (checkActive(addr)) { numSensors++; @@ -257,7 +259,7 @@ void setup() { void loop() { // measure one at a time - for (byte i = 0; i < 62; i++) { + for (byte i = firstAddress; i < lastAddress; i++) { char addr = decToChar(i); if (isActive[i]) { Serial.print(millis() / 1000); diff --git a/examples/k_concurrent_logger/ReadMe.md b/examples/k_concurrent_logger/ReadMe.md index 3025222..02fc3b4 100644 --- a/examples/k_concurrent_logger/ReadMe.md +++ b/examples/k_concurrent_logger/ReadMe.md @@ -1,5 +1,4 @@ -[//]: # ( @page example_k_page Example K: Concurrent Measurements ) -# Example K: Concurrent Measurements +# Example K: Concurrent Measurements This is very similar to example D - finding all attached sensors and logging data from them. 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. @@ -10,3 +9,5 @@ This can be much faster than waiting for each sensor when you have multiple sens [//]: # ( @include{lineno} k_concurrent_logger/platformio.ini ) [//]: # ( @section k_concurrent_logger_code The Complete Example ) + +[//]: # ( @include{lineno} k_concurrent_logger/k_concurrent_logger.ino ) diff --git a/examples/k_concurrent_logger/k_concurrent_logger.ino b/examples/k_concurrent_logger/k_concurrent_logger.ino index ff383f5..983ef0d 100644 --- a/examples/k_concurrent_logger/k_concurrent_logger.ino +++ b/examples/k_concurrent_logger/k_concurrent_logger.ino @@ -1,8 +1,7 @@ /** - * @file k_concurrent_logger.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. + * @example{lineno} k_concurrent_logger.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. * @author Sara Geleskie Damiano * * @brief Example K: Concurrent Measurements @@ -16,37 +15,33 @@ #include -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ +/* connection information */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +int8_t firstAddress = 0; /* The first address in the address space to check (0='0') */ +int8_t lastAddress = 62; /* The last address in the address space to check (62='z') */ /** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); +SDI12 mySDI12(dataPin); // keeps track of active addresses -bool isActive[64] = { - 0, -}; +bool isActive[64]; // keeps track of the wait time for each active addresses -uint8_t meas_time_ms[64] = { - 0, -}; +uint8_t meas_time_ms[64]; // keeps track of the time each sensor was started -uint32_t millisStarted[64] = { - 0, -}; +uint32_t millisStarted[64]; // keeps track of the time each sensor will be ready -uint32_t millisReady[64] = { - 0, -}; +uint32_t millisReady[64]; // keeps track of the number of results expected -uint8_t returnedResults[64] = { - 0, -}; +uint8_t expectedResults[64]; + +// keeps track of the number of results returned +uint8_t returnedResults[64]; uint8_t numSensors = 0; @@ -79,44 +74,13 @@ char decToChar(byte i) { return i; } -/** - * @brief gets identification information from a sensor, and prints it to the serial - * port - * - * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. - */ -void printInfo(char i) { - String command = ""; - command += (char)i; - command += "I!"; - mySDI12.sendCommand(command); - delay(100); - - String sdiResponse = mySDI12.readStringUntil('\n'); - sdiResponse.trim(); - // allccccccccmmmmmmvvvxxx...xx - Serial.print(sdiResponse.substring(0, 1)); // address - Serial.print(", "); - Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number - Serial.print(", "); - Serial.print(sdiResponse.substring(3, 11)); // vendor id - Serial.print(", "); - Serial.print(sdiResponse.substring(11, 17)); // sensor model - Serial.print(", "); - Serial.print(sdiResponse.substring(17, 20)); // sensor version - Serial.print(", "); - Serial.print(sdiResponse.substring(20)); // sensor id - Serial.print(", "); -} - -bool getResults(char i, int resultsExpected) { +bool getResults(char address, int resultsExpected) { uint8_t resultsReceived = 0; uint8_t cmd_number = 0; while (resultsReceived < resultsExpected && cmd_number <= 9) { String command = ""; - // in this example we will only take the 'DO' measurement - command = ""; - command += i; + command = ""; + command += address; command += "D"; command += cmd_number; command += "!"; // SDI-12 command to get data [address][D][dataOption][!] @@ -146,7 +110,7 @@ bool getResults(char i, int resultsExpected) { cmd_number++; } mySDI12.clearBuffer(); - + returnedResults[charToDec(address)] = resultsReceived; return resultsReceived == resultsExpected; } @@ -179,7 +143,7 @@ int startConcurrentMeasurement(char i, String meas_type = "") { } else { millisReady[sensorNum] = millis() + wait * 1000; } - returnedResults[sensorNum] = numResults; + expectedResults[sensorNum] = numResults; return numResults; } @@ -204,8 +168,40 @@ boolean checkActive(char i) { return false; } +/** + * @brief gets identification information from a sensor, and prints it to the serial + * port + * + * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. + * @param printCommands true to print the raw output and input from the command + */ +void printInfo(char i) { + String command = ""; + command += (char)i; + command += "I!"; + mySDI12.sendCommand(command); + delay(100); + + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + // allccccccccmmmmmmvvvxxx...xx + Serial.print("Address: "); + Serial.print(sdiResponse.substring(0, 1)); // address + Serial.print(", SDI-12 Version: "); + Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number + Serial.print(", Vendor ID: "); + Serial.print(sdiResponse.substring(3, 11)); // vendor id + Serial.print(", Sensor Model: "); + Serial.print(sdiResponse.substring(11, 17)); // sensor model + Serial.print(", Sensor Version: "); + Serial.print(sdiResponse.substring(17, 20)); // sensor version + Serial.print(", Sensor ID: "); + Serial.print(sdiResponse.substring(20)); // sensor id + Serial.println(); +} + void setup() { - Serial.begin(SERIAL_BAUD); + Serial.begin(serialBaud); while (!Serial) ; @@ -217,11 +213,11 @@ void setup() { Serial.println(mySDI12.TIMEOUT); // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); - delay(200); + if (powerPin >= 0) { + Serial.println("Powering up sensors, wait..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(10000L); } // Quickly Scan the Address Space @@ -229,7 +225,7 @@ void setup() { Serial.println("Protocol Version, Sensor Address, Sensor Vendor, Sensor Model, " "Sensor Version, Sensor ID"); - for (byte i = 0; i < 62; i++) { + for (int8_t i = firstAddress; i <= lastAddress; i++) { char addr = decToChar(i); if (checkActive(addr)) { numSensors++; @@ -255,31 +251,28 @@ void setup() { void loop() { // start all sensors measuring concurrently - for (byte i = 0; i < 62; i++) { + for (int8_t i = firstAddress; i <= lastAddress; i++) { char addr = decToChar(i); if (isActive[i]) { startConcurrentMeasurement(addr); } } // get all readings uint8_t numReadingsRecorded = 0; - while (numReadingsRecorded < numSensors) { - for (byte i = 0; i < 62; i++) { + do { + for (int8_t i = firstAddress; i <= lastAddress; i++) { char addr = decToChar(i); - if (isActive[i]) { - if (millis() > millisReady[i]) { - if (returnedResults[i] > 0) { - Serial.print(millis() / 1000); - Serial.print(", "); - Serial.print(addr); - Serial.print(", "); - getResults(addr, returnedResults[i]); - Serial.println(); - } - numReadingsRecorded++; - } + if (isActive[i] && millis() > millisReady[i] && expectedResults[i] > 0 && + (returnedResults[i] < expectedResults[i])) { + Serial.print(millis() / 1000); + Serial.print(", "); + Serial.print(addr); + Serial.print(", "); + getResults(addr, expectedResults[i]); + numReadingsRecorded++; + Serial.println(); } } - } + } while (numReadingsRecorded < numSensors); delay(10000); // wait ten seconds between measurement attempts. } diff --git a/examples/l_verify_crc/ReadMe.md b/examples/l_verify_crc/ReadMe.md new file mode 100644 index 0000000..c793bc7 --- /dev/null +++ b/examples/l_verify_crc/ReadMe.md @@ -0,0 +1,13 @@ +# Example L: Verifying CRC Values + +This is a simple demonstration of the SDI-12 library for Arduino. + +This is a very basic (stripped down) example where the user initiates a measurement with a CRC check and receives and verifies the CRC response + +[//]: # ( @section l_verify_crc_pio PlatformIO Configuration ) + +[//]: # ( @include{lineno} l_verify_crc/platformio.ini ) + +[//]: # ( @section l_verify_crc_code The Complete Example ) + +[//]: # ( @include{lineno} l_verify_crc/l_verify_crc.ino ) diff --git a/examples/l_verify_crc/l_verify_crc.ino b/examples/l_verify_crc/l_verify_crc.ino new file mode 100644 index 0000000..65b8f20 --- /dev/null +++ b/examples/l_verify_crc/l_verify_crc.ino @@ -0,0 +1,138 @@ +/** + * @example{lineno} l_verify_crc.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. + * @author Ruben Kertesz or \@rinnamon on twitter + * @date 2/10/2016 + * + * @brief Example L: Verify CRC + * + * This example initiates a measurement anc checks the CRC on the returns. + */ + +#include + +/* connection information */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ +char sensorAddress = '2'; /*!< The address of the SDI-12 sensor */ + +/** Define the SDI-12 bus */ +SDI12 mySDI12(dataPin); + +String sdiResponse = ""; +String myCommand = ""; + +void setup() { + Serial.begin(serialBaud); + while (!Serial) + ; + + Serial.println("Opening SDI-12 bus..."); + mySDI12.begin(); + delay(500); // allow things to settle + + // Power the sensors; + if (powerPin >= 0) { + Serial.println("Powering up sensors..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(200); + } + + // print out the sensor info + String command = ""; + command += String(sensorAddress); + command += "I!"; + mySDI12.sendCommand(command); + Serial.print(">>>"); + Serial.println(command); + delay(100); + + sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + // allccccccccmmmmmmvvvxxx...xx + Serial.print("<<<"); + Serial.println(sdiResponse); + + Serial.print("Address: "); + Serial.print(sdiResponse.substring(0, 1)); // address + Serial.print(", SDI-12 Version: "); + Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number + Serial.print(", Vendor ID: "); + Serial.print(sdiResponse.substring(3, 11)); // vendor id + Serial.print(", Sensor Model: "); + Serial.print(sdiResponse.substring(11, 17)); // sensor model + Serial.print(", Sensor Version: "); + Serial.print(sdiResponse.substring(17, 20)); // sensor version + Serial.print(", Sensor ID: "); + Serial.print(sdiResponse.substring(20)); // sensor id + Serial.println(); +} + +void loop() { + // first command to take a measurement + myCommand = String(sensorAddress) + "MC!"; + Serial.print(">>>"); + Serial.println(myCommand); // echo command to terminal + + mySDI12.sendCommand(myCommand); + delay(5); + + // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of + // measurments available, 0-9] + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + Serial.print("<<<"); + Serial.println(sdiResponse); + mySDI12.clearBuffer(); + + // find out how long we have to wait (in seconds). + uint8_t meas_time_s = sdiResponse.substring(1, 4).toInt(); + Serial.print("expected measurement time: "); + Serial.print(meas_time_s); + Serial.print(" s, "); + + // Set up the number of results to expect + int numResults = sdiResponse.substring(4).toInt(); + Serial.print("Number Results: "); + Serial.println(numResults); + + // listen for measurement to finish + unsigned long timerStart = millis(); + while ((millis() - timerStart) < (static_cast(meas_time_s) + 1) * 1000) { + if (mySDI12.available()) // sensor can interrupt us to let us know it is done early + { + unsigned long measTime = millis() - timerStart; + Serial.print("<<<"); + Serial.println(mySDI12.readStringUntil('\n')); + Serial.print("Completed after "); + Serial.print(measTime); + Serial.println(" ms"); + break; + } + } + + + // next command to request data from last measurement + myCommand = String(sensorAddress) + "D0!"; + Serial.print(">>>"); + Serial.println(myCommand); // echo command to terminal + + mySDI12.sendCommand(myCommand); + delay(30); // wait a while for a response + + + sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + Serial.print("<<<"); + Serial.println(sdiResponse); // write the response to the screen + bool crcMatch = mySDI12.verifyCRC(sdiResponse); + if (crcMatch) { + Serial.println("CRC matches!"); + } else { + Serial.println("CRC check failed!"); + } + mySDI12.clearBuffer(); +} diff --git a/examples/l_verify_crc/platformio.ini b/examples/l_verify_crc/platformio.ini new file mode 100644 index 0000000..81b92d6 --- /dev/null +++ b/examples/l_verify_crc/platformio.ini @@ -0,0 +1,15 @@ +; PlatformIO Project Configuration File + +[platformio] +description = SDI-12 Library Example L: Verifying CRC Values +src_dir = .piolibdeps/Arduino-SDI-12_ID1486/examples/l_verify_crc + +[env:mayfly] +monitor_speed = 115200 +board = mayfly +platform = atmelavr +framework = arduino +lib_ldf_mode = deep+ +lib_ignore = RTCZero +lib_deps = + SDI-12 diff --git a/extras/SDI12_spy/SDI12_spy.ino b/extras/SDI12_spy/SDI12_spy.ino new file mode 100644 index 0000000..c2d42a0 --- /dev/null +++ b/extras/SDI12_spy/SDI12_spy.ino @@ -0,0 +1,26 @@ +#include + +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ + +// Create object by which to communicate with the SDI-12 bus on SDIPIN +SDI12 slaveSDI12(dataPin); + +void setup() { + Serial.begin(115200); + slaveSDI12.begin(); + delay(500); + slaveSDI12.forceListen(); // sets SDIPIN as input to prepare for incoming message + Serial.println("Starting SDI-12 Spy"); +} + +void loop() { + while (slaveSDI12.available()) { + int readChar = slaveSDI12.read(); + Serial.write(readChar); + // if (readChar == '\n') { + // slaveSDI12.forceListen(); + // } else { + // delay(10);// 1 character ~ 7.5ms + // } + } +} diff --git a/extras/TestCommands/TestCommands.ino b/extras/TestCommands/TestCommands.ino new file mode 100644 index 0000000..0384c3a --- /dev/null +++ b/extras/TestCommands/TestCommands.ino @@ -0,0 +1,832 @@ +/** + * @example{lineno} TestCommands.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. + * @author Sara Damiano + */ + +#include + +#ifndef SDI12_DATA_PIN +#define SDI12_DATA_PIN 7 +#endif +#ifndef SDI12_POWER_PIN +#define SDI12_POWER_PIN 22 +#endif + +/* connection information */ +#if F_CPU > 48000000L +uint32_t serialBaud = 921600; /*!< The baud rate for the output serial port */ +#else +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +#endif +int8_t dataPin = SDI12_DATA_PIN; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = + SDI12_POWER_PIN; /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t wake_delay = 10; /*!< Extra time needed for the sensor to wake (0-100ms) */ +const int8_t firstAddress = + 0; /* The first address in the address space to check (0='0') */ +const int8_t lastAddress = + 6; /* The last address in the address space to check (62='z') */ +const int8_t commandsToTest = + 1; /*!< The number of measurement commands to test, between 1 and 11. */ + +/** Define the SDI-12 bus */ +SDI12 mySDI12(dataPin); + +/** Define some testing specs */ +const int8_t n_addresses = (lastAddress - firstAddress) + 1; + +/** Error codes, if returned */ +int8_t error_result_number = 7; +float no_error_value = 0; + +/// variable that alternates output type back and forth between parsed and raw +boolean flip = 0; + +String commands[] = {"", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + +// keeps track of active addresses +bool isActive[n_addresses]; + +// keeps track of the wait time for each active addresses +uint32_t meas_time_ms[n_addresses]; + +// keeps track of the time each sensor was started +uint32_t millisStarted[n_addresses]; + +// keeps track of the time each sensor will be ready +uint32_t millisReady[n_addresses]; + +// keeps track of the number of results expected +uint8_t expectedResults[n_addresses]; + +// keeps track of the number of results returned +uint8_t returnedResults[n_addresses]; + +String prev_result[n_addresses]; +String this_result[n_addresses]; +uint8_t numSensors = 0; + +struct startMeasurementResult { // Structure declaration + String returned_address; + uint8_t meas_time_s; + int numberResults; +}; + +/** + * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a + * decimal number between 0 and 61 (inclusive) to cover the 62 possible + * addresses. + */ +byte charToDec(char i) { + if ((i >= '0') && (i <= '9')) return i - '0'; + if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10; + if ((i >= 'A') && (i <= 'Z')) + return i - 'A' + 36; + else + return i; +} + +/** + * @brief maps a decimal number between 0 and 61 (inclusive) to allowable + * address characters '0'-'9', 'a'-'z', 'A'-'Z', + * + * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL. + */ +char decToChar(byte i) { + if (i < 10) return i + '0'; + if ((i >= 10) && (i < 36)) return i + 'a' - 10; + if ((i >= 36) && (i <= 62)) + return i + 'A' - 36; + else + return i; +} + +struct getResultsResult { // Structure declaration + uint8_t resultsReceived; + uint8_t maxDataCommand; + bool addressMatch; + bool crcMatch; + bool errorCode; + bool success; +}; + +getResultsResult getResults(char address, int resultsExpected, bool verify_crc = false, + bool printCommands = true) { + uint8_t resultsReceived = 0; + uint8_t cmd_number = 0; + // The maximum number of characters that can be returned in the part of the + // response to a D command is either 35 or 75. If the D command is issued to + // retrieve data in response to a concurrent measurement command, or in response to + // a high-volume ASCII measurement command, the maximum is 75. The maximum is also + // 75 in response to a continuous measurement command. Otherwise, the maximum is 35. + int max_sdi_response = 76; + // max chars in a unsigned 64 bit number + int max_sdi_digits = 21; + + String compiled_response = ""; + + bool success = true; + + // Create the return struct + getResultsResult return_result; + return_result.resultsReceived = 0; + return_result.maxDataCommand = 0; + return_result.addressMatch = true; + return_result.crcMatch = true; + return_result.errorCode = false; + return_result.success = true; + + while (resultsReceived < resultsExpected && cmd_number <= 9) { + String command = ""; + command += address; + command += "D"; + command += cmd_number; + command += "!"; // SDI-12 command to get data [address][D][dataOption][!] + mySDI12.sendCommand(command, wake_delay); + + // uint32_t start = millis(); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + char resp_buffer[max_sdi_response] = {'\0'}; + + // read bytes into the char array until we get to a new line (\r\n) + size_t bytes_read = mySDI12.readBytesUntil('\n', resp_buffer, max_sdi_response); + // Serial.print(bytes_read); + // Serial.println(" characters"); + + size_t data_bytes_read = bytes_read - 1; // subtract one for the /r before the /n + String sdiResponse = String(resp_buffer); + compiled_response += sdiResponse; + sdiResponse.trim(); + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + // Serial.println(sdiResponse.length()); + // Serial.print("<<<"); + // Serial.println(resp_buffer); + // Serial.println(strnlen(resp_buffer, max_sdi_response)); + } + // read and clear anything else from the buffer + int extra_chars = 0; + while (mySDI12.available()) { + Serial.write(mySDI12.read()); + extra_chars++; + } + if (extra_chars > 0) { + Serial.print(extra_chars); + Serial.println(" additional characters received."); + } + mySDI12.clearBuffer(); + + // check the address, break if it's incorrect + char returned_address = resp_buffer[0]; + if (returned_address != address) { + if (printCommands) { + Serial.println("Wrong address returned!"); + Serial.print("Expected "); + Serial.print(String(address)); + Serial.print(" Got "); + Serial.println(String(returned_address)); + Serial.println(String(resp_buffer)); + } + success = false; + return_result.addressMatch = false; + break; + } + + // check the crc, break if it's incorrect + if (verify_crc) { + bool crcMatch = mySDI12.verifyCRC(sdiResponse); + data_bytes_read = data_bytes_read - 3; + if (crcMatch) { + if (printCommands) { Serial.println("CRC valid"); } + } else { + if (printCommands) { Serial.println("CRC check failed!"); } + return_result.crcMatch = false; + success = false; + break; + } + } + + bool gotResults = false; + char float_buffer[max_sdi_digits] = {'\0'}; + char* dec_pl = float_buffer; + uint8_t fb_pos = 0; // start at start of buffer + bool finished_last_number = false; + // iterate through the char array and to check results + // NOTE: start at 1 since we already looked at the address! + for (size_t i = 1; i < data_bytes_read; i++) { + // Get the character at position + char c = resp_buffer[i]; + // Serial.print(i); + // Serial.print(" of "); + // Serial.print(data_bytes_read); + // Serial.print(" '"); + // Serial.print(c); + // Serial.println("'"); + // if we didn't get something number-esque or we're at the end of the buffer, + // assume the last number finished and parse it + //(c != '-' && (c < '0' || c > '9') && c != '.') + if (c == '-' || (c >= '0' && c <= '9') || c == '.') { + // if there's a number, a decimal, or a negative sign next in the + // buffer, add it to the float buffer. + float_buffer[fb_pos] = c; + fb_pos++; + float_buffer[fb_pos] = '\0'; // null terminate the buffer + finished_last_number = false; + // Serial.print("Added to float buffer, currently: '"); + // Serial.print(float_buffer); + // Serial.println("'"); + } else { + // Serial.println("Non Numeric"); + finished_last_number = true; + } + // if we've gotten to the end of a number or the end of the buffer, parse the + // character + if ((finished_last_number || i == data_bytes_read - 1) && + strnlen(float_buffer, max_sdi_digits) > 0) { + float result = atof(float_buffer); + if (printCommands) { + Serial.print("Result "); + Serial.print(resultsReceived); + Serial.print(", Raw value: "); + Serial.print(float_buffer); + dec_pl = strchr(float_buffer, '.'); + size_t len_post_dec = 0; + if (dec_pl != nullptr) { len_post_dec = strnlen(dec_pl, max_sdi_digits) - 1; } + Serial.print(", Len after decimal: "); + Serial.print(len_post_dec); + Serial.print(", Parsed value: "); + Serial.println(String(result, len_post_dec)); + } + // add how many results we have + if (result != -9999) { + gotResults = true; + resultsReceived++; + } + // check for a failure error code at the end + if (error_result_number >= 1) { + if (resultsReceived == error_result_number && result != no_error_value) { + success = false; + return_result.errorCode = true; + if (printCommands) { + Serial.print("Got a failure code of "); + Serial.println(String(result, strnlen(dec_pl, max_sdi_digits) - 1)); + } + } + } + + // empty the buffer + float_buffer[0] = '\0'; + fb_pos = 0; + } + } + + if (!gotResults) { + if (printCommands) { + Serial.println((" No results received, will not continue requests!")); + } + break; + } // don't do another loop if we got nothing + + if (printCommands) { + Serial.print("Total Results Received: "); + Serial.print(resultsReceived); + Serial.print(", Remaining: "); + Serial.println(resultsExpected - resultsReceived); + } + + cmd_number++; + } + + mySDI12.clearBuffer(); + + if (printCommands) { + Serial.print("After "); + Serial.print(cmd_number); + Serial.print(" data commands got "); + Serial.print(resultsReceived); + Serial.print(" results of the expected "); + Serial.print(resultsExpected); + Serial.print(" expected. This is a "); + Serial.println(resultsReceived == resultsExpected ? "success." : "failure."); + } + + success &= resultsReceived == resultsExpected; + this_result[charToDec(address)] = compiled_response; + return_result.resultsReceived = resultsReceived; + return_result.maxDataCommand = cmd_number; + return_result.success = success; + return return_result; +} + +bool getContinuousResults(char address, int resultsExpected, + bool printCommands = true) { + uint8_t resultsReceived = 0; + uint8_t cmd_number = 0; + while (resultsReceived < resultsExpected && cmd_number <= 9) { + String command = ""; + command += address; + command += "R"; + command += cmd_number; + command += "!"; // SDI-12 command to get data [address][D][dataOption][!] + mySDI12.sendCommand(command, wake_delay); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + + uint32_t start = millis(); + while (mySDI12.available() < 3 && (millis() - start) < 1500) {} + if (printCommands) { + Serial.print("<<<"); + Serial.write(mySDI12.read()); // ignore the repeated SDI12 address + } + + while (mySDI12.available()) { + char c = mySDI12.peek(); + if (c == '-' || (c >= '0' && c <= '9') || c == '.') { + float result = mySDI12.parseFloat(SKIP_NONE); + Serial.print(String(result, 10)); + if (result != -9999) { resultsReceived++; } + } else if (c >= 0 && c != '\r' && c != '\n') { + Serial.write(mySDI12.read()); + } else { + mySDI12.read(); + } + delay(10); // 1 character ~ 7.5ms + } + if (printCommands) { + Serial.print("Total Results Received: "); + Serial.print(resultsReceived); + Serial.print(", Remaining: "); + Serial.println(resultsExpected - resultsReceived); + } + if (!resultsReceived) { break; } // don't do another loop if we got nothing + cmd_number++; + } + mySDI12.clearBuffer(); + + return resultsReceived == resultsExpected; +} + +startMeasurementResult startMeasurement(char address, bool is_concurrent = false, + bool request_crc = false, String meas_type = "", + bool printCommands = true) { + // Create the return struct + startMeasurementResult return_result; + return_result.returned_address = ""; + return_result.meas_time_s = 0; + return_result.numberResults = 0; + + String command = ""; + command += address; // All commands start with the address + command += is_concurrent ? "C" : "M"; // C for concurrent, M for standard + command += request_crc ? "C" : ""; // add an additional C to request a CRC + command += meas_type; // Measurement type, "" or 0-9 + command += "!"; // All commands end with "!" + mySDI12.sendCommand(command, wake_delay); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + + // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of + // measurments available, 0-9] + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + mySDI12.clearBuffer(); + + // check the address, return if it's incorrect + String returned_address = sdiResponse.substring(0, 1); + char ret_addr_array[2]; + returned_address.toCharArray(ret_addr_array, sizeof(ret_addr_array)); + return_result.returned_address = ret_addr_array[0]; + if (returned_address != String(address)) { + if (printCommands) { + Serial.println("Wrong address returned!"); + Serial.print("Expected "); + Serial.print(String(address)); + Serial.print(" Got "); + Serial.println(returned_address); + } + return return_result; + } + + // find out how long we have to wait (in seconds). + uint8_t meas_time_s = sdiResponse.substring(1, 4).toInt(); + return_result.meas_time_s = meas_time_s; + if (printCommands) { + Serial.print("expected measurement time: "); + Serial.print(meas_time_s); + Serial.print(" s, "); + } + + // Set up the number of results to expect + int numResults = sdiResponse.substring(4).toInt(); + return_result.numberResults = numResults; + if (printCommands) { + Serial.print("Number Results: "); + Serial.println(numResults); + } + + return return_result; +} + +// This is a separate function in this example so the wait times can all be filled into +// the appropriate arrays +int startConcurrentMeasurement(char address, bool request_crc = false, + String meas_type = "", bool printCommands = true) { + startMeasurementResult startResult = startMeasurement(address, true, request_crc, + meas_type, printCommands); + + uint8_t sensorNum = + charToDec(address); // e.g. convert '0' to 0, 'a' to 10, 'Z' to 61. + meas_time_ms[sensorNum] = (static_cast(startResult.meas_time_s)) * 1000; + millisStarted[sensorNum] = millis(); + if (startResult.meas_time_s == 0) { + millisReady[sensorNum] = millis(); + } else { + // give an extra second + // millisReady[sensorNum] = millis() + meas_time_ms[sensorNum] + 1000; + // subtract a second to start polling early + millisReady[sensorNum] = millis() + meas_time_ms[sensorNum] - 1000; + } + expectedResults[sensorNum] = startResult.numberResults; + + return startResult.numberResults; +} + +uint32_t takeMeasurement(char address, bool request_crc = false, String meas_type = "", + bool printCommands = true) { + startMeasurementResult startResult = startMeasurement(address, false, request_crc, + meas_type, printCommands); + if (startResult.numberResults == 0) { return -1; } + + uint32_t timerStart = millis(); + uint32_t measTime = -1; + // wait up to 1 second longer than the specified return time + while ((millis() - timerStart) < + (static_cast(startResult.meas_time_s) + 1) * 1000) { + if (mySDI12.available()) { + break; + } // sensor can interrupt us to let us know it is done early + } + measTime = millis() - timerStart; + String interrupt_response = mySDI12.readStringUntil('\n'); + if (printCommands) { + Serial.print("<<<"); + Serial.println(interrupt_response); + Serial.print("Completed after "); + Serial.print(measTime); + Serial.println(" ms"); + } + + // if we got results, return the measurement time, else -1 + if (getResults(address, startResult.numberResults, request_crc, printCommands) + .success) { + return measTime; + } + + return -1; +} + +// this checks for activity at a particular address +// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' +bool checkActive(char address, int8_t numPings = 3, bool printCommands = true) { + String command = ""; + command += (char)address; // sends basic 'acknowledge' command [address][!] + command += "!"; + + for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + mySDI12.sendCommand(command, wake_delay); + + // the sensor should just return its address + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + mySDI12.clearBuffer(); + + // check the address, return false if it's incorrect + String returned_address = sdiResponse.substring(0, 1); + char ret_addr_array[2]; + returned_address.toCharArray(ret_addr_array, sizeof(ret_addr_array)); + if (returned_address == String(address)) { return true; } + } + mySDI12.clearBuffer(); + return false; +} + +/** + * @brief gets identification information from a sensor, and prints it to the serial + * port + * + * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. + * @param printCommands true to print the raw output and input from the command + */ +bool printInfo(char i, bool printCommands = true) { + String command = ""; + command += (char)i; + command += "I!"; + mySDI12.sendCommand(command, wake_delay); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + delay(100); + + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + // allccccccccmmmmmmvvvxxx...xx + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + + Serial.print("Address: "); + Serial.print(sdiResponse.substring(0, 1)); // address + Serial.print(", SDI-12 Version: "); + Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number + Serial.print(", Vendor ID: "); + Serial.print(sdiResponse.substring(3, 11)); // vendor id + Serial.print(", Sensor Model: "); + Serial.print(sdiResponse.substring(11, 17)); // sensor model + Serial.print(", Sensor Version: "); + Serial.print(sdiResponse.substring(17, 20)); // sensor version + Serial.print(", Sensor ID: "); + Serial.print(sdiResponse.substring(20)); // sensor id + Serial.println(); + + if (sdiResponse.length() < 3) { return false; }; + return true; +} + +void setup() { + Serial.begin(serialBaud); + while (!Serial) + ; + + Serial.print("Opening SDI-12 bus on pin "); + Serial.print(String(dataPin)); + Serial.println("..."); + mySDI12.begin(); + delay(500); // allow things to settle + + Serial.println("Timeout value: "); + Serial.println(mySDI12.TIMEOUT); + + // Fill arrays with 0's + for (int8_t i = firstAddress; i <= lastAddress; i++) { + isActive[i] = false; + meas_time_ms[i] = 0; + millisStarted[i] = 0; + millisReady[i] = 0; + expectedResults[i] = 0; + returnedResults[i] = 0; + prev_result[i] = ""; + this_result[i] = ""; + } + + // Power the sensors; + if (powerPin >= 0) { + Serial.println("Powering up sensors with pin "); + Serial.print(String(powerPin)); + Serial.println(", wait 30s..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(30000L); + } else { + Serial.println("Wait 5s..."); + delay(5000L); + } + + // Quickly Scan the Address Space + Serial.println("Scanning all addresses, please wait..."); + + for (int8_t i = firstAddress; i <= lastAddress; i++) { + char addr = decToChar(i); + Serial.print("i: "); + Serial.print(i); + Serial.print(", addr: "); + Serial.print(addr); + Serial.print(", reversed: "); + Serial.println(charToDec(addr)); + if (checkActive(addr, 5, true)) { + numSensors++; + isActive[i] = 1; + // Serial.println(", +"); + printInfo(addr, true); + } else { + // Serial.println(", -"); + } + } + Serial.print("Total number of sensors found: "); + Serial.println(numSensors); + + if (numSensors == 0) { + Serial.println( + "No sensors found, please check connections and restart the Arduino."); + while (true) { delay(10); } // do nothing forever + } + + Serial.println(); + Serial.println("-------------------------------------------------------------------" + "------------"); + + delay(1000); +} + +void loop() { + flip = !flip; // flip the switch between concurrent and not + // flip = 1; + // flip = 0; + uint32_t start = millis(); + // Serial.print("Flip: "); + // Serial.println(flip); + + // Fill arrays with 0's + for (int8_t i = firstAddress; i <= lastAddress; i++) { + meas_time_ms[i] = 0; + millisStarted[i] = 0; + millisReady[i] = 0; + expectedResults[i] = 0; + returnedResults[i] = 0; + } + + if (flip) { + // measure one at a time + for (int8_t i = firstAddress; i <= lastAddress; i++) { + char addr = decToChar(i); + if (isActive[i]) { + for (uint8_t a = 0; a < commandsToTest; a++) { + Serial.print("Command "); + Serial.print(i); + Serial.print("M"); + Serial.print(commands[a]); + Serial.println('!'); + takeMeasurement(addr, true, commands[a], true); + } + // getContinuousResults(addr, 3); + Serial.println(); + } else { + Serial.print("Address "); + Serial.print(addr); + Serial.println(" is not active"); + } + } + Serial.print("Total Time for Individual Measurements: "); + Serial.println(millis() - start); + } else { + for (uint8_t a = 0; a < commandsToTest; a++) { + uint32_t min_wait = 60000L; + uint32_t max_wait = 0; + uint32_t for_start = millis(); + // start all sensors measuring concurrently + for (int8_t i = firstAddress; i <= lastAddress; i++) { + char addr = decToChar(i); + if (isActive[i]) { + Serial.print("Command "); + Serial.print(i); + Serial.print("C"); + Serial.print(commands[a]); + Serial.println('!'); + startConcurrentMeasurement(addr, true, commands[a], true); + if (meas_time_ms[i] < min_wait) { min_wait = meas_time_ms[i]; } + if (meas_time_ms[i] > max_wait) { max_wait = meas_time_ms[i]; } + } else { + Serial.print("Address "); + Serial.print(addr); + Serial.println(" is not active"); + } + } + // min_wait = 800; + min_wait = max(static_cast(10), min_wait / 2); + max_wait = max(static_cast(1000), + max_wait + static_cast(2000)); + Serial.print("minimum expected wait for all sensors: "); + Serial.println(min_wait); + Serial.print("maximum expected wait for all sensors: "); + Serial.println(max_wait); + +#if defined(TEST_PRINT_ARRAY) + Serial.print("i,\t"); + Serial.print("addr,\t"); + Serial.print("isActive[i],\t"); + Serial.print("millis,\t"); + Serial.print("timeWaited,\t"); + Serial.print("millisReady[i],\t"); + Serial.print("expectedResults[i],\t"); + Serial.print("returnedResults[i],\t"); + Serial.print("millis() > millisReady[i],\t"); + Serial.print("expectedResults[i] > 0,\t"); + Serial.print("returnedResults[i] < expectedResults[i],\t"); + Serial.print("numSensors,\t"); + Serial.print("numReadingsRecorded,\t"); + Serial.print("maxDataCommand,\t"); + Serial.print("resultsReceived,\t"); + Serial.print("errorCode,\t"); + Serial.print("crcMatch,\t"); + Serial.print("gotGoodResults,\t"); + Serial.print("numReadingsRecorded"); + Serial.println(); +#endif + + uint8_t numReadingsRecorded = 0; + delay(min_wait); + + do { + // get all readings + for (int8_t i = firstAddress; i <= lastAddress; i++) { + uint32_t timeWaited = 0; + if (millisStarted[i] != 0) { timeWaited = millis() - millisStarted[i]; } + if (this_result[i] != "") { prev_result[i] = this_result[i]; } + + char addr = decToChar(i); + +#if defined(TEST_PRINT_ARRAY) + Serial.print(i); + Serial.print(",\t\""); + Serial.print(decToChar(i)); + Serial.print("\",\t"); + Serial.print(isActive[i]); + Serial.print(",\t"); + Serial.print(millis()); + Serial.print(",\t"); + Serial.print(timeWaited); + Serial.print(",\t"); + Serial.print(millisReady[i]); + Serial.print(",\t"); + Serial.print(expectedResults[i]); + Serial.print(",\t"); + Serial.print(returnedResults[i]); + Serial.print(",\t"); + Serial.print(millis() > millisReady[i]); + Serial.print(",\t"); + Serial.print(expectedResults[i] > 0); + Serial.print(",\t"); + Serial.print(returnedResults[i] < expectedResults[i]); + Serial.print(",\t"); + Serial.print(numSensors); + Serial.print(",\t"); + Serial.print(numReadingsRecorded); +#endif + + if (isActive[i] && (millis() > millisReady[i]) && (expectedResults[i] > 0) && + (returnedResults[i] < expectedResults[i])) { +#ifndef TEST_PRINT_ARRAY + Serial.print("timeWaited: "); + Serial.println(timeWaited); +#endif + getResultsResult cResult = getResults(addr, expectedResults[i], true); + returnedResults[i] = cResult.resultsReceived; + bool gotGoodResults = cResult.success; + if (gotGoodResults) { + numReadingsRecorded++; +#ifndef TEST_PRINT_ARRAY + Serial.print("Got results from "); + Serial.print(numReadingsRecorded); + Serial.print(" of "); + Serial.print(numSensors); + Serial.print(" sensors"); +#endif + } +#if defined(TEST_PRINT_ARRAY) + Serial.print(",\t"); + Serial.print(cResult.maxDataCommand); + Serial.print(",\t"); + Serial.print(cResult.resultsReceived); + Serial.print(",\t"); + Serial.print(cResult.errorCode); + Serial.print(",\t"); + Serial.print(cResult.crcMatch); + Serial.print(",\t"); + Serial.print(gotGoodResults); +#endif + } + Serial.println(); + } + + } while (millis() - for_start < max_wait && numReadingsRecorded < numSensors); + } + Serial.print("Total Time for Concurrent Measurements: "); + Serial.println(millis() - start); + } + + Serial.println("-------------------------------------------------------------------" + "------------"); +} diff --git a/extras/TestSensorTiming/TestSensorTiming.ino b/extras/TestSensorTiming/TestSensorTiming.ino new file mode 100644 index 0000000..19cb802 --- /dev/null +++ b/extras/TestSensorTiming/TestSensorTiming.ino @@ -0,0 +1,580 @@ +/** + * @example{lineno} TestSensorTiming.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. + * @author Sara Damiano + * @date March 2024 + */ + +#include + +#ifndef SDI12_DATA_PIN +#define SDI12_DATA_PIN 7 +#endif +#ifndef SDI12_POWER_PIN +#define SDI12_POWER_PIN 22 +#endif + +/* connection information */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = SDI12_DATA_PIN; /*!< The pin of the SDI-12 data bus */ +int8_t powerPin = + SDI12_POWER_PIN; /*!< The sensor power pin (or -1 if not switching power) */ +uint32_t wake_delay = 0; /*!< Extra time needed for the sensor to wake (0-100ms) */ +char sensorAddress = '0'; /*!< The address of the SDI-12 sensor */ + +/** Define the SDI-12 bus */ +SDI12 mySDI12(dataPin); + +/** Define some testing specs */ + +/** Error codes, if returned */ +int8_t error_result_number = 7; +float no_error_value = 0; + +/** Testing turning off power */ +bool testPowerOff = false; +int32_t min_power_delay = 100L; /*!< The min time to test wake after power on. */ +int32_t max_power_delay = 180000L; /*!< The max time to test wake after power on. */ +int32_t increment_power_delay = 100L; /*!< The time to lengthen waits between reps. */ +int32_t power_off_time = 60000L; /*!< The time to power off between tests. */ +/** NOTE: set the power off time to be something similar to what you will be using the + * the real world! Some sensors take longer to warm up if they've been off for a while. + */ + +/** Testing the length of the break */ +bool testBreak = true; +int32_t min_wake_delay = 0; /*!< The min time to test wake after a line break. */ +int32_t max_wake_delay = 100; /*!< The max time to test wake (should be <=100). */ +int32_t increment_wake = 5; /*!< The time to lengthen waits between reps. */ + +/** set some initial values */ +int32_t power_delay = min_power_delay; +int32_t wake_delay = min_wake_delay; + +int32_t total_meas_time = 0; +int32_t total_meas_made = 0; +uint32_t max_meas_time = 0; + +struct startMeasurementResult { // Structure declaration + String returned_address; + uint8_t meas_time_s; + int numberResults; +}; + +struct getResultsResult { // Structure declaration + uint8_t resultsReceived; + uint8_t maxDataCommand; + bool addressMatch; + bool crcMatch; + bool errorCode; + bool success; +}; + +getResultsResult getResults(char address, int resultsExpected, bool verify_crc = false, + bool printCommands = true) { + uint8_t resultsReceived = 0; + uint8_t cmd_number = 0; + // The maximum number of characters that can be returned in the part of the + // response to a D command is either 35 or 75. If the D command is issued to + // retrieve data in response to a concurrent measurement command, or in response to + // a high-volume ASCII measurement command, the maximum is 75. The maximum is also + // 75 in response to a continuous measurement command. Otherwise, the maximum is 35. + int max_sdi_response = 76; + // max chars in a unsigned 64 bit number + int max_sdi_digits = 21; + + String compiled_response = ""; + + bool success = true; + + // Create the return struct + getResultsResult return_result; + return_result.resultsReceived = 0; + return_result.maxDataCommand = 0; + return_result.addressMatch = true; + return_result.crcMatch = true; + return_result.errorCode = false; + return_result.success = true; + + while (resultsReceived < resultsExpected && cmd_number <= 9) { + String command = ""; + command += address; + command += "D"; + command += cmd_number; + command += "!"; // SDI-12 command to get data [address][D][dataOption][!] + mySDI12.sendCommand(command, wake_delay); + + // uint32_t start = millis(); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + char resp_buffer[max_sdi_response] = {'\0'}; + + // read bytes into the char array until we get to a new line (\r\n) + size_t bytes_read = mySDI12.readBytesUntil('\n', resp_buffer, max_sdi_response); + // Serial.print(bytes_read); + // Serial.println(" characters"); + + size_t data_bytes_read = bytes_read - 1; // subtract one for the /r before the /n + String sdiResponse = String(resp_buffer); + compiled_response += sdiResponse; + sdiResponse.trim(); + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + // Serial.println(sdiResponse.length()); + // Serial.print("<<<"); + // Serial.println(resp_buffer); + // Serial.println(strnlen(resp_buffer, max_sdi_response)); + } + // read and clear anything else from the buffer + int extra_chars = 0; + while (mySDI12.available()) { + Serial.write(mySDI12.read()); + extra_chars++; + } + if (extra_chars > 0) { + Serial.print(extra_chars); + Serial.println(" additional characters received."); + } + mySDI12.clearBuffer(); + + // check the address, break if it's incorrect + char returned_address = resp_buffer[0]; + if (returned_address != address) { + if (printCommands) { + Serial.println("Wrong address returned!"); + Serial.print("Expected "); + Serial.print(String(address)); + Serial.print(" Got "); + Serial.println(String(returned_address)); + Serial.println(String(resp_buffer)); + } + success = false; + return_result.addressMatch = false; + break; + } + + // check the crc, break if it's incorrect + if (verify_crc) { + bool crcMatch = mySDI12.verifyCRC(sdiResponse); + data_bytes_read = data_bytes_read - 3; + if (crcMatch) { + if (printCommands) { Serial.println("CRC valid"); } + } else { + if (printCommands) { Serial.println("CRC check failed!"); } + return_result.crcMatch = false; + success = false; + break; + } + } + + bool gotResults = false; + char float_buffer[max_sdi_digits] = {'\0'}; + char* dec_pl = float_buffer; + uint8_t fb_pos = 0; // start at start of buffer + bool finished_last_number = false; + // iterate through the char array and to check results + // NOTE: start at 1 since we already looked at the address! + for (size_t i = 1; i < data_bytes_read; i++) { + // Get the character at position + char c = resp_buffer[i]; + // Serial.print(i); + // Serial.print(" of "); + // Serial.print(data_bytes_read); + // Serial.print(" '"); + // Serial.print(c); + // Serial.println("'"); + // if we didn't get something number-esque or we're at the end of the buffer, + // assume the last number finished and parse it + //(c != '-' && (c < '0' || c > '9') && c != '.') + if (c == '-' || (c >= '0' && c <= '9') || c == '.') { + // if there's a number, a decimal, or a negative sign next in the + // buffer, add it to the float buffer. + float_buffer[fb_pos] = c; + fb_pos++; + float_buffer[fb_pos] = '\0'; // null terminate the buffer + finished_last_number = false; + // Serial.print("Added to float buffer, currently: '"); + // Serial.print(float_buffer); + // Serial.println("'"); + } else { + // Serial.println("Non Numeric"); + finished_last_number = true; + } + // if we've gotten to the end of a number or the end of the buffer, parse the + // character + if ((finished_last_number || i == data_bytes_read - 1) && + strnlen(float_buffer, max_sdi_digits) > 0) { + float result = atof(float_buffer); + if (printCommands) { + Serial.print("Result "); + Serial.print(resultsReceived); + Serial.print(", Raw value: "); + Serial.print(float_buffer); + dec_pl = strchr(float_buffer, '.'); + size_t len_post_dec = 0; + if (dec_pl != nullptr) { len_post_dec = strnlen(dec_pl, max_sdi_digits) - 1; } + Serial.print(", Len after decimal: "); + Serial.print(len_post_dec); + Serial.print(", Parsed value: "); + Serial.println(String(result, len_post_dec)); + } + // add how many results we have + if (result != -9999) { + gotResults = true; + resultsReceived++; + } + // check for a failure error code at the end + if (error_result_number >= 1) { + if (resultsReceived == error_result_number && result != no_error_value) { + success = false; + return_result.errorCode = true; + if (printCommands) { + Serial.print("Got a failure code of "); + Serial.println(String(result, strnlen(dec_pl, max_sdi_digits) - 1)); + } + } + } + + // empty the buffer + float_buffer[0] = '\0'; + fb_pos = 0; + } + } + + if (!gotResults) { + if (printCommands) { + Serial.println((" No results received, will not continue requests!")); + } + break; + } // don't do another loop if we got nothing + + if (printCommands) { + Serial.print("Total Results Received: "); + Serial.print(resultsReceived); + Serial.print(", Remaining: "); + Serial.println(resultsExpected - resultsReceived); + } + + cmd_number++; + } + + mySDI12.clearBuffer(); + + if (printCommands) { + Serial.print("After "); + Serial.print(cmd_number); + Serial.print(" data commands got "); + Serial.print(resultsReceived); + Serial.print(" results of the expected "); + Serial.print(resultsExpected); + Serial.print(" expected. This is a "); + Serial.println(resultsReceived == resultsExpected ? "success." : "failure."); + } + + success &= resultsReceived == resultsExpected; + return_result.resultsReceived = resultsReceived; + return_result.maxDataCommand = cmd_number; + return_result.success = success; + return return_result; +} + +startMeasurementResult startMeasurement(char address, bool is_concurrent = false, + bool request_crc = false, String meas_type = "", + bool printCommands = true) { + // Create the return struct + startMeasurementResult return_result; + return_result.returned_address = ""; + return_result.meas_time_s = 0; + return_result.numberResults = 0; + + String command = ""; + command += address; // All commands start with the address + command += is_concurrent ? "C" : "M"; // C for concurrent, M for standard + command += request_crc ? "C" : ""; // add an additional C to request a CRC + command += meas_type; // Measurement type, "" or 0-9 + command += "!"; // All commands end with "!" + mySDI12.sendCommand(command, wake_delay); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + + // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of + // measurments available, 0-9] + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + mySDI12.clearBuffer(); + + // check the address, return if it's incorrect + String returned_address = sdiResponse.substring(0, 1); + char ret_addr_array[2]; + returned_address.toCharArray(ret_addr_array, sizeof(ret_addr_array)); + return_result.returned_address = ret_addr_array[0]; + if (returned_address != String(address)) { + if (printCommands) { + Serial.println("Wrong address returned!"); + Serial.print("Expected "); + Serial.print(String(address)); + Serial.print(" Got "); + Serial.println(returned_address); + } + return return_result; + } + + // find out how long we have to wait (in seconds). + uint8_t meas_time_s = sdiResponse.substring(1, 4).toInt(); + return_result.meas_time_s = meas_time_s; + if (printCommands) { + Serial.print("expected measurement time: "); + Serial.print(meas_time_s); + Serial.print(" s, "); + } + + // Set up the number of results to expect + int numResults = sdiResponse.substring(4).toInt(); + return_result.numberResults = numResults; + if (printCommands) { + Serial.print("Number Results: "); + Serial.println(numResults); + } + + return return_result; +} + +uint32_t takeMeasurement(char address, bool request_crc = false, String meas_type = "", + bool printCommands = true) { + startMeasurementResult startResult = startMeasurement(address, false, request_crc, + meas_type, printCommands); + if (startResult.numberResults == 0) { return -1; } + + uint32_t timerStart = millis(); + uint32_t measTime = -1; + // wait up to 1 second longer than the specified return time + while ((millis() - timerStart) < + (static_cast(startResult.meas_time_s) + 1) * 1000) { + if (mySDI12.available()) { + break; + } // sensor can interrupt us to let us know it is done early + } + measTime = millis() - timerStart; + String interrupt_response = mySDI12.readStringUntil('\n'); + if (printCommands) { + Serial.print("<<<"); + Serial.println(interrupt_response); + Serial.print("Completed after "); + Serial.print(measTime); + Serial.println(" ms"); + } + + // if we got results, return the measurement time, else -1 + if (getResults(address, startResult.numberResults, request_crc, printCommands) + .success) { + return measTime; + } + + return -1; +} + +// this checks for activity at a particular address +// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' +bool checkActive(char address, int8_t numPings = 3, bool printCommands = true) { + String command = ""; + command += (char)address; // sends basic 'acknowledge' command [address][!] + command += "!"; + + for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + mySDI12.sendCommand(command, wake_delay); + + // the sensor should just return its address + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + mySDI12.clearBuffer(); + + // check the address, return false if it's incorrect + String returned_address = sdiResponse.substring(0, 1); + char ret_addr_array[2]; + returned_address.toCharArray(ret_addr_array, sizeof(ret_addr_array)); + if (returned_address == String(address)) { return true; } + } + mySDI12.clearBuffer(); + return false; +} + +/** + * @brief gets identification information from a sensor, and prints it to the serial + * port + * + * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. + * @param printCommands true to print the raw output and input from the command + */ +bool printInfo(char i, bool printCommands = true) { + String command = ""; + command += (char)i; + command += "I!"; + mySDI12.sendCommand(command, wake_delay); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + delay(100); + + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + // allccccccccmmmmmmvvvxxx...xx + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + + Serial.print("Address: "); + Serial.print(sdiResponse.substring(0, 1)); // address + Serial.print(", SDI-12 Version: "); + Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number + Serial.print(", Vendor ID: "); + Serial.print(sdiResponse.substring(3, 11)); // vendor id + Serial.print(", Sensor Model: "); + Serial.print(sdiResponse.substring(11, 17)); // sensor model + Serial.print(", Sensor Version: "); + Serial.print(sdiResponse.substring(17, 20)); // sensor version + Serial.print(", Sensor ID: "); + Serial.print(sdiResponse.substring(20)); // sensor id + Serial.println(); + + if (sdiResponse.length() < 3) { return false; }; + return true; +} + +void setup() { + Serial.begin(serialBaud); + while (!Serial) + ; + + Serial.print("Opening SDI-12 bus on pin "); + Serial.print(String(dataPin)); + Serial.println("..."); + mySDI12.begin(); + delay(500); // allow things to settle + + Serial.println("Timeout value: "); + Serial.println(mySDI12.TIMEOUT); + + // Power the sensors; + if (powerPin >= 0 && !testPowerOff) { + Serial.println("Powering up sensors with pin "); + Serial.print(String(powerPin)); + Serial.println(", wait 30s..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(30000L); + } else { + Serial.println("Wait 5s..."); + delay(5000L); + } +} + +void loop() { + bool got_good_results = true; + int checks_at_time = 0; + while (got_good_results && checks_at_time < 25) { + Serial.print("Repeat attempt "); + Serial.print(checks_at_time); + Serial.print(" with power on warm-up time of "); + Serial.println(power_delay); + + // Power down the sensors; + if (powerPin >= 0 && testPowerOff) { + Serial.print("Powering down sensors, wait "); + Serial.print(power_off_time); + Serial.println(" ms"); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, LOW); + delay(power_off_time); + } + + // Power up the sensors; + if (powerPin >= 0 && testPowerOff) { + Serial.print("Powering up sensors, wait "); + Serial.print(power_delay); + Serial.println(" ms"); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(power_delay); + mySDI12.clearBuffer(); + } + + // checkActive(sensorAddress, true); + + uint32_t this_meas_time = takeMeasurement(sensorAddress, true, "", true); + bool this_result_good = this_meas_time != static_cast(-1); + + if (this_result_good) { + total_meas_time += this_meas_time; + total_meas_made++; + if (this_meas_time > max_meas_time) { max_meas_time = this_meas_time; } + Serial.print("Warm-up Time: "); + Serial.print(power_delay); + Serial.print(", This measurement time: "); + Serial.print(this_meas_time); + Serial.print(", Mean measurement time: "); + Serial.print(total_meas_time / total_meas_made); + Serial.print(", Max measurement time: "); + Serial.println(max_meas_time); + } + + got_good_results &= this_result_good; + checks_at_time++; + + if (!got_good_results) { + Serial.print("Time check failed after "); + Serial.print(checks_at_time); + Serial.println(" reps"); + total_meas_time = 0; + total_meas_made = 0; + max_meas_time = 0; + } + } + + // if we got a good result 25x at this warm-up, keep testing how long the + // measurements take + while (got_good_results) { + uint32_t this_meas_time = takeMeasurement(sensorAddress, true, "", false); + if (this_meas_time != static_cast(-1)) { + total_meas_time += this_meas_time; + total_meas_made++; + if (this_meas_time > max_meas_time) { max_meas_time = this_meas_time; } + Serial.print("Warm-up Time: "); + Serial.print(power_delay); + Serial.print(", This measurement time: "); + Serial.print(this_meas_time); + Serial.print(", Mean measurement time: "); + Serial.print(total_meas_time / total_meas_made); + Serial.print(", Max measurement time: "); + Serial.println(max_meas_time); + } + } + + + Serial.println("-------------------------------------------------------------------" + "------------"); + if (power_delay > max_power_delay) { + power_delay = min_power_delay; + } else { + power_delay = power_delay + increment_power_delay; + } +} diff --git a/extras/TestWarmUp/TestWarmUp.ino b/extras/TestWarmUp/TestWarmUp.ino new file mode 100644 index 0000000..df734ec --- /dev/null +++ b/extras/TestWarmUp/TestWarmUp.ino @@ -0,0 +1,177 @@ +/** + * @example{lineno} TestWarmUp.ino + * @copyright Stroud Water Research Center + * @license This example is published under the BSD-3 license. + * @author Sara Damiano + * @date March 2021 + */ + +#include + +/* connection information */ +uint32_t serialBaud = 115200; /*!< The baud rate for the output serial port */ +int8_t dataPin = 7; /*!< The pin of the SDI-12 data bus */ +char sensorAddress = '0'; /*!< The address of the SDI-12 sensor */ +int8_t powerPin = 22; /*!< The sensor power pin (or -1 if not switching power) */ + +/** Define the SDI-12 bus */ +SDI12 mySDI12(dataPin); + +/** Define some testing specs */ + +/** Error codes, if returned */ +int8_t error_result_number = 7; +float no_error_value = 0; + +/** Testing turning off power */ +int32_t min_power_delay = 100L; /*!< The min time to test wake after power on. */ +int32_t max_power_delay = 10000L; /*!< The max time to test wake after power on. */ +int32_t increment_power = 100; /*!< The time to lengthen waits between reps. */ + +/** Testing the length of the break */ +int32_t min_wake_delay = 0; /*!< The min time to test wake after a line break. */ +int32_t max_wake_delay = 100; /*!< The max time to test wake (should be <=100). */ +int32_t increment_wake = 5; /*!< The time to lengthen waits between reps. */ + +/** set some initial values */ +int32_t power_delay = min_power_delay; +int32_t wake_delay = min_wake_delay; + +// this checks for activity at a particular address +// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' +bool checkActive(char address, int8_t numPings = 3, bool printCommands = false) { + String command = ""; + command += (char)address; // sends basic 'acknowledge' command [address][!] + command += "!"; + + for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + mySDI12.sendCommand(command, wake_delay); + + // the sensor should just return its address + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + mySDI12.clearBuffer(); + + // check the address, return false if it's incorrect + String returned_address = sdiResponse.substring(0, 1); + char ret_addr_array[2]; + returned_address.toCharArray(ret_addr_array, sizeof(ret_addr_array)); + if (returned_address == String(address)) { return true; } + } + mySDI12.clearBuffer(); + return false; +} + +/** + * @brief gets identification information from a sensor, and prints it to the serial + * port + * + * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. + */ +bool printInfo(char i, bool printCommands = true) { + String command = ""; + command += (char)i; + command += "I!"; + mySDI12.sendCommand(command, wake_delay); + if (printCommands) { + Serial.print(">>>"); + Serial.println(command); + } + delay(100); + + String sdiResponse = mySDI12.readStringUntil('\n'); + sdiResponse.trim(); + // allccccccccmmmmmmvvvxxx...xx + if (printCommands) { + Serial.print("<<<"); + Serial.println(sdiResponse); + } + + Serial.print("Address: "); + Serial.print(sdiResponse.substring(0, 1)); // address + Serial.print(", SDI-12 Version: "); + Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number + Serial.print(", Vendor ID: "); + Serial.print(sdiResponse.substring(3, 11)); // vendor id + Serial.print(", Sensor Model: "); + Serial.print(sdiResponse.substring(11, 17)); // sensor model + Serial.print(", Sensor Version: "); + Serial.print(sdiResponse.substring(17, 20)); // sensor version + Serial.print(", Sensor ID: "); + Serial.print(sdiResponse.substring(20)); // sensor id + Serial.println(); + + if (sdiResponse.length() < 3) { return false; }; + return true; +} + +void setup() { + Serial.begin(serialBaud); + while (!Serial) + ; + + Serial.println("Opening SDI-12 bus..."); + mySDI12.begin(); + delay(500); // allow things to settle + + Serial.println("Timeout value: "); + Serial.println(mySDI12.TIMEOUT); +} + +void loop() { + while (wake_delay <= max_wake_delay) { + // Power the sensors; + if (powerPin >= 0) { + Serial.println("Powering down sensors..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, LOW); + delay(300000L); + } + + // Power the sensors; + if (powerPin >= 0) { + Serial.println("Powering up sensors..."); + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, HIGH); + delay(power_delay); + mySDI12.clearBuffer(); + } + + if (checkActive(sensorAddress, 1, true)) { + Serial.print("Got some response after "); + Serial.print(power_delay); + Serial.print("ms after power with "); + Serial.print(wake_delay); + Serial.println("ms with wake delay"); + if (printInfo(sensorAddress, true)) { + // if we got sensor info, stop + Serial.println("Looks good. Stopping."); + while (1) + ; + } else { + Serial.println("Sensor info not valid!"); + } + } else { + Serial.print("No response after "); + Serial.print(power_delay); + Serial.print("ms after power with "); + Serial.print(wake_delay); + Serial.println("ms with wake delay"); + } + Serial.println("-------------------------------------------------------------------" + "------------"); + } + power_delay = power_delay + increment_power; + if (power_delay > max_power_delay) { + Serial.println("FINISHED!!"); + while (1) {} + } +} diff --git a/tools/powerOn/powerOn.ino b/extras/powerOn/powerOn.ino similarity index 100% rename from tools/powerOn/powerOn.ino rename to extras/powerOn/powerOn.ino diff --git a/extras/print_character_parity/print_character_parity.ino b/extras/print_character_parity/print_character_parity.ino new file mode 100644 index 0000000..e83fd49 --- /dev/null +++ b/extras/print_character_parity/print_character_parity.ino @@ -0,0 +1,109 @@ +#include + +#if defined __AVR__ +#include // optimized parity bit handling +#else +// Added MJB: parity function to replace the one specific for AVR from util/parity.h +// http://graphics.stanford.edu/~seander/bithacks.html#ParityNaive +uint8_t parity_even_bit(uint8_t v) { + uint8_t parity = 0; + while (v) { + parity = !parity; + v = v & (v - 1); + } + return parity; +} +#endif + +void printBIN(uint16_t c, uint8_t padding = 8, bool reverse = false) { + Serial.print("0b"); + for (int8_t b = 0; b < padding; b++) { + if (reverse) { + Serial.print(bitRead(c, b)); + } else { + Serial.print(bitRead(c, padding - 1 - b)); + } + } +} + +void printHEX(uint8_t c) { + Serial.print("0x"); + if (c < 0x10) { Serial.print("0"); } + Serial.print(String(c, HEX)); +} + +// All characters transmitted on the SDI-12 bus must be printable ASCII characters. +// Allowable: " " (space) = 32 = 0x20 -> "~" = 126 = 0x7E +// Exceptions: +// - sensor responses end with a carriage return (0D hex, 13 decimal) and a line feed +// (0A hex, 10 decimal) character +// - 2nd and 3rd character of CRC +// - the contents of data packets returned by the High Volume Binary command +// Total number of allowable characters = 97, plus CRC/BIN unprintables +uint8_t sdi_chars[97]; + +void setup() { + Serial.begin(115200); + while (!Serial) + ; + + // add allowable characters to the array + sdi_chars[0] = '\r'; + sdi_chars[1] = '\n'; + uint8_t j = 2; + for (uint8_t i = 32; i <= 126; i++) { + sdi_chars[j] = i; + j++; + } + Serial.println( + "Character, ASCII (decimal), ASCII (hex), ASCII (binary), Parity, " + "Character with parity, Transmission Order (LSB first), Bit - Start, Bit 0, " + "Bit 1, Bit 2, Bit 3, Bit 4, Bit 5, Bit 6, Bit 7 Parity, Bit + Stop"); + + // print info about the allowable characters + for (uint8_t i = 0; i < 97; i++) { + uint8_t c = sdi_chars[i]; + Serial.print("\""); + if (c == '\r') { + Serial.print("\\r"); + } else if (c == '\n') { + Serial.print("\\n"); + } else if (c == ' ') { + Serial.print("space"); + } else if (c == '"') { + Serial.print("quote"); + } else { + Serial.write(c); + } + Serial.print("\", "); + Serial.print(String(c, DEC)); + Serial.print(", "); + printHEX(c); + Serial.print(", "); + printBIN(c); + + uint8_t parityBit = parity_even_bit(c); // Calculate the parity bit + Serial.print(", "); + Serial.print(parityBit); + uint8_t c_par = c | (parityBit << 7); // Add parity bit to the outgoing character + Serial.print(", "); + printBIN(c_par); + + // print in reverse to show transmission order (transmitted LSB first) + Serial.print(", "); + printBIN(c_par, 8, true); + // print the levels of each bit with the start and stop bits and separated columns + // SDI-12 uses inverse logic, so a 0 bit is HIGH and a 1 bit is LOW + // Start bit is HIGH (equivalent to data bit 0) + // Stop bit is LOW (equivalent to data bit 1) + Serial.print(", 1"); // HIGH start bit + for (int8_t b = 0; b < 8; b++) { + Serial.print(", "); + Serial.print(!bitRead(c_par, b)); // Inverse logic! Use ! + } + Serial.print(", 0"); // LOW stop bit + Serial.println(); + } +} + +void loop() {} diff --git a/library.json b/library.json index 95b25f7..d2fd955 100644 --- a/library.json +++ b/library.json @@ -24,10 +24,7 @@ ], "license": "BSD-3-Clause", "frameworks": "arduino", - "platforms": [ - "atmelavr", - "atmelsam" - ], + "platforms": ["atmelavr", "atmelsam", "espressif8266", "espressif32"], "export": { "exclude": ["doc/*"] }, diff --git a/src/SDI12.cpp b/src/SDI12.cpp index f88fc56..d7c7b0a 100644 --- a/src/SDI12.cpp +++ b/src/SDI12.cpp @@ -1,7 +1,7 @@ /** * @file SDI12.cpp - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team + * @copyright Stroud Water Research Center + * @license This library is published under the BSD-3 license. * @date August 2013 * @author Kevin M.Smith * @@ -59,33 +59,21 @@ SDI12Timer SDI12::sdi12timer; // The size of a bit in microseconds // 1200 baud = 1200 bits/second ~ 833.333 µs/bit -const uint16_t SDI12::bitWidth_micros = (uint16_t)833; +const uint16_t SDI12::bitWidth_micros = static_cast(833); // The required "break" before sending commands, >= 12ms -const uint16_t SDI12::lineBreak_micros = (uint16_t)12300; +const uint16_t SDI12::lineBreak_micros = static_cast(12300); // The required mark before a command or response, >= 8.33ms -const uint16_t SDI12::marking_micros = (uint16_t)8500; +const uint16_t SDI12::marking_micros = static_cast(8500); // the width of a single bit in "ticks" of the cpu clock. -const uint8_t SDI12::txBitWidth = TICKS_PER_BIT; -// A fudge factor to make things work -const uint8_t SDI12::rxWindowWidth = RX_WINDOW_FUDGE; -// The number of bits per tick, shifted by 2^10. -const uint8_t SDI12::bitsPerTick_Q10 = BITS_PER_TICK_Q10; +const sdi12timer_t SDI12::txBitWidth = TICKS_PER_BIT; // A mask waiting for a start bit; 0b11111111 const uint8_t SDI12::WAITING_FOR_START_BIT = 0xFF; -uint16_t SDI12::prevBitTCNT; // previous RX transition in micros -uint8_t SDI12::rxState = WAITING_FOR_START_BIT; // 0: got start bit; >0: bits rcvd -uint8_t SDI12::rxMask; // bit mask for building received character -uint8_t SDI12::rxValue; // character being built - -uint16_t SDI12::mul8x8to16(uint8_t x, uint8_t y) { - return x * y; -} - -uint16_t SDI12::bitTimes(uint8_t dt) { - return mul8x8to16(dt + rxWindowWidth, bitsPerTick_Q10) >> 10; -} +sdi12timer_t SDI12::prevBitTCNT; // previous RX transition in micros +uint8_t SDI12::rxState = WAITING_FOR_START_BIT; // 0: got start bit; >0: bits rcvd +uint8_t SDI12::rxMask; // bit mask for building received character +uint8_t SDI12::rxValue; // character being built /* ================ Buffer Setup ====================================================*/ uint8_t SDI12::_rxBuffer[SDI12_BUFFER_SIZE]; // The Rx buffer @@ -96,12 +84,14 @@ volatile uint8_t SDI12::_rxBufferHead = 0; // index of buff head // reveals the number of characters available in the buffer int SDI12::available() { + SDI12_YIELD() if (_bufferOverflow) return -1; return (_rxBufferTail + SDI12_BUFFER_SIZE - _rxBufferHead) % SDI12_BUFFER_SIZE; } // reveals the next character in the buffer without consuming int SDI12::peek() { + SDI12_YIELD() if (_rxBufferHead == _rxBufferTail) return -1; // Empty buffer? If yes, -1 return _rxBuffer[_rxBufferHead]; // Otherwise, read from "head" } @@ -115,6 +105,7 @@ void SDI12::clearBuffer() { // reads in the next character from the buffer (and moves the index ahead) int SDI12::read() { + SDI12_YIELD() _bufferOverflow = false; // Reading makes room in the buffer if (_rxBufferHead == _rxBufferTail) return -1; // Empty buffer? If yes, -1 uint8_t nextChar = _rxBuffer[_rxBufferHead]; // Otherwise, grab char at head @@ -239,7 +230,7 @@ SDI12::SDI12(int8_t dataPin) { // Destructor SDI12::~SDI12() { setState(SDI12_DISABLED); - if (isActive()) { _activeObject = NULL; } + if (isActive()) { _activeObject = nullptr; } // Set the timer prescalers back to original values // NOTE: This does NOT reset SAMD board pre-scalers! sdi12timer.resetSDI12TimerPrescale(); @@ -247,6 +238,11 @@ SDI12::~SDI12() { // Begin void SDI12::begin() { +#if defined(ESP32) || defined(ESP8266) + // Add and remove a fake interrupt to avoid errors with gpio_install_isr_service + attachInterrupt(digitalPinToInterrupt(_dataPin), nullptr, CHANGE); + detachInterrupt(digitalPinToInterrupt(_dataPin)); +#endif // setState(SDI12_HOLDING); setActive(); // Set up the prescaler as needed for timers @@ -264,7 +260,6 @@ void SDI12::end() { setState(SDI12_DISABLED); _activeObject = nullptr; // Set the timer prescalers back to original values - // NOTE: This does NOT reset SAMD board pre-scalers! sdi12timer.resetSDI12TimerPrescale(); } @@ -319,14 +314,7 @@ uint8_t SDI12::parity_even_bit(uint8_t v) { // a helper function to switch pin interrupts on or off void SDI12::setPinInterrupts(bool enable) { -#if defined(ARDUINO_ARCH_SAMD) || defined(ESP32) || defined(ESP8266) - // Merely need to attach the interrupt function to the pin - if (enable) attachInterrupt(digitalPinToInterrupt(_dataPin), handleInterrupt, CHANGE); - // Merely need to detach the interrupt function from the pin - else - detachInterrupt(digitalPinToInterrupt(_dataPin)); - -#elif defined(__AVR__) && not defined(SDI12_EXTERNAL_PCINT) +#if defined(__AVR__) && not defined(SDI12_EXTERNAL_PCINT) if (enable) { // Enable interrupts on the register with the pin of interest *digitalPinToPCICR(_dataPin) |= (1 << digitalPinToPCICRbit(_dataPin)); @@ -344,46 +332,60 @@ void SDI12::setPinInterrupts(bool enable) { } // We don't detach the function from the interrupt for AVR processors } -#else + // if using AVR with external interrupts, do nothing +#elif defined(__AVR__) if (enable) { return; } else { return; } +// for other boards (SAMD/Espressif/??) use the attachInterrupt function +#else + // Merely need to attach the interrupt function to the pin + if (enable) attachInterrupt(digitalPinToInterrupt(_dataPin), handleInterrupt, CHANGE); + // Merely need to detach the interrupt function from the pin + else + detachInterrupt(digitalPinToInterrupt(_dataPin)); #endif } // sets the state of the SDI-12 object. void SDI12::setState(SDI12_STATES state) { switch (state) { - case SDI12_HOLDING: { - pinMode(_dataPin, INPUT); // Turn off the pull-up resistor - pinMode(_dataPin, OUTPUT); // Pin mode = output - digitalWrite(_dataPin, LOW); // Pin state = low - marking - setPinInterrupts(false); // Interrupts disabled on data pin - break; - } - case SDI12_TRANSMITTING: { - pinMode(_dataPin, INPUT); // Turn off the pull-up resistor - pinMode(_dataPin, OUTPUT); // Pin mode = output - setPinInterrupts(false); // Interrupts disabled on data pin - break; - } - case SDI12_LISTENING: { - digitalWrite(_dataPin, LOW); // Pin state = low (turns off pull-up) - pinMode(_dataPin, INPUT); // Pin mode = input, pull-up resistor off - interrupts(); // Enable general interrupts - setPinInterrupts(true); // Enable Rx interrupts on data pin - rxState = WAITING_FOR_START_BIT; - break; - } + case SDI12_HOLDING: + { + pinMode(_dataPin, INPUT); // Turn off the pull-up resistor + pinMode(_dataPin, OUTPUT); // Pin mode = output + digitalWrite(_dataPin, LOW); // Pin state = low - marking + setPinInterrupts(false); // Interrupts disabled on data pin + break; + } + case SDI12_TRANSMITTING: + { + pinMode(_dataPin, INPUT); // Turn off the pull-up resistor + pinMode(_dataPin, OUTPUT); // Pin mode = output + setPinInterrupts(false); // Interrupts disabled on data pin +#ifdef SDI12_CHECK_PARITY + _parityFailure = false; // reset the parity failure flag +#endif + break; + } + case SDI12_LISTENING: + { + digitalWrite(_dataPin, LOW); // Pin state = low (turns off pull-up) + pinMode(_dataPin, INPUT); // Pin mode = input, pull-up resistor off + interrupts(); // Enable general interrupts + setPinInterrupts(true); // Enable Rx interrupts on data pin + rxState = WAITING_FOR_START_BIT; + break; + } default: // SDI12_DISABLED or SDI12_ENABLED - { - digitalWrite(_dataPin, LOW); // Pin state = low (turns off pull-up) - pinMode(_dataPin, INPUT); // Pin mode = input, pull-up resistor off - setPinInterrupts(false); // Interrupts disabled on data pin - break; - } + { + digitalWrite(_dataPin, LOW); // Pin state = low (turns off pull-up) + pinMode(_dataPin, INPUT); // Pin mode = input, pull-up resistor off + setPinInterrupts(false); // Interrupts disabled on data pin + break; + } } } @@ -398,7 +400,8 @@ void SDI12::forceListen() { } /* ================ Waking Up and Talking To Sensors ================================*/ -// this function wakes up the entire sensor bus +// this function wakes up the entire sensor bus by sending a 12ms break followed by 8.33 +// ms of marking void SDI12::wakeSensors(int8_t extraWakeTime) { setState(SDI12_TRANSMITTING); // Universal interrupts can be on while the break and marking happen because @@ -416,7 +419,27 @@ void SDI12::writeChar(uint8_t outChar) { uint8_t currentTxBitNum = 0; // first bit is start bit uint8_t bitValue = 1; // start bit is HIGH (inverse parity...) - noInterrupts(); // _ALL_ interrupts disabled so timing can't be shifted + // The tolerance on all SDI-12 commands is 0.40ms = 400µs. But... that's for between + // commands, and we don't know how accurate all sensors are, so we probably don't want + // to be off by more than 1/10 of that between bits. + + // Let's assume an interrupt routine can take up 1000 clock cycles. I don't know if + // that's reasonable, but per + // https://forum.arduino.cc/t/how-many-clock-cycles-does-digitalread-write-take/467153 + // a single digitalWrite function takes up 50 clock cycles so 20x that seems like a + // safe buffer. Our own SDI-12 receive ISR takes up roughly 617 clock cycles on a + // Mayfly. [Calculated using a modified version of + // https://github.com/SRGDamia1/avrcycles.] For the a 1000 clock cycle interrupt + // to not shift timing by more than 400µs the clock must have more than 40,000,000 + // cycles in one second (40MHz). For any board slower than 40MHz, we'll, disable _ALL_ + // interrupts during sending so timing can't be shifted. For faster boards, we + // can probably safely leave interrupts on. Disabling interrupts can screw up build-in + // functions like micros(), millis() and any real-time clocks, so we don't want to + // disable them if we don't really have to. + +#if F_CPU < 48000000UL + noInterrupts(); // _ALL_ interrupts disabled +#endif sdi12timer_t t0 = READTIME; // start time @@ -426,12 +449,18 @@ void SDI12::writeChar(uint8_t outChar) { // this gives us 833µs to calculate parity and position of last high bit currentTxBitNum++; + // Calculate parity, while writing the start bit + // This takes about 24 clock cycles on an AVR board (at 8MHz, that's 3µsec) + uint8_t parityBit = parity_even_bit(outChar); // Calculate the parity bit outChar |= (parityBit << 7); // Add parity bit to the outgoing character // Calculate the position of the last bit that is a 0/HIGH (ie, HIGH, not marking) // That bit will be the last time-critical bit. All bits after that can be // sent with interrupts enabled. + // This calculation should also finish while writing the start bit + // This takes at least 10+13 clock cycles, and up to 10+(13*9)= 127 clock cycles (at + // 8MHz, that's 15.875µsec) uint8_t lastHighBit = 9; // The position of the last bit that is a 0 (ie, HIGH, not marking) @@ -442,8 +471,10 @@ void SDI12::writeChar(uint8_t outChar) { } // Hold the line for the rest of the start bit duration + // We've used up roughly 150 clock cycles messing with parity, but a bit is 833µs, so + // we've got time. - while ((uint8_t)(READTIME - t0) < txBitWidth) {} + while (static_cast(READTIME - t0) < txBitWidth) {} t0 = READTIME; // advance start time // repeat for all data bits until the last bit different from marking @@ -455,8 +486,8 @@ void SDI12::writeChar(uint8_t outChar) { digitalWrite(_dataPin, HIGH); // set the pin state to HIGH for 0's } // Hold the line for this bit duration - while ((uint8_t)(READTIME - t0) < txBitWidth) {} - t0 = READTIME; // start time + while (static_cast(READTIME - t0) < txBitWidth) {} + t0 = READTIME; // advance start time outChar = outChar >> 1; // shift character to expose the following bit } @@ -464,11 +495,13 @@ void SDI12::writeChar(uint8_t outChar) { // Set the line low for the all remaining 1's and the stop bit digitalWrite(_dataPin, LOW); +#if F_CPU < 48000000UL interrupts(); // Re-enable universal interrupts as soon as critical timing is past +#endif // Hold the line low until the end of the 10th bit - uint8_t bitTimeRemaining = txBitWidth * (10 - lastHighBit); - while ((uint8_t)(READTIME - t0) < bitTimeRemaining) {} + sdi12timer_t bitTimeRemaining = txBitWidth * (10 - lastHighBit); + while (static_cast(READTIME - t0) < bitTimeRemaining) {} } // The typical write functionality for a stream object @@ -511,27 +544,41 @@ void SDI12::sendCommand(FlashString cmd, int8_t extraWakeTime) { // marking and then sending out the characters of resp one by one (for slave-side use, // that is, when the Arduino itself is acting as an SDI-12 device rather than a // recorder). -void SDI12::sendResponse(String& resp) { +void SDI12::sendResponse(String& resp, bool addCRC) { setState(SDI12_TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); // marking is LOW delayMicroseconds(marking_micros); // 8.33 ms marking before response for (int unsigned i = 0; i < resp.length(); i++) { writeChar(resp[i]); // write each character } + // tack on the CRC if requested + if (addCRC) { + String crc = crcToString(calculateCRC(resp)); + for (int unsigned i = 0; i < 3; i++) { + writeChar(crc[i]); // write each character + } + } setState(SDI12_LISTENING); // return to listening state } -void SDI12::sendResponse(const char* resp) { +void SDI12::sendResponse(const char* resp, bool addCRC) { setState(SDI12_TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); // marking is LOW delayMicroseconds(marking_micros); // 8.33 ms marking before response for (int unsigned i = 0; i < strlen(resp); i++) { writeChar(resp[i]); // write each character } + // tack on the CRC if requested + if (addCRC) { + String crc = crcToString(calculateCRC(resp)); + for (int unsigned i = 0; i < 3; i++) { + writeChar(crc[i]); // write each character + } + } setState(SDI12_LISTENING); // return to listening state } -void SDI12::sendResponse(FlashString resp) { +void SDI12::sendResponse(FlashString resp, bool addCRC) { setState(SDI12_TRANSMITTING); // Get ready to send data to the recorder digitalWrite(_dataPin, LOW); // marking is LOW delayMicroseconds(marking_micros); // 8.33 ms marking before response @@ -539,35 +586,130 @@ void SDI12::sendResponse(FlashString resp) { // write each character writeChar(static_cast(pgm_read_byte((const char*)resp + i))); } + // tack on the CRC if requested + if (addCRC) { + String crc = crcToString(calculateCRC(resp)); + for (int unsigned i = 0; i < 3; i++) { + writeChar(crc[i]); // write each character + } + } setState(SDI12_LISTENING); // return to listening state } +/** + * @brief The polynomial to match the CRC with; set in the SDI-12 specifications + */ +#define POLY 0xa001 + +uint16_t SDI12::calculateCRC(String& resp) { + uint16_t crc = 0; + + for (uint16_t i = 0; i < resp.length(); i++) { + crc ^= static_cast( + resp[i]); // Set the CRC equal to the exclusive OR of the character and itself + for (int j = 0; j < 8; j++) { // count = 1 to 8 + if (crc & 0x0001) { // if the least significant bit of the CRC is one + crc >>= 1; // right shift the CRC one bit + crc ^= + POLY; // set CRC equal to the exclusive OR of the match polynomial and itself + } else { + crc >>= 1; // right shift the CRC one bit + } + } + } + return crc; +} + +uint16_t SDI12::calculateCRC(const char* resp) { + uint16_t crc = 0; -/* ================ Interrupt Service Routine =======================================*/ + for (size_t i = 0; i < strlen(resp); i++) { + crc ^= static_cast( + resp[i]); // Set the CRC equal to the exclusive OR of the character and itself + for (int j = 0; j < 8; j++) { // count = 1 to 8 + if (crc & 0x0001) { // if the least significant bit of the CRC is one + crc >>= 1; // right shift the CRC one bit + crc ^= POLY; // set CRC equal to the exclusive OR of POLY and itself + } else { + crc >>= 1; // right shift the CRC one bit + } + } + } + return crc; +} -// Passes off responsibility for the interrupt to the active object. -// On espressif boards (ESP8266 and ESP32), the ISR must be stored in IRAM -#if defined(ESP32) || defined(ESP8266) -void ICACHE_RAM_ATTR SDI12::handleInterrupt() { - if (_activeObject) _activeObject->receiveISR(); +uint16_t SDI12::calculateCRC(FlashString resp) { + uint16_t crc = 0; + char responsechar; + + for (size_t i = 0; i < strlen_P((PGM_P)resp); i++) { + responsechar = (char)pgm_read_byte((const char*)resp + i); + crc ^= static_cast(responsechar); // Set the CRC equal to the exclusive + // OR of the character and itself + for (int j = 0; j < 8; j++) { // count = 1 to 8 + if (crc & 0x0001) { // if the least significant bit of the CRC is one + crc >>= 1; // right shift the CRC one bit + crc ^= POLY; // set CRC equal to the exclusive OR of POLY and itself + } else { + crc >>= 1; // right shift the CRC one bit + } + } + } + return crc; } -#else -void SDI12::handleInterrupt() { + +String SDI12::crcToString(uint16_t crc) { + char crcStr[3] = {0}; + crcStr[0] = (char)(0x0040 | (crc >> 12)); + crcStr[1] = (char)(0x0040 | ((crc >> 6) & 0x003F)); + crcStr[2] = (char)(0x0040 | (crc & 0x003F)); + return (String(crcStr[0]) + String(crcStr[1]) + String(crcStr[2])); +} + +bool SDI12::verifyCRC(String& respWithCRC) { + // trim trailing \r and \n ( and ) + respWithCRC.trim(); + uint16_t nChar = + respWithCRC.length(); // number of characters without (readable string composed of + // sensor address, values separated by + and -) and the 3 + // characters of the CRC + String recCRC = ""; // the CRC portion of the response + String recString = ""; // the data portion of the response + + // extract the data portion of the string + for (uint16_t i = 0; i < (nChar - 3); i++) recString += respWithCRC[i]; + + // extract the last 3 characters that are the CRC from the full response string + for (uint16_t i = (nChar - 3); i < nChar; i++) recCRC += respWithCRC[i]; + + // calculate the CRC for the data portion + String calcCRC = crcToString(calculateCRC(recString)); + + if (recCRC == calcCRC) { + return true; + } else { + return false; + } +} + +/* ================ Interrupt Service Routine =======================================*/ + +// 7.1 - Passes off responsibility for the interrupt to the active object. +void ISR_MEM_ACCESS SDI12::handleInterrupt() { if (_activeObject) _activeObject->receiveISR(); } -#endif -// Creates a blank slate of bits for an incoming character -void SDI12::startChar() { +// 7.2 - Creates a blank slate of bits for an incoming character +void ISR_MEM_ACCESS SDI12::startChar() { rxState = 0x00; // 0b00000000, got a start bit rxMask = 0x01; // 0b00000001, bit mask, lsb first rxValue = 0x00; // 0b00000000, RX character to be, a blank slate } // startChar -// The actual interrupt service routine -void SDI12::receiveISR() { - // time of this data transition (plus ISR latency) - sdi12timer_t thisBitTCNT = READTIME; +// 7.3 - The actual interrupt service routine +void ISR_MEM_ACCESS SDI12::receiveISR() { + sdi12timer_t thisBitTCNT = + READTIME; // time of this data transition (plus ISR latency) uint8_t pinLevel = digitalRead(_dataPin); // current RX data level @@ -586,7 +728,7 @@ void SDI12::receiveISR() { // data, parity, or stop bit. // Check how many bit times have passed since the last change - uint16_t rxBits = bitTimes((uint8_t)(thisBitTCNT - prevBitTCNT)); + uint16_t rxBits = SDI12Timer::bitTimes(thisBitTCNT - prevBitTCNT); // Calculate how many *data+parity* bits should be left in the current character // - Each character has a total of 10 bits, 1 start bit, 7 data bits, 1 parity // bit, and 1 stop bit @@ -659,16 +801,25 @@ void SDI12::receiveISR() { } // If this was the 8th or more bit then the character and parity are complete. + // The stop bit may still be outstanding if (rxState > 7) { +#ifdef SDI12_CHECK_PARITY + uint8_t rxParity = bitRead(rxValue, 7); // pull out the parity bit +#endif rxValue &= 0x7F; // Throw away the parity bit (and with 0b01111111) charToBuffer(rxValue); // Put the finished character into the buffer - +#ifdef SDI12_CHECK_PARITY + uint8_t checkParity = + parity_even_bit(rxValue); // Calculate the parity bit from character w/o parity + if (rxParity != checkParity) { _parityFailure = true; } +#endif // if this is LOW, or we haven't exceeded the number of bits in a // character (but have gotten all the data bits) then this should be a // stop bit and we can start looking for a new start bit. if ((pinLevel == LOW) || !nextCharStarted) { - rxState = WAITING_FOR_START_BIT; // DISABLE STOP BIT TIMER + rxState = + WAITING_FOR_START_BIT; // reset the rx state, stop waiting for stop bit } else { // If we just switched to HIGH, or we've exceeded the total number of // bits in a character, then the character must have ended with 1's/LOW, diff --git a/src/SDI12.h b/src/SDI12.h index 06eb7ab..fe42161 100644 --- a/src/SDI12.h +++ b/src/SDI12.h @@ -1,7 +1,7 @@ /** * @file SDI12.h - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team + * @copyright Stroud Water Research Center + * @license This library is published under the BSD-3 license. * @date August 2013 * @author Kevin M.Smith * @@ -126,6 +126,7 @@ #define SRC_SDI12_H_ // Import Required Libraries + #include // integer types library #include // Arduino core library #include // Arduino Stream library @@ -137,6 +138,13 @@ typedef const __FlashStringHelper* FlashString; /// a char not found in a valid ASCII numeric field #define NO_IGNORE_CHAR '\x01' +#ifndef SDI12_IGNORE_PARITY +/** + * @brief Check the value of the parity bit on reception + */ +#define SDI12_CHECK_PARITY +#endif + #ifndef SDI12_WAKE_DELAY /** * @brief The amount of additional time in milliseconds that the sensor takes to wake @@ -161,6 +169,36 @@ typedef const __FlashStringHelper* FlashString; #define SDI12_BUFFER_SIZE 81 #endif +#ifndef SDI12_YIELD_MS +/** + * @brief The time to delay, in milliseconds, to allow the buffer to fill before + * returning the value from the buffer. + * + * This may be needed for faster processors to account for the slow baud rate of SDI-12. + * Without this, the available() function may return 0 while we're in the middle of + * reading a character. + * + * There are 8.33 ms/character, so we delay by 8ms for fast processors to allow one + * character to finish. + */ +#if F_CPU >= 48000000L +#define SDI12_YIELD_MS 8 +#else +#define SDI12_YIELD_MS 0 +#endif +#endif + +#ifndef SDI12_YIELD +/** + * @brief A delay function to allow the buffer to fill before returning the value from + * the buffer. + * + * This may be needed for faster processors to account for the slow baud rate of SDI-12. + */ +#define SDI12_YIELD() \ + { delay(SDI12_YIELD_MS); } +#endif + #if defined(ESP32) || defined(ESP8266) /** * @brief This enumeration provides the lookahead options for parseInt(), parseFloat(). @@ -180,22 +218,6 @@ enum LookaheadMode { /** Only tabs, spaces, line feeds & carriage returns are skipped.*/ SKIP_WHITESPACE }; -/** - * @brief The function or macro used to read the clock timer value. - * - * @note The ESP32 and ESP8266 are fast enough processors that they can take the - * time to read the core 'micros()' function still complete the other processing needed - * on the serial bits. All of the other processors using the Arduino core also have the - * micros function, but the rest are not fast enough to waste the processor cycles to - * use the micros function and must use the faster assembly macros to read the - * processor timer directly. - */ -#define READTIME sdi12timer.SDI12TimerRead() -#else -/** - * @brief The function or macro used to read the clock timer value. - */ -#define READTIME TCNTX #endif // defined(ESP32) || defined(ESP8266) /** @@ -237,15 +259,7 @@ class SDI12 : public Stream { /** * @brief the width of a single bit in "ticks" of the cpu clock. */ - static const uint8_t txBitWidth; - /** - * @brief A fudge factor to make things work - */ - static const uint8_t rxWindowWidth; - /** - * @brief The number of bits per tick, shifted by 2^10. - */ - static const uint8_t bitsPerTick_Q10; + static const sdi12timer_t txBitWidth; /** * @brief A mask for the #rxState while waiting for a start bit; 0b11111111 */ @@ -254,12 +268,24 @@ class SDI12 : public Stream { /** * @brief Stores the time of the previous RX transition in micros */ - static uint16_t prevBitTCNT; + static sdi12timer_t prevBitTCNT; /** * @brief Tracks how many bits are accounted for on an incoming character. * * - if 0: indicates that we got a start bit * - if >0: indicates the number of bits received + * + * 0 - got start bit + * 1 - got data bit 0 + * 2 - got data bit 1 + * 3 - got data bit 2 + * 4 - got data bit 3 + * 5 - got data bit 4 + * 6 - got data bit 5 + * 7 - got data bit 6 + * 8 - got data bit 7 (parity) + * 9 - got stop bit + * 255 - waiting for next start bit */ static uint8_t rxState; /** @@ -273,35 +299,6 @@ class SDI12 : public Stream { * @brief the value of the character being built */ static uint8_t rxValue; - - /** - * @brief static method for getting a 16-bit value from the multiplication of 2 8-bit - * values - * - * @param x The first 8 bit integer - * @param y The second 8 bit integer - * @return @m_span{m-type} uint16_t @m_endspan The result of the multiplication, as a - * 16 bit integer. - */ - static uint16_t mul8x8to16(uint8_t x, uint8_t y); - - /** - * @brief static method for calculating the number of bit-times that have elapsed - * given an 8-bit counter/timer timestamp. - * - * @param dt The current value of the 8-bit timer - * @return @m_span{m-type} uint16_t @m_endspan The number of bit times that have - * passed at 1200 baud. - * - * Adds a rxWindowWidth fudge factor to the time difference to get the number of - * ticks, and then multiplies the fudged ticks by the number of bits per tick. Uses - * the number of bits per tick shifted up by 2^10 and then shifts the result down by - * the same amount to compensate for the fact that the number of bits per tick is a - * decimal the timestamp is only an 8-bit integer. - * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13#issuecomment-315463522 - */ - static uint16_t bitTimes(uint8_t dt); /**@}*/ @@ -377,7 +374,7 @@ class SDI12 : public Stream { /** * @brief Return the number of bytes available in the Rx buffer * - * @return @m_span{m-type} int @m_endspan The number of characters in the buffer + * @return The number of characters in the buffer * * available() is a public function that returns the number of characters available in * the Rx buffer. @@ -421,7 +418,7 @@ class SDI12 : public Stream { /** * @brief Reveal next byte in the Rx buffer without consuming it. * - * @return @m_span{m-type} int @m_endspan The next byte in the character buffer. + * @return The next byte in the character buffer. * * peek() is a public function that allows the user to look at the character that is * at the head of the buffer. Unlike read() it does not consume the character (i.e. @@ -439,7 +436,7 @@ class SDI12 : public Stream { /** * @brief Return next byte in the Rx buffer, consuming it * - * @return @m_span{m-type} int @m_endspan The next byte in the character buffer. + * @return The next byte in the character buffer. * * read() returns the character at the current head in the buffer after incrementing * the index of the buffer head. This action 'consumes' the character, meaning it can @@ -465,7 +462,7 @@ class SDI12 : public Stream { * @param lookahead the mode to use to look ahead in the * stream, default is LookaheadMode::SKIP_ALL * @param ignore a character to ignore in the stream, default is '\\x01' - * @return @m_span{m-type} long @m_endspan The first valid integer in the stream + * @return The first valid integer in the stream * * @note This function _hides_ the Stream class function to allow a custom value to be * returned on timeout. It cannot overwrite the Stream function because it is not @@ -486,7 +483,7 @@ class SDI12 : public Stream { * @param lookahead the mode to use to look ahead in the * stream, default is LookaheadMode::SKIP_ALL * @param ignore a character to ignore in the stream, default is '\\x01' - * @return @m_span{m-type} long @m_endspan The first valid float in the stream + * @return The first valid float in the stream * * @note This function _hides_ the Stream class function to allow a custom value to be * returned on timeout. It cannot overwrite the Stream function because it is not @@ -503,7 +500,7 @@ class SDI12 : public Stream { * stream * @param detectDecimal True to accept a decimal point ('.') as part of a * number - * @return @m_span{m-type} int @m_endspan The next numeric digit in the stream + * @return The next numeric digit in the stream */ int peekNextDigit(LookaheadMode lookahead, bool detectDecimal); /**@}*/ @@ -612,7 +609,7 @@ class SDI12 : public Stream { /** * @brief Get the data pin for the current SDI-12 instance * - * @return @m_span{m-type} int8_t @m_endspan the data pin number + * @return The data pin number */ int8_t getDataPin(); /** @@ -621,6 +618,9 @@ class SDI12 : public Stream { * @param dataPin The data pin's digital pin number */ void setDataPin(int8_t dataPin); +#ifdef SDI12_CHECK_PARITY + bool _parityFailure; +#endif /**@}*/ @@ -659,7 +659,7 @@ class SDI12 : public Stream { /** * @brief Set this instance as the active SDI-12 instance * - * @return @m_span{m-type} bool @m_endspan True indicates that the current SDI-12 + * @return True indicates that the current SDI-12 * instance was not formerly the active one and now is. False indicates that the * current SDI-12 instance *is already the active one* and the state was not changed. * @@ -675,7 +675,7 @@ class SDI12 : public Stream { /** * @brief Check if this instance is active * - * @return @m_span{m-type} bool @m_endspan True indicates that the curren SDI-12 + * @return True indicates that the curren SDI-12 * instace is the active one. * * isActive() is a method for checking if the object is the active object. Returns @@ -763,7 +763,7 @@ class SDI12 : public Stream { * @brief Calculate the parity value for a character using even parity. * * @param v **uint8_t (char)** the character to calculate the parity of - * @return @m_span{m-type} uint8_t @m_endspan the input character with the 8th bit set + * @return The input character with the 8th bit set * to the even parity value for that character * * Sets up parity and interrupts for different processor types - that is, imports the @@ -883,7 +883,7 @@ class SDI12 : public Stream { * @brief Write out a byte on the SDI-12 line * * @param byte The character to write - * @return @m_span{m-type} size_t @m_endspan The number of characters written + * @return The number of characters written * * Sets the state to transmitting, writes a character, and then sets the state back to * listening. This function must be implemented as part of the Arduino Stream @@ -911,23 +911,61 @@ class SDI12 : public Stream { /// @copydoc SDI12::sendCommand(String&, int8_t) void sendCommand(FlashString cmd, int8_t extraWakeTime = SDI12_WAKE_DELAY); + /** + * @brief Calculates the 16-bit Cyclic Redundancy Check (CRC) for an SDI-12 message. + * + * @param resp The message to calculate the CRC for. + * @return *uint16_t* The calculated CRC + */ + uint16_t calculateCRC(String& resp); + /// @copydoc SDI12::calculateCRC(String&) + uint16_t calculateCRC(const char* resp); + /// @copydoc SDI12::calculateCRC(String&) + uint16_t calculateCRC(FlashString resp); + + /** + * @brief Converts a numeric 16-bit CRC to an ASCII String. + * + * From the SDI-12 Specifications: + * + * The 16 bit CRC is encoded as three ASCII characters + * using the following algorithm: + * 1st character = 0x40 OR (CRC shifted right 12 bits) + * 2nd character = 0x40 OR ((CRC shifted right 6 bits) AND 0x3F) + * 3rd character = 0x40 OR (CRC AND 0x3F) + * + * @param crc The 16-bit CRC + * @return *String* An ASCII string for the CRC + */ + String crcToString(uint16_t crc); + + /** + * @brief Verifies that the CRC returned at the end of an SDI-12 message matches that + * of the content of the message. + * + * @param respWithCRC The full SDI-12 message, including the CRC at the end. + * @return True if the CRC matches and the message is valid, false if the CRC doesn't + * match and the message could be retried. + */ + bool verifyCRC(String& respWithCRC); + /** * @brief Send a response out on the data line (for slave use) * * @param resp the response to send + * @param addCRC True to append a CRC to the outgoing response * * A publicly accessible function that sends out an 8.33 ms marking and a response * byte by byte on the data line. This is needed if the Arduino is acting as an * SDI-12 device itself, not as a recorder for another SDI-12 device. */ - void sendResponse(String& resp); - /// @copydoc SDI12::sendResponse(String& resp) - void sendResponse(const char* resp); - /// @copydoc SDI12::sendResponse(String& resp) - void sendResponse(FlashString resp); + void sendResponse(String& resp, bool addCRC = false); + /// @copydoc SDI12::sendResponse(String& resp, bool addCRC) + void sendResponse(const char* resp, bool addCRC = false); + /// @copydoc SDI12::sendResponse(String& resp, bool addCRC) + void sendResponse(FlashString resp, bool addCRC = false); ///@} - /** * @anchor interrupt_fxns * @name Interrupt Service Routine diff --git a/src/SDI12_boards.cpp b/src/SDI12_boards.cpp index beb28e8..13cd648 100644 --- a/src/SDI12_boards.cpp +++ b/src/SDI12_boards.cpp @@ -1,7 +1,7 @@ /** * @file SDI12_boards.cpp - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team + * @copyright Stroud Water Research Center + * @license This library is published under the BSD-3 license. * @author Sara Geleskie Damiano (sdamiano@stroudcenter.org) * * @brief This file implements the setting and unsetting of the proper prescalers for @@ -18,8 +18,30 @@ sensors. This library provides a general software solution, without requiring SDI12Timer::SDI12Timer() {} +uint16_t SDI12Timer::mul8x8to16(uint8_t x, uint8_t y) { + return x * y; +} + +// Using an 8-bit timer, we need to do fanciness to get proper 16 bit results +#if TIMER_INT_SIZE == 8 +uint16_t SDI12Timer::bitTimes(sdi12timer_t dt) { + // multiply the time delta in ticks by the bits per tick + return mul8x8to16(dt + RX_WINDOW_FUDGE, BITS_PER_TICK_Q10) >> 10; +} + +// But nothing fancy for bigger timers +#elif TIMER_INT_SIZE == 16 || TIMER_INT_SIZE == 32 +uint16_t SDI12Timer::bitTimes(sdi12timer_t dt) { + // divide the number of ticks by the ticks per bit + return static_cast((dt + static_cast(RX_WINDOW_FUDGE)) / + static_cast(TICKS_PER_BIT)); +} +#else +#error "Board timer is incorrectly configured!" +#endif + + // Most 'standard' AVR boards -// #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || \ defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || \ defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || \ @@ -34,104 +56,69 @@ static uint8_t preSDI12_TCCR2A; */ static uint8_t preSDI12_TCCR2B; -#if F_CPU == 16000000L +sdi12timer_t SDI12Timer::SDI12TimerRead(void) { + return TCNT2; +} void SDI12Timer::configSDI12TimerPrescale(void) { preSDI12_TCCR2A = TCCR2A; preSDI12_TCCR2B = TCCR2B; +#if F_CPU == 16000000L TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & // OC2B disconnected TCCR2B = 0x07; // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - // prescaler set to CK/1024 -} -void SDI12Timer::resetSDI12TimerPrescale(void) { - TCCR2A = preSDI12_TCCR2A; - TCCR2B = preSDI12_TCCR2B; -} - #elif F_CPU == 12000000L - -void SDI12Timer::configSDI12TimerPrescale(void) { - preSDI12_TCCR2A = TCCR2A; - preSDI12_TCCR2B = TCCR2B; TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & // OC2B disconnected TCCR2B = 0x07; // TCCR2B = 0x07 = 0b00000111 - Clock Select bits 22, 21, & 20 on - // prescaler set to CK/1024 -} -void SDI12Timer::resetSDI12TimerPrescale(void) { - TCCR2A = preSDI12_TCCR2A; - TCCR2B = preSDI12_TCCR2B; -} - #elif F_CPU == 8000000L - -void SDI12Timer::configSDI12TimerPrescale(void) { - preSDI12_TCCR2A = TCCR2A; - preSDI12_TCCR2B = TCCR2B; TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, OC2A & // OC2B disconnected TCCR2B = 0x06; // TCCR2B = 0x06 = 0b00000110 - Clock Select bits 22 & 20 on - // prescaler set to CK/256 +#endif } + void SDI12Timer::resetSDI12TimerPrescale(void) { TCCR2A = preSDI12_TCCR2A; TCCR2B = preSDI12_TCCR2B; } -// void SDI12Timer::configSDI12TimerPrescale(void) -// { -// preSDI12_TCCR2A = TCCR2A; -// preSDI12_TCCR2B = TCCR2B; -// TCCR2A = 0x00; // TCCR2A = 0x00 = "normal" operation - Normal port operation, -// OC2A & OC2B disconnected TCCR2B = 0x07; // TCCR2B = 0x07 = 0b00000111 - Clock -// Select bits 22, 21, & 20 on - prescaler set to CK/1024 -// } -// void SDI12Timer::resetSDI12TimerPrescale(void) -// { -// TCCR2A = preSDI12_TCCR2A; -// TCCR2B = preSDI12_TCCR2B; -// } -#endif - - // ATtiny boards (ie, adafruit trinket) -// #elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) +sdi12timer_t SDI12Timer::SDI12TimerRead(void) { + return TCNT1; +} + /** * @brief The value of timer control register 1A prior to being set for SDI-12. */ static uint8_t preSDI12_TCCR1A; -#if F_CPU == 16000000L - void SDI12Timer::configSDI12TimerPrescale(void) { preSDI12_TCCR1A = TCCR1; - TCCR1 = 0b00001011; // Set the prescaler to 1024 -} -void SDI12Timer::resetSDI12TimerPrescale(void) { - TCCR1 = preSDI12_TCCR1A; -} - - +#if F_CPU == 16000000L + TCCR1 = 0b00001011; // Set the prescaler to 1024 #elif F_CPU == 8000000L - -void SDI12Timer::configSDI12TimerPrescale(void) { - preSDI12_TCCR1A = TCCR1; TCCR1 = 0b00001010; // Set the prescaler to 512 +#endif } + void SDI12Timer::resetSDI12TimerPrescale(void) { TCCR1 = preSDI12_TCCR1A; } -#endif - // Arduino Leonardo & Yun and other 32U4 boards -// #elif defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_LEONARDO) || \ defined(__AVR_ATmega32U4__) +sdi12timer_t SDI12Timer::SDI12TimerRead(void) { + return TCNT4; +} + /** * @brief The value of timer control register 4A prior to being set for SDI-12. */ @@ -153,8 +140,6 @@ static uint8_t preSDI12_TCCR4D; */ static uint8_t preSDI12_TCCR4E; -#if F_CPU == 16000000L - void SDI12Timer::configSDI12TimerPrescale(void) { preSDI12_TCCR4A = TCCR4A; preSDI12_TCCR4B = TCCR4B; @@ -163,37 +148,19 @@ void SDI12Timer::configSDI12TimerPrescale(void) { preSDI12_TCCR4E = TCCR4E; TCCR4A = 0x00; // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & // OC4B disconnected +#if F_CPU == 16000000L TCCR4B = 0x0B; // TCCR4B = 0x0B = 0b00001011 - Clock Select bits 43, 41, & 40 on - // prescaler set to CK/1024 - TCCR4C = 0x00; // TCCR4C = 0x00 = "normal" operation - Normal port operation, OC4D0 - // disconnected - TCCR4D = 0x00; // TCCR4D = 0x00 = No fault protection - TCCR4E = 0x00; // TCCR4E = 0x00 = No register locks or overrides -} -void SDI12Timer::resetSDI12TimerPrescale(void) { - TCCR4A = preSDI12_TCCR4A; - TCCR4B = preSDI12_TCCR4B; - TCCR4C = preSDI12_TCCR4C; - TCCR4D = preSDI12_TCCR4D; - TCCR4E = preSDI12_TCCR4E; -} - #elif F_CPU == 8000000L -void SDI12Timer::configSDI12TimerPrescale(void) { - preSDI12_TCCR4A = TCCR4A; - preSDI12_TCCR4B = TCCR4B; - preSDI12_TCCR4C = TCCR4C; - preSDI12_TCCR4D = TCCR4D; - preSDI12_TCCR4E = TCCR4E; - TCCR4A = 0x00; // TCCR4A = 0x00 = "normal" operation - Normal port operation, OC4A & - // OC4B disconnected TCCR4B = 0x0A; // TCCR4B = 0x0A = 0b00001010 - Clock Select bits 43 & 41 on - // prescaler set to CK/512 +#endif TCCR4C = 0x00; // TCCR4C = 0x00 = "normal" operation - Normal port operation, OC4D0 // disconnected TCCR4D = 0x00; // TCCR4D = 0x00 = No fault protection TCCR4E = 0x00; // TCCR4E = 0x00 = No register locks or overrides } + void SDI12Timer::resetSDI12TimerPrescale(void) { TCCR4A = preSDI12_TCCR4A; TCCR4B = preSDI12_TCCR4B; @@ -201,78 +168,355 @@ void SDI12Timer::resetSDI12TimerPrescale(void) { TCCR4D = preSDI12_TCCR4D; TCCR4E = preSDI12_TCCR4E; } -#endif - // Arduino Zero other SAMD21 boards -// -#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || \ - defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__SAMD21E18A__) +#elif defined(ARDUINO_SAMD_ZERO) || defined(__SAMD21G18A__) || \ + defined(__SAMD21J18A__) || defined(__SAMD21E18A__) + +/// Fully reset the TC to factory settings and disable it +static inline void resetTC(Tc* TCx) { + // Disable TCx + TCx->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; + while (TCx->COUNT16.STATUS.bit.SYNCBUSY) + ; + + // Reset TCx + TCx->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; + while (TCx->COUNT16.STATUS.bit.SYNCBUSY) + ; + while (TCx->COUNT16.CTRLA.bit.SWRST) + ; +} + +/** + * @brief The value of generic clock generator divider register prior to being set for + * SDI-12. + * + * This is an 32-bit register located at 0x40000C00 + 0x8 + */ +static uint32_t preSDI12_REG_GCLK_GENDIV; +/** + * @brief The value of generic clock _generator_ control register prior to being set for + * SDI-12. + * + * This is an 32-bit register located at 0x40000C00 + 0x4 + */ +static uint32_t preSDI12_REG_GCLK_GENCTRL; +/** + * @brief The value of generic clock control register prior to being set for + * SDI-12. + * + * This is an 32-bit register located at 0x40000C00 + 0x2 + */ +static uint8_t preSDI12_REG_GCLK_CLKCTRL; + +sdi12timer_t SDI12Timer::SDI12TimerRead(void) { + return REG_TC3_COUNT8_COUNT; +} void SDI12Timer::configSDI12TimerPrescale(void) { - // Select generic clock generator 4 (Arduino core uses 0, 1, and 3. RTCZero uses 2) - // Many examples use clock generator 4.. consider yourself warned! - // I would use a higher clock number, but some of the cores don't include them for - // some reason + // read control register values prior to changes + preSDI12_REG_GCLK_GENDIV = REG_GCLK_GENDIV; + preSDI12_REG_GCLK_GENCTRL = REG_GCLK_GENCTRL; + preSDI12_REG_GCLK_CLKCTRL = REG_GCLK_CLKCTRL; + + // Set up the generic clock generator divisor register + // NOTE: Could write the below as GCLK->GENDIV.reg instead of REG_GCLK_GENDIV REG_GCLK_GENDIV = GCLK_GENDIV_ID(4) | // Select Generic Clock Generator 4 - GCLK_GENDIV_DIV(3); // Divide the clock source by divisor 3 - while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization + GCLK_GENDIV_DIV(6); // Divide the clock source by divisor 6 + while (GCLK->STATUS.bit.SYNCBUSY) + ; + ; // Wait for synchronization - // Write the generic clock generator 4 configuration + // Set up the generic clock generator control register + // NOTE: Could write the below as GCLK->GENCTRL.reg instead ofREG_GCLK_GENCTRL REG_GCLK_GENCTRL = (GCLK_GENCTRL_ID(4) | // Select GCLK4 GCLK_GENCTRL_SRC_DFLL48M | // Select the 48MHz clock source GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW GCLK_GENCTRL_GENEN) & // Enable the generic clock clontrol ~GCLK_GENCTRL_RUNSTDBY & // Do NOT run in stand by - ~GCLK_GENCTRL_DIVSEL; // Divide clock source by GENDIV.DIV: 48MHz/3=16MHz - // ^^ & ~ for DIVSEL because not not divided - while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization - - // Feed GCLK4 to TC3 (also feeds to TCC2, the two must have the same source) - // TC3 (and TCC2) seem to be free, so I'm using them - // TC4 is used by Tone, TC5 is tied to the same clock as TC4 - // TC6 and TC7 are not available on all boards - REG_GCLK_CLKCTRL = GCLK_CLKCTRL_GEN_GCLK4 | // Select Generic Clock Generator 4 + ~GCLK_GENCTRL_DIVSEL; // Divide clock source by GENDIV.DIV: 48MHz/5=9.6MHz + // ^^ & ~ for DIVSEL to set DIVSEL to 0 + while (GCLK->STATUS.bit.SYNCBUSY) + ; + ; // Wait for synchronization + + // Set up the generic clock control register + // NOTE: Could write the below as GCLK->CLKCTRL.reg instead of REG_GCLK_CLKCTRL + // Feed GCLK4 to TC3 (also feeds to TCC2, the two must have the same source). + // TC3 (and TCC2) seem to be free, so I'm using them. + // TC4 is used by Tone and Servo, TC5 is tied to the same clock as TC4. + // TC6 and TC7 are not available on all boards. + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_GEN_GCLK4 | // Select Generic Clock Generator + // 4 GCLK_CLKCTRL_CLKEN | // Enable the generic clock generator GCLK_CLKCTRL_ID_TCC2_TC3; // Feed the Generic Clock Generator 4 to TCC2 and TC3 - while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization + while (GCLK->STATUS.bit.SYNCBUSY) + ; + ; // Wait for synchronization + + // fully software reset and disable the TC before we start messing with it + resetTC(SDI12_TC); + + // Set up the control register for Timer Controller 3 REG_TC3_CTRLA |= - TC_CTRLA_PRESCALER_DIV1024 | // Set prescaler to 1024, 16MHz/1024 = 15.625kHz - TC_CTRLA_WAVEGEN_NFRQ | // Put the timer TC3 into normal frequency (NFRQ) mode - TC_CTRLA_MODE_COUNT8 | // Put the timer TC3 into 8-bit mode - TC_CTRLA_ENABLE; // Enable TC3 - while (TC3->COUNT16.STATUS.bit.SYNCBUSY) {} // Wait for synchronization + TC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 9.6MHz/16 = 600kHz + TC_CTRLA_WAVEGEN_NFRQ | // Put the timer TC3 into normal frequency (NFRQ) mode + TC_CTRLA_MODE_COUNT16 | // Put the timer TC3 into 16-bit mode + TC_CTRLA_ENABLE; // Enable TC3 + while (SDI12_TC->COUNT16.STATUS.bit.SYNCBUSY) + ; // Wait for synchronization } -// NOT resetting the SAMD timer settings + void SDI12Timer::resetSDI12TimerPrescale(void) { - // Disable TCx - TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; - while (TC3->COUNT16.STATUS.bit.SYNCBUSY) {} + // reset the generic clock generator divisor register + REG_GCLK_GENDIV = preSDI12_REG_GCLK_GENDIV; + while (GCLK->STATUS.bit.SYNCBUSY) + ; // Wait for synchronization + + // reset the generic clock generator control register + REG_GCLK_GENCTRL = preSDI12_REG_GCLK_GENCTRL; + while (GCLK->STATUS.bit.SYNCBUSY) + ; // Wait for synchronization + + // reset the generic clock control register + REG_GCLK_CLKCTRL = preSDI12_REG_GCLK_CLKCTRL; + while (GCLK->STATUS.bit.SYNCBUSY) + ; // Wait for synchronization + + // fully software reset the control register for Timer Controller 3 and then disable + // it + resetTC(SDI12_TC); +} - // Reset TCx - TC3->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; - while (TC3->COUNT16.STATUS.bit.SYNCBUSY) {} - while (TC3->COUNT16.CTRLA.bit.SWRST) {} - - // Disable generic clock generator - REG_GCLK_GENCTRL = GCLK_GENCTRL_ID(4) & // Select GCLK4 - ~GCLK_GENCTRL_GENEN; // Disable the generic clock control - while (GCLK->STATUS.bit.SYNCBUSY) {} // Wait for synchronization +// SAMD51 and SAME51 boards +#elif defined(__SAMD51__) || defined(__SAME51__) + +/// Fully reset the TC to factory settings and disable it +static inline void resetTC(Tc* TCx) { + // Disable TCx + TCx->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; // unset enable bit + while (TCx->COUNT16.SYNCBUSY.bit.ENABLE) + ; // wait for enable sync busy bit to clear + + // Reset TCx with SWRST (Software Reset) bit + // - Writing a '0' to this bit has no effect. + // - Writing a '1' to this bit resets all registers in the TC, except DBGCTRL, to + // their initial state, and the TC will be disabled. + // - Writing a '1' to CTRLA.SWRST will always take precedence; all other writes in the + // same write-operation will be discarded. + + TCx->COUNT16.CTRLA.reg = TC_CTRLA_SWRST; + while (TCx->COUNT16.SYNCBUSY.bit.SWRST) + ; // wait for software reset busy bit to clear } -// Espressif ESP32/ESP8266 boards -// -#elif defined(ESP32) || defined(ESP8266) +/** + * @brief The value of generic clock _generator_ control register prior to being set for + * SDI-12. + * + * This is an 32-bit register offset from the GCLK register by 0x20 + n*0x04 [n=0..11], + * where n is the generic clock number. + */ +static uint32_t preSDI12_REG_GCLK_GENCTRL; +/** + * @brief The value of generic clock peripheral control channel register prior to being + * set for SDI-12. + * + * This is an 32-bit register offset from the GCLK register by 0x80 + m*0x04 [m=0..47], + * where m is the peripheral channel number. + * + * @see docs/SAMD51PeripheralClocks.dox for a list of the peripheral channel numbers. + */ +static uint32_t preSDI12_REG_GCLK_PCHCTRL; -void SDI12Timer::configSDI12TimerPrescale(void) {} -void SDI12Timer::resetSDI12TimerPrescale(void) {} sdi12timer_t SDI12Timer::SDI12TimerRead(void) { - // Its a one microsecond clock but we want 64uS ticks so divide by 64 i.e. right shift - // 6 - return ((sdi12timer_t)(micros() >> 6)); + // Note from datasheet: Prior to any read access, this register must be synchronized + // by user by writing the according TC Command value to the Control B Set register + // (CTRLBSET.CMD=READSYNC) + // see: + // https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-13/GUID-5033DFD7-EB2D-4870-AE98-D40CADB0531E.html + + // Code taken from Microchip article on how to read the tiemr value + // https://microchip.my.site.com/s/article/SAM-D5x-E5x--Reading-TC-TCC-COUNT-register + + + // write READSYNC command to the Control B Set register + SDI12_TC->COUNT16.CTRLBSET.reg = TC_CTRLBSET_CMD_READSYNC; + + // wait for the CMD bits in CTRLBSET to be cleared, meaning the CMD has been executed + while (SDI12_TC->COUNT16.CTRLBSET.reg & TC_CTRLBSET_CMD_READSYNC) + ; + + // read the COUNT register + return SDI12_TC->COUNT16.COUNT.reg; } + +void SDI12Timer::configSDI12TimerPrescale(void) { + // read the values of the registers prior to making changes + preSDI12_REG_GCLK_GENCTRL = GCLK->GENCTRL[GENERIC_CLOCK_GENERATOR_SDI12].reg; + preSDI12_REG_GCLK_PCHCTRL = GCLK->PCHCTRL[SDI12_TC_GCLK_ID].reg; + + // Calculate the new value for the generic clock _generator_ control register + uint32_t postSDI12_REG_GCLK_GENCTRL = + (GCLK_GENCTRL_DIV( + 15) & // Bits 31:16 – DIV[15:0] Division Factor + // ^^ These bits represent a division value for the corresponding + // Generator. The actual division factor is dependent on the state of + // DIVSEL. The number of relevant DIV bits for each Generator can be seen + // in this table. Written bits outside of the specified range will be + // ignored. See datasheet table 14-6 + // ((0xFFFFU << 16) & ((15) << 16)) = 0b00000000000011110000000000000000 + // Bits 15:14 are reserved + ~GCLK_GENCTRL_RUNSTDBY & // Bit 13 – RUNSTDBY Run in Standby + // ^^ This bit is used to keep the Generator running in Standby as long as it is + // configured to output to a dedicated GCLK_IOn pin. If GENCTRLn.OE is zero, this + // bit has no effect and the generator will only be running if a peripheral + // requires the clock. + // (0x1U << 13) = ~0b00000000000000000010000000000000 = + // 0b11111111111111111101111111111111 + // For SDI-12, we do *not* run in standby + ~GCLK_GENCTRL_DIVSEL & // Bit 12 – DIVSEL Divide Selection + // ^^ This bit determines how the division factor of the clock source of the + // Generator will be calculated from DIV. If the clock source should not be + // divided, DIVSEL must be 0 and the GENCTRLn.DIV value must be either 0 or 1. + // (0x1U << 12) = ~0b00000000000000000001000000000000 + // 0b11111111111111111110111111111111 + // For SDI-12, we set this to 0 to divide by the value in the Division Factor bits + // (ie, 512) + ~GCLK_GENCTRL_OE) | // Bit 11 – OE Output Enable + // ^^ This bit is used to output the Generator clock output to the corresponding pin + // (GCLK_IO[7..0]), as long as GCLK_IOn is not defined as the Generator source in + // the GENCTRLn.SRC bit field. + // (0x1U << 11) = ~0b00000000000000000000100000000000 + // 0b11111111111111111111011111111111 + // For SDI-12, we don't need to enable output + // GCLK_GENCTRL_OOV | // Bit 10 – OOV Output Off Value + // ^^ This bit is used to control the clock output value on pin (GCLK_IO[7..0]) when + // the Generator is turned off or the OE bit is zero, as long as GCLK_IOn is not + // defined as the Generator source in the GENCTRLn.SRC bit field. + // (0x1U << 10) = ~0b00000000000000000000010000000000 + // 0b11111111111111111111101111111111 + // For SDI-12, we don't need to enable output or have an output value + (GCLK_GENCTRL_IDC | // Bit 9 = Improve Duty Cycle + // ^^ This bit is used to improve the duty cycle of the + // Generator output to 50/50 for odd division factors. For + // SDI-12, set the generator output clock duty cycle to 50/50 + // (0x1U << 9) = 0b00000000000000000000001000000000 + GCLK_GENCTRL_GENEN | // Bit 8 Generator Enable + // ^^ This bit is used to enable and disable the Generator. + // (0x1U << 8) = ~0b00000000000000000000000100000000 + // Enable the generator! + // Bits 7:4 are reserved + GCLK_GENCTRL_SRC( + GCLK_GENCTRL_SRC_DPLL0) // Bits 3:0 Generator Clock Source Selection + // ^^ These bits select the Generator clock source, as shown in this table. (See + // datasheet table 14-3) + // MAIN_CLOCK_SOURCE = GCLK_GENCTRL_SRC_DPLL0 = 120 MHz primary clock + // (0x7U << 0) = 0b00000000000000000000000000000111 + // GCLK_GENCTRL_SRC_OSCULP32K = 32 kHz Ultra Low Power Internal Oscillator + // (OSCULP32K) + // (0x3U << 0) = 0b00000000000000000000000000000011 + // GCLK_GENCTRL_SRC_XOSC32K = 32 kHz External Crystal Oscillator (XOSC32K) + // (0x5U << 0) = 0b00000000000000000000000000000101 + ); + + // Set the generator control register for the clock generator for the selected clock + // generator source + GCLK->GENCTRL[GENERIC_CLOCK_GENERATOR_SDI12].reg = postSDI12_REG_GCLK_GENCTRL; + while (GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_SDI12) + ; // Wait for the SDI-12 clock generator sync busy bit to clear + + // Calculate the new value for the generic clock peripheral control channel register. + uint8_t postSDI12_REG_GCLK_PCHCTRL = + // Bit 7 – WRTLOCK Write Lock + // ^^ After this bit is set to '1', further writes to the PCHCTRLm register will + // be discarded. The control register of the corresponding Generator n (GENCTRLn), + // as assigned in PCHCTRLm.GEN, will also be locked. It can only be unlocked by a + // Power Reset. For SDI-12, ignore this bit + GCLK_PCHCTRL_CHEN | + // Bit 6 – CHEN Channel Enable + // ^^ This bit is used to enable and disable a Peripheral Channel. + // We're enabling the channel + // Bits 5:4 are reserved + GENERIC_CLOCK_GENERATOR_SDI12 + // Bits 3:0 – GEN[3:0] Generator Selection + // ^^ This bit field selects the Generator to be used as the source of a peripheral + // clock, as shown in the table 14-7 of the datasheet + // For SDI-12, we select generic clock generator 6 + ; + + // Set the generic clock peripheral control channel register + GCLK->PCHCTRL[SDI12_TC_GCLK_ID].reg = postSDI12_REG_GCLK_PCHCTRL; + while (!GCLK->PCHCTRL[SDI12_TC_GCLK_ID].bit.CHEN) + ; // wait to finish enabling + + // fully software reset and disable the TC before we start messing with it + resetTC(SDI12_TC); + + // Set timer counter mode as normal PWM + SDI12_TC->COUNT16.WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val; + // NOTE: This register isn't synced, so no wait needed + + // Set the timer to count up + // >> This bit is used to change the direction of the counter. + // >> Writing a '0' to this bit has no effect + // >> Writing a '1' to this bit will clear the bit and make the counter count up. + // SRGD Note: Writing to 1 actually flips the direction from whatever it was, not + // reset it to up + if (SDI12_TC->COUNT16.CTRLBSET.bit + .DIR) { // check the current direction first (0=counting up, 1=counting down) + SDI12_TC->COUNT16.CTRLBSET.bit.DIR = 1; + while (SDI12_TC->COUNT16.SYNCBUSY.bit.CTRLB) + ; // wait the control B sync busy bit to clear + } + + // configure the control register for the timer control + uint32_t postSDI12_REG_TC_CTRLA = + ~TC_CTRLA_CAPTEN0 & // Disable capture for channel 0 (use for compare) + ~TC_CTRLA_CAPTEN1 & // Disable capture for channel 1 (use for compare) + ~TC_CTRLA_RUNSTDBY & // Disable run on standby + (TC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16 + TCC_CTRLA_PRESCSYNC_GCLK | // Reload or reset the counter on next generic clock + TC_CTRLA_MODE_COUNT16 | // Put the timer TC3 into 16-bit mode + TC_CTRLA_ENABLE // Enable TC3 + ); + + SDI12_TC->COUNT16.CTRLA.reg = postSDI12_REG_TC_CTRLA; + while (SDI12_TC->COUNT16.SYNCBUSY.bit.ENABLE) + ; // wait for to finish enabling +} + +void SDI12Timer::resetSDI12TimerPrescale(void) { + // Reset the generator control register for the clock generator + GCLK->GENCTRL[GENERIC_CLOCK_GENERATOR_SDI12].reg = preSDI12_REG_GCLK_GENCTRL; + while (GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_SDI12) + ; // Wait for the SDI-12 clock generator sync busy bit to clear + + // Reset the generic clock peripheral control channel register + GCLK->PCHCTRL[SDI12_TC_GCLK_ID].reg = preSDI12_REG_GCLK_PCHCTRL; + while (!GCLK->PCHCTRL[SDI12_TC_GCLK_ID].bit.CHEN) + ; // wait to finish enabling ?? + + // fully software reset the control register for SDI-12 Timer Controller and then + // disable it + resetTC(SDI12_TC); +} + +// Espressif ESP32/ESP8266 boards or other boards faster than 48MHz +// WARNING: I haven't tested the minimum speed that this will work at! +#elif defined(ESP32) || defined(ESP8266) || F_CPU >= 48000000L + +void SDI12Timer::configSDI12TimerPrescale(void) {} + +void SDI12Timer::resetSDI12TimerPrescale(void) {} + +sdi12timer_t ISR_MEM_ACCESS SDI12Timer::SDI12TimerRead(void) { + return (static_cast(micros())); +} + // Unknown board #else #error "Please define your board timer and pins" diff --git a/src/SDI12_boards.h b/src/SDI12_boards.h index d91eff4..ccc654b 100644 --- a/src/SDI12_boards.h +++ b/src/SDI12_boards.h @@ -1,7 +1,7 @@ /** * @file SDI12_boards.h - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team + * @copyright Stroud Water Research Center + * @license This library is published under the BSD-3 license. * @author Sara Geleskie Damiano (sdamiano@stroudcenter.org) * * @brief This file defines the timing units needed for various Arduino boards. @@ -17,502 +17,341 @@ sensors. This library provides a general software solution, without requiring #define SRC_SDI12_BOARDS_H_ #include - -#if defined(ESP32) || defined(ESP8266) -/** The interger type (size) of the timer return value */ -typedef uint32_t sdi12timer_t; -#else -/** The interger type (size) of the timer return value */ -typedef uint8_t sdi12timer_t; -#endif - /** - * @brief The class used to define the processor timer for the SDI-12 serial emulation. + * @def ISR_MEM_ACCESS + * @brief Defines a memory access location, if needed for the interrupts service + * routines. + * + * On espressif boards (ESP8266 and ESP32), the ISR must be stored in IRAM */ -class SDI12Timer { - public: - /** - * @brief Construct a new SDI12Timer - */ - SDI12Timer(); - /** - * @brief Set the processor timer prescaler such that the 10 bits of an SDI-12 - * character are divided into the rollover time of the timer. - * - * @note The ESP32 and ESP8266 are fast enough processors that they can take the - * time to read the core 'micros()' function still complete the other processing - * needed on the serial bits. All of the other processors using the Arduino core also - * have the micros function, but the rest are not fast enough to waste the processor - * cycles to use the micros function and must manually configure the processor timer - * and use the faster assembly macros to read that processor timer directly. - */ - void configSDI12TimerPrescale(void); - /** - * @brief Reset the processor timer prescaler to whatever it was prior to being - * adjusted for this library. - * - * @note The prescaler is *NOT* set back to initial values for SAMD boards! On those - * processors, generic clock generator 4 will remain configured for SDI-12 until it is - * reset outside of this library. - */ - void resetSDI12TimerPrescale(void); +#if defined(ESP32) || defined(ESP8266) +#define ISR_MEM_ACCESS IRAM_ATTR +#else +#define ISR_MEM_ACCESS +#endif // defined(ESP32) || defined(ESP8266) -// Most 'standard' AVR boards -// -#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || \ - defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || \ - defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || \ - defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) /** + * @def TIMER_IN_USE_STR * @brief A string description of the timer to use * - * Timer/Counter2 (TC2) is a general purpose, single channel, 8-bit Timer/Counter - * module. + * @def TIMER_INT_TYPE + * @brief The interger type of the timer. * - * Features - * - Single Channel Counter - * - Clear Timer on Compare Match (Auto Reload) - * - Glitch-free, Phase Correct Pulse Width Modulator (PWM) - * - Frequency Generator - * - 10-bit Clock Prescaler - * - Overflow and Compare Match Interrupt Sources (TOV2, OCF2A, and OCF2B) - * - Allows Clocking from External 32kHz Watch Crystal Independent of the I/O Clock - */ -#define TIMER_IN_USE_STR "TCNT2" -/** - * @brief The c macro name for the assembly timer to use + * @def TIMER_INT_SIZE + * @brief The size in bits of the timer count value. * - * The register used to access the timer/counter value is TCNT2 - */ -#define TCNTX TCNT2 // Using Timer 2 - -#if F_CPU == 16000000L -/** - * @brief A string description of the prescaler in use. - */ -#define PRESCALE_IN_USE_STR "1024" -/** - * @brief The number of "ticks" of the timer that occur within the timing of one bit at - * the SDI-12 baud rate of 1200 bits/second. - * - * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit - * - * The 8-bit timer rolls over after 256 ticks, 19.66085 bits, or 16.38505 ms - * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66085 bits - * (256 ticks/roll-over) * (1 sec/15624 ticks) = 16.38505 milliseconds - */ -#define TICKS_PER_BIT 13 -/** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. - * - * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 - */ -#define BITS_PER_TICK_Q10 79 -/** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. + * @def READTIME + * @brief The function or macro used to read the clock timer value. * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 - */ -#define RX_WINDOW_FUDGE 2 - -#elif F_CPU == 12000000L -/** + * @def PRESCALE_IN_USE_STR * @brief A string description of the prescaler in use. - */ -#define PRESCALE_IN_USE_STR "1024" -/** - * @brief The number of "ticks" of the timer that occur within the timing of one bit at - * the SDI-12 baud rate of 1200 bits/second. - * - * 12MHz / 1024 prescaler = 11719 'ticks'/sec = 85 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/85 µs) = 9.765625 ticks/bit - * - * The 8-bit timer rolls over after 256 ticks, 26.2144 bits, or 21.84487 ms - * (256 ticks/roll-over) * (1 bit/9.765625 ticks) = 26.2144 bits - * (256 ticks/roll-over) * (1 sec/11719 ticks) = 21.84487 milliseconds - */ -#define TICKS_PER_BIT 10 -/** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. * - * 1/(9.765625 ticks/bit) * 2^10 = 104.8576 - */ -#define BITS_PER_TICK_Q10 105 -/** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. + * @def TICKS_PER_SECOND + * @brief The number of clock ticks per second, after accounting for the prescaler. * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 - */ -#define RX_WINDOW_FUDGE 2 - -#elif F_CPU == 8000000L -/** - * @brief A string description of the prescaler in use. - */ -#define PRESCALE_IN_USE_STR "256" -/** + * @def TICKS_PER_BIT * @brief The number of "ticks" of the timer that occur within the timing of one bit at * the SDI-12 baud rate of 1200 bits/second. * - * 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit - * - * The 8-bit timer rolls over after 256 ticks, 9.8304 bits, or 8.192 ms - * (256 ticks/roll-over) * (1 bit/26.04166667 ticks) = 9.8304 bits - * (256 ticks/roll-over) * (1 sec/31250 ticks) = 8.192 milliseconds - * @note The timer will roll-over with each character! - */ -#define TICKS_PER_BIT 26 -/** + * @def BITS_PER_TICK_Q10 * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. * - * 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 - */ -#define BITS_PER_TICK_Q10 39 -/** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that + * @def RX_WINDOW_FUDGE + * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that * uneven tick increments get rounded up. * * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 */ -#define RX_WINDOW_FUDGE 10 - // #define PRESCALE_IN_USE_STR "1024" - // #define TICKS_PER_BIT 6 - // // 8MHz / 1024 prescaler = 31250 'ticks'/sec = 128 µs / 'tick' - // // (1 sec/1200 bits) * (1 tick/128 µs) = 6.5104166667 ticks/bit - // #define BITS_PER_TICK_Q10 157 - // // 1/(6.5104166667 ticks/bit) * 2^10 = 157.2864 - // #define RX_WINDOW_FUDGE 5 -#endif +// Most 'standard' AVR boards +#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || \ + defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || \ + defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || \ + defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + +// Use Timer/Counter 2 on most AVR boards +#define TIMER_IN_USE_STR "Timer2" +// Timer 2 on AtMega boards is an 8-bit timer +#define TIMER_INT_TYPE uint8_t +#define TIMER_INT_SIZE 8 +#define READTIME TCNT2 + +#if F_CPU == 16000000L +#define PRESCALE_IN_USE_STR "16MHz/1024=15.625kHz" +// 16MHz / 1024 prescaler = 15625 'ticks'/sec = 64 µs / 'tick' +#define TICKS_PER_SECOND 15625 + +#elif F_CPU == 12000000L +#define PRESCALE_IN_USE_STR "12MHz/1024=11.7kHz" +// 12MHz / 1024 prescaler = 11719 'ticks'/sec = 85.33 µs / 'tick' +#define TICKS_PER_SECOND 11719 + +#elif F_CPU == 8000000L +#define PRESCALE_IN_USE_STR "8MHz/256=31.25kHz" +// 8MHz / 256 prescaler = 31250 'ticks'/sec = 32 µs / 'tick' +#define TICKS_PER_SECOND 31250 + +#endif // F_CPU // ATtiny boards (ie, adafruit trinket) -// #elif defined(__AVR_ATtiny25__) | defined(__AVR_ATtiny45__) | defined(__AVR_ATtiny85__) -/** - * @brief A string description of the timer to use - * - * The Timer/Counter1 features a high resolution and a high accuracy usage with the - * lower prescaling opportunities. It can also support two accurate, high speed, 8-bit - * pulse width modulators using clock speeds up to 64MHz (or 32MHz in low speedmode). - */ -#define TIMER_IN_USE_STR "TCNT1" -/** - * @brief The c macro name for the assembly timer to use - * - * The register used to access the timer/counter value is TCNT1. - */ -#define TCNTX TCNT1 // Using Timer 1 +// On the ATTiny boards, we use Timer/Counter 1 +#define TIMER_IN_USE_STR "Timer1" +// Timer 1 on the ATTiny boards is an 8-bit timer +#define TIMER_INT_TYPE uint8_t +#define TIMER_INT_SIZE 8 +#define READTIME TCNT1 #if F_CPU == 16000000L -/** - * @brief A string description of the prescaler in use. - */ -#define PRESCALE_IN_USE_STR "1024" -/** - * @brief The number of "ticks" of the timer that occur within the timing of one bit at - * the SDI-12 baud rate of 1200 bits/second. - * - * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit - * - * The 8-bit timer rolls over after 256 ticks, 19.66 bits, or 16.38505 ms - * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66 bits - * (256 ticks/roll-over) * (1 sec/15624 ticks) = 16.38505 milliseconds - */ -#define TICKS_PER_BIT 13 -/** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. - * - * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 - */ -#define BITS_PER_TICK_Q10 79 -/** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. - * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 - */ -#define RX_WINDOW_FUDGE 2 +#define PRESCALE_IN_USE_STR "16MHz/1024=15.625kHz" +// 16MHz / 1024 prescaler = 15625 'ticks'/sec = 15.625 kHz = 64 µs / 'tick' +#define TICKS_PER_SECOND 15625 #elif F_CPU == 8000000L -#define PRESCALE_IN_USE_STR "512" -/** - * @brief The number of "ticks" of the timer that occur within the timing of one bit at - * the SDI-12 baud rate of 1200 bits/second. - * - * 8MHz / 512 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit - * - * The 8-bit timer rolls over after 256 ticks, 19.66 bits, or 16.38505 ms - * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66 bits - * (256 ticks/roll-over) * (1 sec/15624 ticks) = 16.38505 milliseconds - */ -#define TICKS_PER_BIT 13 -/** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. - * - * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 - */ -#define BITS_PER_TICK_Q10 79 -/** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. - * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 - */ -#define RX_WINDOW_FUDGE 5 +#define PRESCALE_IN_USE_STR "8MHz/512=15.625kHz" +// 8MHz / 512 prescaler = 15625 'ticks'/sec = 15.625 kHz = 64 µs / 'tick' +#define TICKS_PER_SECOND 15625 -#endif +#endif // F_CPU // Arduino Leonardo & Yun and other 32U4 boards -// #elif defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_LEONARDO) || \ defined(__AVR_ATmega32U4__) +// On the AtMega16U4 and AtMega32U4, we use Timer/Counter 4 as an 8-bit timer. +#define TIMER_IN_USE_STR "Timer4" /** - * @brief A string description of the timer to use - * - * Timer/Counter4 is a general purpose high speed Timer/Counter module, with three - * independent Output Compare Units, and with enhanced PWM support. - * - * Features - * - Up to 10-Bit Accuracy - * - Three Independent Output Compare Units - * - Clear Timer on Compare Match (Auto Reload) - * - Glitch Free, Phase and Frequency Correct Pulse Width Modulator (PWM) - * - Enhanced PWM mode: one optional additional accuracy bit without effect on output - * frequency - * - Variable PWM Period - * - Independent Dead Time Generators for each PWM channels - * - Synchronous update of PWM registers - * - Five Independent Interrupt Sources (TOV4, OCF4A, OCF4B, OCF4D, FPF4) - * - High Speed Asynchronous and Synchronous Clocking Modes - * - Separate Prescaler Unit - */ -#define TIMER_IN_USE_STR "TCNT4" -/** - * @brief The c macro name for the assembly timer to use - * - * The register used to access the low byte timer/counter value is TCNT4. - * @note We only utilize the low byte register, effectively using the 10-bit timer as an - * 8-bit timer. + * Timer 4 on the U4 series is an 10-bit timer, but we're only using the lower 8 bits */ -#define TCNTX TCNT4 // Using Timer 4 +#define TIMER_INT_TYPE uint8_t +#define TIMER_INT_SIZE 8 +#define READTIME TCNT4 #if F_CPU == 16000000L +#define PRESCALE_IN_USE_STR "16MHz/1024=15.625kHz" +// 16MHz / 1024 prescaler = 15625 'ticks'/sec = 64 µs / 'tick' +#define TICKS_PER_SECOND 15625 + +#elif F_CPU == 8000000L +#define PRESCALE_IN_USE_STR "8MHz/512=15.625kHz" +// 8MHz / 512 prescaler = 15625 'ticks'/sec = 64 µs / 'tick' +#define TICKS_PER_SECOND 15625 + +#endif // F_CPU + + +// Arduino Zero other SAMD21 boards +#elif defined(ARDUINO_SAMD_ZERO) || defined(__SAMD21G18A__) || \ + defined(__SAMD21J18A__) || defined(__SAMD21E18A__) + +// For SDI-12, we'll use Generic Clock Generator 4 and Timer Controller 3 +#define TIMER_IN_USE_STR "GCLK4-TC3" +// We're using the timer in 16-bit mode +#define TIMER_INT_TYPE uint16_t +#define TIMER_INT_SIZE 16 + +/// The timer controller to use +#define SDI12_TC TC3 + +// This signifies the register of timer/counter 3, the 16-bit count, the count value +// This is equivalent to TC3->COUNT16.COUNT.reg +#define READTIME REG_TC3_COUNT16_COUNT + +#define PRESCALE_IN_USE_STR "48MHz/6/16=500kHz" +// Start with 48MHz "main" clock source (GCLK_GENCTRL_SRC_DFLL48M) +// 48MHz / 6x clock source divider (GCLK_GENDIV_DIV(6)) = 8MHz +// 8MHz / 16x prescaler (TC_CTRLA_PRESCALER_DIV16) = 500kHz = 500,000 'ticks'/sec +#define TICKS_PER_SECOND 500000 + + +// SAMD51 and SAME51 boards +#elif defined(__SAMD51__) || defined(__SAME51__) + +// For SDI-12, we'll use Generic Clock Generator 6 and Timer Controller 2 +#define TIMER_IN_USE_STR "GCLK6-TC2" +// We're using the timer in 16-bit mode +#define TIMER_INT_TYPE uint16_t +#define TIMER_INT_SIZE 16 + +/// The clock generator number to use +#define GENERIC_CLOCK_GENERATOR_SDI12 (6u) +/// The bit to check for synchronization +#define GCLK_SYNCBUSY_SDI12 GCLK_SYNCBUSY_GENCTRL6 +/// The timer controller to use +#define SDI12_TC TC2 +// The peripheral index within the generic clock for the selected timer controller +#define SDI12_TC_GCLK_ID TC2_GCLK_ID + +// For the SAMD51, reading the timer is a multi-step process of first writing a read +// sync bit, waiting, and then reading the register. Because of the steps, we need a +// function. +#define READTIME sdi12timer.SDI12TimerRead() + +#define PRESCALE_IN_USE_STR "120MHz/15/16=500kHz" +// Start with 120MHz "main" clock source (MAIN_CLOCK_SOURCE = GCLK_GENCTRL_SRC_DPLL0) +// 120MHz / 15x clock source divider (GCLK_GENCTRL_DIV(15)) = 8MHz +// 8MHz / 16x prescaler (TC_CTRLA_PRESCALER_DIV16) = 500kHz +// 500,000 'ticks'/sec = 2 µs / 'tick' (1 sec/1200 bits) * (1 tick/2 µs) = 416.66667 +// ticks/bit +#define TICKS_PER_SECOND 500000 + +// Espressif ESP32/ESP8266 boards or any boards faster than 48MHz not mentioned above +// WARNING: I haven't actually tested the minimum speed that this will work at! +#elif defined(ESP32) || defined(ESP8266) || F_CPU >= 48000000L + +// Using the micros() function +#define TIMER_IN_USE_STR "micros()" +// Since we're using `micros()`, this is 32 bit +#define TIMER_INT_TYPE uint32_t +#define TIMER_INT_SIZE 32 +#define READTIME sdi12timer.SDI12TimerRead() +// Since we're using micros() each 'tick' is 1µs +#define TICKS_PER_SECOND 1000000 + +// Unknown board +#else +#error "Please define your board timer and prescaler!" +#endif + + +#if TICKS_PER_SECOND == 15625 && TIMER_INT_SIZE == 8 /** - * @brief A string description of the prescaler in use. - */ -#define PRESCALE_IN_USE_STR "1024" -/** - * @brief The number of "ticks" of the timer that occur within the timing of one bit at - * the SDI-12 baud rate of 1200 bits/second. - * - * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' + * 15625 'ticks'/sec = 64 µs / 'tick' * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit * - * The first 8-bits of the 10-bit timer roll over after 256 ticks, 19.66 bits, - * or 16.38505 ms - * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66 bits - * (256 ticks/roll-over) * (1 sec/15624 ticks) = 16.38505 milliseconds + * The 8-bit timer rolls over after 256 ticks, 19.66085 bits, or 16.38505 ms + * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66085 bits + * (256 ticks/roll-over) * (1 sec/15625 ticks) = 16.38505 milliseconds */ #define TICKS_PER_BIT 13 /** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. - * * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 */ #define BITS_PER_TICK_Q10 79 +#define RX_WINDOW_FUDGE 2 + +#elif TICKS_PER_SECOND == 11719 && TIMER_INT_SIZE == 8 /** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. + * 11719 'ticks'/sec = 85 µs / 'tick' + * (1 sec/1200 bits) * (1 tick/85 µs) = 9.765625 ticks/bit * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 + * The 8-bit timer rolls over after 256 ticks, 26.2144 bits, or 21.84487 ms + * (256 ticks/roll-over) * (1 bit/9.765625 ticks) = 26.2144 bits + * (256 ticks/roll-over) * (1 sec/11719 ticks) = 21.84487 milliseconds + */ +#define TICKS_PER_BIT 10 +/** + * 1/(9.765625 ticks/bit) * 2^10 = 104.8576 */ +#define BITS_PER_TICK_Q10 105 #define RX_WINDOW_FUDGE 2 -#elif F_CPU == 8000000L +#elif TICKS_PER_SECOND == 31250 && TIMER_INT_SIZE == 8 /** - * @brief A string description of the prescaler in use. + * 31250 'ticks'/sec = 32 µs / 'tick' + * (1 sec/1200 bits) * (1 tick/32 µs) = 26.04166667 ticks/bit + * + * The 8-bit timer rolls over after 256 ticks, 9.8304 bits, or 8.192 ms + * (256 ticks/roll-over) * (1 bit/26.04166667 ticks) = 9.8304 bits + * (256 ticks/roll-over) * (1 sec/31250 ticks) = 8.192 milliseconds + * @note The timer will roll-over with each character! */ -#define PRESCALE_IN_USE_STR "512" +#define TICKS_PER_BIT 26 /** - * @brief The number of "ticks" of the timer that occur within the timing of one bit - * at the SDI-12 baud rate of 1200 bits/second. - * - * 8MHz / 512 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit - * - * The first 8-bits of the 10-bit timer roll over after 256 ticks, 19.66 bits, - * or 16.38505 ms - * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66 bits - * (256 ticks/roll-over) * (1 sec/15624 ticks) = 16.38505 milliseconds + * 1/(26.04166667 ticks/bit) * 2^10 = 39.3216 */ -#define TICKS_PER_BIT 13 +#define BITS_PER_TICK_Q10 39 +#define RX_WINDOW_FUDGE 10 + + +#elif TICKS_PER_SECOND == 500000 && TIMER_INT_SIZE == 16 /** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. + * 500kHz = 500,000 'ticks'/sec = 2 µs / 'tick' + * (1 sec/1200 bits) * (1 tick/2 µs) = 416.66667 ticks/bit * - * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 + * The 16-bit timer rolls over after 65536 ticks, 157.284 bits, or 131.07 ms + * (65536 ticks/roll-over) * (1 bit/416.66667 ticks) = 157.284 bits + * (65536 ticks/roll-over) * (1 sec/500000 ticks) = 131.07 milliseconds */ -#define BITS_PER_TICK_Q10 79 +#define TICKS_PER_BIT 416 +#define RX_WINDOW_FUDGE 30 + +#elif TICKS_PER_SECOND == 1000000 && TIMER_INT_SIZE == 32 /** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. + * Using `micros()` 1 "tick" is 1 µsec + * (1 sec/1200 bits) * (1 tick/1 µs) * (1000000 µsec/sec)= 833.33333 ticks/bit * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 + * The 32-bit timer rolls over after 4294967296 ticks, or 4294.9673 seconds */ -#define RX_WINDOW_FUDGE 5 +#define TICKS_PER_BIT 833UL +#define RX_WINDOW_FUDGE 50 +#else +#error "Board timer is incorrectly configured!" #endif -// Arduino Zero other SAMD21 boards -// -#elif defined(ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_SAMD) || \ - defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__SAMD21E18A__) +/** The interger type (size) of the timer return value */ +typedef TIMER_INT_TYPE sdi12timer_t; /** - * @brief A string description of the timer to use - * - * The Generic Clock controller GCLK provides nine Generic Clock Generators that can - * provide a wide range of clock frequencies. - * - * Generators can be set to use different external and internal oscillators as source. - * The clock of each Generator can be divided. The outputs from the Generators are used - * as sources for the Generic Clock Multiplexers, which provide the Generic Clock - * (GCLK_PERIPHERAL) to the peripheral modules, as shown in Generic Clock Controller - * Block Diagram. - * - * Features - * - Provides Generic Clocks - * - Wide frequency range - * - Clock source for the generator can be changed on the fly - * - * The TC consists of a counter, a prescaler, compare/capture channels and control - * logic. The counter can be set to count events, or it can be configured to count clock - * pulses. The counter, together with the compare/capture channels, can be configured to - * timestamp input events, allowing capture of frequency and pulse width. It can also - * perform waveform generation, such as frequency generation and pulse-width modulation - * (PWM). - * - * Features - * - Selectable configuration - * – Up to five 16-bit Timer/Counters (TC) including one low-power TC, each - * configurable as: - * - 8-bit TC with two compare/capture channels - * - 16-bit TC with two compare/capture channels - * - 32-bit TC with two compare/capture channels, by using two TCs - * - Waveform generation - * – Frequency generation - * – Single-slope pulse-width modulation - * - Input capture - * – Event capture - * – Frequency capture - * – Pulse-width capture - * - One input event - * - Interrupts/output events on: - * – Counter overflow/underflow - * – Compare match or capture - * - Internal prescaler - * - Can be used with DMA and to trigger DMA transactions - */ -#define TIMER_IN_USE_STR "GCLK4-TC3" -/** - * @brief The c macro name for the assembly timer to use - * - * This signifies the register of timer/counter 3, the 8-bit count, the count value + * @brief The class used to define the processor timer for the SDI-12 serial emulation. */ -#define TCNTX REG_TC3_COUNT8_COUNT // Using Timer 3 with generic clock 4 +class SDI12Timer { + public: + /** + * @brief Construct a new SDI12Timer + */ + SDI12Timer(); -/** - * @brief A string description of the prescaler in use. - */ -#define PRESCALE_IN_USE_STR "3x1024" -/** - * @brief The number of "ticks" of the timer that occur within the timing of one bit at - * the SDI-12 baud rate of 1200 bits/second. - * - * 48MHz / 3 pre-prescaler = 16MHz - * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit - * - * The 8-bit count rolls over after 256 ticks, 19.66 bits, or 16.38505 ms - * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66 bits - * (256 ticks/roll-over) * (1 sec/15624 ticks) = 16.38505 milliseconds - */ -#define TICKS_PER_BIT 13 -/** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. - * - * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 - */ -#define BITS_PER_TICK_Q10 79 -/** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. - * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 - */ -#define RX_WINDOW_FUDGE 2 + /** + * @brief static method for getting a 16-bit value from the multiplication of 2 8-bit + * values + * + * @param x The first 8 bit integer + * @param y The second 8 bit integer + * @return The result of the multiplication, as a 16 bit integer. + */ + static uint16_t mul8x8to16(uint8_t x, uint8_t y); -// Espressif ESP32/ESP8266 boards -// -#elif defined(ESP32) || defined(ESP8266) /** - * @brief Read the processor micros and right shift 6 bits (ie, divide by 64) to get a - * 64µs tick. + * @brief static method for calculating the number of bit-times that have elapsed + * between interrupts. + * + * @param dt The current value of the timer + * @return The number of bit times that have passed at 1200 baud. * - * @note The ESP32 and ESP8266 are fast enough processors that they can take the time - * to read the core 'micros()' function still complete the other processing needed on - * the serial bits. All of the other processors using the Arduino core also have the - * micros function, but the rest are not fast enough to waste the processor cycles to - * use the micros function and must use the faster assembly macros to read the - * processor timer directly. + * Adds a rxWindowWidth fudge factor to the time difference to get the number of + * ticks, and then multiplies the fudged ticks by the number of bits per tick. Uses + * the number of bits per tick shifted up by 2^10 and then shifts the result down by + * the same amount to compensate for the fact that the number of bits per tick is a + * decimal the timestamp is only an 8-bit integer. * - * @return **sdi12timer_t** The current processor micros + * @see https://github.com/SlashDevin/NeoSWSerial/pull/13#issuecomment-315463522 */ - sdi12timer_t SDI12TimerRead(void); - -/** - * @brief The number of "ticks" of the timer that occur within the timing of one bit - * at the SDI-12 baud rate of 1200 bits/second. - * - * 48MHz / 3 pre-prescaler = 16MHz - * 16MHz / 1024 prescaler = 15624 'ticks'/sec = 64 µs / 'tick' - * (1 sec/1200 bits) * (1 tick/64 µs) = 13.0208 ticks/bit - * - * The 8-bit count rolls over after 256 ticks, 19.66 bits, or 16.38505 ms - * (256 ticks/roll-over) * (1 bit/13.0208 ticks) = 19.66 bits - * (256 ticks/roll-over) * (1 sec/15624 ticks) = 16.38505 microseconds - */ -#define TICKS_PER_BIT 13 -/** - * @brief The number of "ticks" of the timer per SDI-12 bit, shifted by 2^10. - * - * 1/(13.0208 ticks/bit) * 2^10 = 78.6432 - */ -#define BITS_PER_TICK_Q10 79 -/** - * @brief A "fudge factor" to get the Rx to work well. It mostly works to ensure that - * uneven tick increments get rounded up. - * - * @see https://github.com/SlashDevin/NeoSWSerial/pull/13 - */ -#define RX_WINDOW_FUDGE 2 + static uint16_t bitTimes(sdi12timer_t dt); -// Unknown board -#else -#error "Please define your board timer and pins" -#endif + /** + * @brief Set the processor timer prescaler such that the 10 bits of an SDI-12 + * character are divided into the rollover time of the timer. + */ + void configSDI12TimerPrescale(void); + /** + * @brief Reset the processor timer prescaler to whatever it was prior to being + * adjusted for this library. + */ + void resetSDI12TimerPrescale(void); + /** + * @brief A function to read the timer value, where a multi-step function is needed. + * + * @return **sdi12timer_t** The current timer value + */ + sdi12timer_t SDI12TimerRead(void); }; #endif // SRC_SDI12_BOARDS_H_ diff --git a/tools/SDI12_spy/SDI12_spy.ino b/tools/SDI12_spy/SDI12_spy.ino deleted file mode 100644 index a914eea..0000000 --- a/tools/SDI12_spy/SDI12_spy.ino +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @file h_SDI-12_slave_implementation.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. - * @date 2016 - * @author D. Wasielewski - * - * @brief Example H: Using SDI-12 in Slave Mode - * - * 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. - * - * 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. - * - * D. Wasielewski, 2016 - * Builds upon work started by: - * https://github.com/jrzondagh/AgriApps-SDI-12-Arduino-Sensor - * https://github.com/Jorge-Mendes/Agro-Shield/tree/master/SDI-12ArduinoSensor - * - * Suggested improvements: - * - Get away from memory-hungry arduino String objects in favor of char buffers - * - Make an int variable for the "number of values to report" instead of the - * hard-coded 9s interspersed throughout the code - */ - -#include - -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ - -// Create object by which to communicate with the SDI-12 bus on SDIPIN -SDI12 slaveSDI12(DATA_PIN); - -void setup() { - Serial.begin(115200); - slaveSDI12.begin(); - delay(500); - slaveSDI12.forceListen(); // sets SDIPIN as input to prepare for incoming message - Serial.println("Starting SDI-12 Spy"); -} - -void loop() { - while (slaveSDI12.available()) { - int readChar = slaveSDI12.read(); - Serial.write(readChar); - // if (readChar == '\n') { - // slaveSDI12.forceListen(); - // } else { - // delay(10);// 1 character ~ 7.5ms - // } - } -} diff --git a/tools/TestCommands/TestCommands.ino b/tools/TestCommands/TestCommands.ino deleted file mode 100644 index 12ec9aa..0000000 --- a/tools/TestCommands/TestCommands.ino +++ /dev/null @@ -1,606 +0,0 @@ -/** - * @file d_simple_logger.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. - * @author Kevin M.Smith - * @date August 2013 - * - * @brief Example D: Check all Addresses for Active Sensors and Log Data - * - * This is a simple demonstration of the SDI-12 library for Arduino. - * - * It discovers the address of all sensors active on a single bus and takes measurements - * from them. - * - * 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. - * - * 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. - * - * To address a sensor, please see Example B: b_address_change.ino - */ - -#include - -uint32_t SERIAL_BAUD = 115200; /*!< The baud rate for the output serial port */ -uint8_t DATA_PIN = 7; /*!< The pin of the SDI-12 data bus */ -uint8_t POWER_PIN = 22; /*!< The sensor power pin (or -1 if not switching power) */ -int8_t FIRST_ADDRESS = 1; -int8_t LAST_ADDRESS = 6; // 62 -int8_t WAKE_DELAY = 0; /*!< The extra time needed for the board to wake. */ -int8_t COMMANDS_TO_TEST = - 1; /*!< The number of measurement commands to test, between 1 and 11. */ - -/** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); - -/// variable that alternates output type back and forth between parsed and raw -boolean flip = 0; - -String commands[] = {"", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; - -// keeps track of active addresses -bool isActive[62]; - -// keeps track of the wait time for each active addresses -uint32_t meas_time_ms[62]; - -// keeps track of the time each sensor was started -uint32_t millisStarted[62]; - -// keeps track of the time each sensor will be ready -uint32_t millisReady[62]; - -// keeps track of the number of results expected -uint8_t returnedResults[62]; - -String prev_result[62]; -String this_result[62]; -uint8_t numSensors = 0; - -/** - * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a - * decimal number between 0 and 61 (inclusive) to cover the 62 possible - * addresses. - */ -byte charToDec(char i) { - if ((i >= '0') && (i <= '9')) return i - '0'; - if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10; - if ((i >= 'A') && (i <= 'Z')) - return i - 'A' + 36; - else - return i; -} - -/** - * @brief maps a decimal number between 0 and 61 (inclusive) to allowable - * address characters '0'-'9', 'a'-'z', 'A'-'Z', - * - * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL. - */ -char decToChar(byte i) { - if (i < 10) return i + '0'; - if ((i >= 10) && (i < 36)) return i + 'a' - 10; - if ((i >= 36) && (i <= 62)) - return i + 'A' - 36; - else - return i; -} - -/** - * @brief gets identification information from a sensor, and prints it to the serial - * port - * - * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. - */ -void printInfo(char i, bool printCommands = true) { - String command = ""; - command += (char)i; - command += "I!"; - mySDI12.sendCommand(command, WAKE_DELAY); - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - delay(100); - - String sdiResponse = mySDI12.readStringUntil('\n'); - sdiResponse.trim(); - // allccccccccmmmmmmvvvxxx...xx - if (printCommands) { - Serial.print("<<<"); - Serial.println(sdiResponse); - } - - Serial.print("Address: "); - Serial.print(sdiResponse.substring(0, 1)); // address - Serial.print(", SDI-12 Version: "); - Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number - Serial.print(", Vendor ID: "); - Serial.print(sdiResponse.substring(3, 11)); // vendor id - Serial.print(", Sensor Model: "); - Serial.print(sdiResponse.substring(11, 17)); // sensor model - Serial.print(", Sensor Version: "); - Serial.print(sdiResponse.substring(17, 20)); // sensor version - Serial.print(", Sensor ID: "); - Serial.print(sdiResponse.substring(20)); // sensor id - Serial.println(); -} - -bool getResults(char addr, int resultsExpected, bool printCommands = true) { - uint8_t resultsReceived = 0; - uint8_t cmd_number = 0; - String str_result = ""; - while (resultsReceived < resultsExpected && cmd_number <= 9) { - // while (resultsReceived < resultsExpected && cmd_number <= 1) { - String command = ""; - // in this example we will only take the 'DO' measurement - command = ""; - command += addr; - command += "D"; - command += cmd_number; - command += "!"; // SDI-12 command to get data [address][D][dataOption][!] - mySDI12.sendCommand(command, WAKE_DELAY); - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - - uint32_t start = millis(); - while (mySDI12.available() < 3 && (millis() - start) < 150) {} - if (printCommands) { - Serial.print("<<<"); - Serial.write(mySDI12.read()); // ignore the repeated SDI12 address - } else { - mySDI12.read(); - } - - bool gotResults = false; - while (mySDI12.available()) { - // First peek to see if the next character in the buffer is a number - char c = mySDI12.peek(); - // if there's a number, a decimal, or a negative sign next in the - // buffer, start reading it as a float. - if (c == '-' || (c >= '0' && c <= '9') || c == '.') { - // Read the float without skipping any in-valid characters. - // We don't want to skip anything because we want to be able to - // debug and see exactly which characters the sensor sent over - // if they weren't numbers. - // Reading the numbers as a float will remove them from the - // buffer. - float result = mySDI12.parseFloat(SKIP_NONE); - // add result to print out string - str_result += String(result, 8); - // also print results if we're printing commands - if (printCommands) { Serial.print(String(result, 8)); } - // add how many results we have - if (result != -9999) { - gotResults = true; - resultsReceived++; - } - } else if (c >= 0 && c != '\r' && c != '\n') { - str_result += String(c); - if (printCommands) { - Serial.write(mySDI12.read()); - } else { - mySDI12.read(); - } - } else { - mySDI12.read(); - } - delay(10); // 1 character ~ 7.5ms - } - if (printCommands) { - Serial.print("\nTotal Results Received: "); - Serial.print(resultsReceived); - Serial.print(", Remaining: "); - Serial.println(resultsExpected - resultsReceived); - } - if (!gotResults) { - if (printCommands) { - Serial.println((" No results received, will not continue requests!")); - } - break; - } // don't do another loop if we got nothing - cmd_number++; - } - mySDI12.clearBuffer(); - - bool success = resultsReceived == resultsExpected; - if (success) { this_result[charToDec(addr)] = str_result; } - return success; -} - -bool getContinuousResults(char i, int resultsExpected, bool printCommands = true) { - uint8_t resultsReceived = 0; - uint8_t cmd_number = 0; - while (resultsReceived < resultsExpected && cmd_number <= 9) { - String command = ""; - // in this example we will only take the 'DO' measurement - command = ""; - command += i; - command += "R"; - command += cmd_number; - command += "!"; // SDI-12 command to get data [address][D][dataOption][!] - mySDI12.sendCommand(command, WAKE_DELAY); - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - - uint32_t start = millis(); - while (mySDI12.available() < 3 && (millis() - start) < 1500) {} - if (printCommands) { - Serial.print("<<<"); - Serial.write(mySDI12.read()); // ignore the repeated SDI12 address - } - - while (mySDI12.available()) { - char c = mySDI12.peek(); - if (c == '-' || (c >= '0' && c <= '9') || c == '.') { - float result = mySDI12.parseFloat(SKIP_NONE); - Serial.print(String(result, 10)); - if (result != -9999) { resultsReceived++; } - } else if (c >= 0 && c != '\r' && c != '\n') { - Serial.write(mySDI12.read()); - } else { - mySDI12.read(); - } - delay(10); // 1 character ~ 7.5ms - } - if (printCommands) { - Serial.print("Total Results Received: "); - Serial.print(resultsReceived); - Serial.print(", Remaining: "); - Serial.println(resultsExpected - resultsReceived); - } - if (!resultsReceived) { break; } // don't do another loop if we got nothing - cmd_number++; - } - mySDI12.clearBuffer(); - - return resultsReceived == resultsExpected; -} - -int startConcurrentMeasurement(char i, String meas_type = "", - bool printCommands = true) { - String command = ""; - command += i; - command += "C"; - command += meas_type; - command += "!"; // SDI-12 concurrent measurement command format [address]['C'][!] - mySDI12.sendCommand(command, WAKE_DELAY); - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - delay(5); - - // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of - // measurments available, 0-9] - String sdiResponse = mySDI12.readStringUntil('\n'); - sdiResponse.trim(); - if (printCommands) { - Serial.print("<<<"); - Serial.println(sdiResponse); - } - mySDI12.clearBuffer(); - - // find out how long we have to wait (in seconds). - uint8_t meas_time_s = sdiResponse.substring(1, 4).toInt(); - if (printCommands) { - Serial.print("expected measurement time: "); - Serial.print(meas_time_s); - Serial.print(" s, "); - } - - // Set up the number of results to expect - int numResults = sdiResponse.substring(4).toInt(); - if (printCommands) { - Serial.print("Number Results: "); - Serial.println(numResults); - } - - uint8_t sensorNum = charToDec(i); // e.g. convert '0' to 0, 'a' to 10, 'Z' to 61. - meas_time_ms[sensorNum] = ((uint32_t)(meas_time_s)) * 1000; - millisStarted[sensorNum] = millis(); - if (meas_time_ms == 0) { - millisReady[sensorNum] = millis(); - } else { - // give an extra second - millisReady[sensorNum] = millis() + meas_time_ms[sensorNum] + 1000; - } - returnedResults[sensorNum] = numResults; - - return numResults; -} - -bool takeMeasurement(char i, String meas_type = "", bool printCommands = true) { - String command = ""; - command += i; - command += "M"; - command += meas_type; - command += "!"; // SDI-12 measurement command format [address]['M'][!] - mySDI12.sendCommand(command, WAKE_DELAY); - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - delay(100); - - // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of - // measurments available, 0-9] - String sdiResponse = mySDI12.readStringUntil('\n'); - sdiResponse.trim(); - if (printCommands) { - Serial.print("<<<"); - Serial.println(sdiResponse); - } - - // find out how long we have to wait (in seconds). - uint32_t meas_time_s = sdiResponse.substring(1, 4).toInt(); - if (printCommands) { - Serial.print("Sensor Reported Measurement Time: "); - Serial.print(meas_time_s); - Serial.print(" s, "); - } - - // Set up the number of results to expect - int numResults = sdiResponse.substring(4).toInt(); - if (printCommands) { - Serial.print("Number Results: "); - Serial.println(numResults); - } - // if (numResults==0){return false;} - if (numResults == 0) { return ""; } - - unsigned long timerStart = millis(); - while ((millis() - timerStart) < (meas_time_s + 1) * 1000) { - if (mySDI12.available()) // sensor can interrupt us to let us know it is done early - { - unsigned long measTime = millis() - timerStart; - if (printCommands) { - Serial.print("<<<"); - Serial.println(mySDI12.readStringUntil('\n')); - // mySDI12.clearBuffer(); - } - Serial.print("Completed after "); - Serial.print(measTime); - Serial.println(" ms"); - break; - } - } - // Wait for anything else and clear it out - delay(30); - mySDI12.clearBuffer(); - - return getResults(i, numResults, printCommands); -} - -// this checks for activity at a particular address -// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' -boolean checkActive(char i, int8_t numPings = 3, bool printCommands = false) { - String command = ""; - command += (char)i; // sends basic 'acknowledge' command [address][!] - command += "!"; - - for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - mySDI12.sendCommand(command, WAKE_DELAY); - delay(100); - if (mySDI12.available()) { // If we here anything, assume we have an active sensor - if (printCommands) { - Serial.print("<<<"); - while (mySDI12.available()) { - Serial.write(mySDI12.read()); - delay(10); - } - } else { - mySDI12.clearBuffer(); - } - return true; - } - } - mySDI12.clearBuffer(); - return false; -} - -void setup() { - Serial.begin(SERIAL_BAUD); - while (!Serial) - ; - - Serial.println("Opening SDI-12 bus..."); - mySDI12.begin(); - delay(500); // allow things to settle - - Serial.println("Timeout value: "); - Serial.println(mySDI12.TIMEOUT); - - for (int8_t i = 0; i < 62; i++) { - isActive[i] = false; - meas_time_ms[i] = 0; - millisStarted[i] = 0; - millisReady[i] = 0; - returnedResults[i] = 0; - prev_result[i] = ""; - this_result[i] = ""; - } - - // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering down sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, LOW); - // delay(2500L); - delay(250L); - } - - // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering up sensors, wait..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); - // delay(10000L); - delay(125); - } - - // Quickly Scan the Address Space - Serial.println("Scanning all addresses, please wait..."); - - for (int8_t i = FIRST_ADDRESS; i <= LAST_ADDRESS; i++) { - char addr = decToChar(i); - Serial.print("i: "); - Serial.print(i); - Serial.print(", addr: "); - Serial.print(addr); - Serial.print(", reversed: "); - Serial.println(charToDec(addr)); - if (checkActive(addr, 5, true)) { - numSensors++; - isActive[i] = 1; - // Serial.println(", +"); - printInfo(addr, true); - } else { - // Serial.println(", -"); - } - } - Serial.print("Total number of sensors found: "); - Serial.println(numSensors); - - if (numSensors == 0) { - Serial.println( - "No sensors found, please check connections and restart the Arduino."); - while (true) { delay(10); } // do nothing forever - } - - Serial.println(); - Serial.println("-------------------------------------------------------------------" - "------------"); - - delay(1000); -} - -void loop() { - flip = !flip; // flip the switch between concurrent and not - // flip = 1; - // flip = 0; - uint32_t start = millis(); - // Serial.print("Flip: "); - // Serial.println(flip); - - // // Power the sensors; - // if (POWER_PIN > 0) { - // Serial.println("Powering down sensors..."); - // pinMode(POWER_PIN, OUTPUT); - // digitalWrite(POWER_PIN, LOW); - // delay(5000L); - // } - - // // Power the sensors; - // if (POWER_PIN > 0) { - // Serial.println("Powering up sensors..."); - // pinMode(POWER_PIN, OUTPUT); - // digitalWrite(POWER_PIN, HIGH); - // delay(125); - // } - - if (flip) { - // measure one at a time - for (int8_t i = 0; i < 62; i++) { - char addr = decToChar(i); - if (isActive[i]) { - for (uint8_t a = 0; a < COMMANDS_TO_TEST; a++) { - Serial.print("Command "); - Serial.print(i); - Serial.print("M"); - Serial.print(commands[a]); - Serial.println('!'); - takeMeasurement(addr, commands[a], true); - } - // getContinuousResults(addr, 3); - Serial.println(); - } else { - Serial.print("Address "); - Serial.print(addr); - Serial.println(" is not active"); - } - } - Serial.print("Total Time for Individual Measurements: "); - Serial.println(millis() - start); - } else { - for (uint8_t a = 0; a < COMMANDS_TO_TEST; a++) { - uint32_t min_wait = 60000L; - uint32_t max_wait = 0; - uint32_t for_start = millis(); - // start all sensors measuring concurrently - for (int8_t i = 0; i < 62; i++) { - char addr = decToChar(i); - if (isActive[i]) { - Serial.print("Command "); - Serial.print(i); - Serial.print("C"); - Serial.print(commands[a]); - Serial.println('!'); - startConcurrentMeasurement(addr, commands[a], true); - if (meas_time_ms[i] < min_wait) { min_wait = meas_time_ms[i]; } - if (meas_time_ms[i] > max_wait) { max_wait = meas_time_ms[i]; } - } else { - Serial.print("Address "); - Serial.print(addr); - Serial.println(" is not active"); - } - } - min_wait = 800; - // min_wait = max(10, min_wait / 2); - max_wait = max(1000L, max_wait + 1000L); - Serial.print("minimum expected wait for all sensors: "); - Serial.println(min_wait); - Serial.print("maximum expected wait for all sensors: "); - Serial.println(max_wait); - - - uint8_t numReadingsRecorded = 0; - delay(min_wait); - do { - // get all readings - for (int8_t i = 0; i < 62; i++) { - uint32_t timeWaited = millis() - millisStarted[i]; - if (this_result[i] != "") { prev_result[i] = this_result[i]; } - - char addr = decToChar(i); - if (isActive[i]) { - // if (millis() > millisReady[i]) { - // if (millis() > millisStarted[i] + a) { - if (returnedResults[i] > 0) { - Serial.print("timeWaited: "); - Serial.println(timeWaited); - bool resultsReady = getResults(addr, returnedResults[i], true); - if (resultsReady) { - numReadingsRecorded++; - Serial.print("Got results from "); - Serial.print(numReadingsRecorded); - Serial.print(" of "); - Serial.print(numSensors); - Serial.println(" sensors"); - } - } - } - } - } while (millis() - for_start < max_wait && numReadingsRecorded < numSensors); - } - Serial.print("Total Time for Concurrent Measurements: "); - Serial.println(millis() - start); - } - - Serial.println("-------------------------------------------------------------------" - "------------"); - // delay(1000); // wait ten seconds between measurement attempts. -} diff --git a/tools/TestWarmUp/TestWarmUp.ino b/tools/TestWarmUp/TestWarmUp.ino deleted file mode 100644 index 60104ad..0000000 --- a/tools/TestWarmUp/TestWarmUp.ino +++ /dev/null @@ -1,166 +0,0 @@ -/** - * @file TestWarmUp.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. - * @author Sara Damiano - * @date March 2021 - */ - -#include - -#define SERIAL_BAUD 115200 /*!< The baud rate for the output serial port */ -#define DATA_PIN 7 /*!< The pin of the SDI-12 data bus */ -#define SENSOR_ADDRESS '2' /*!< The address of the SDI-12 sensor */ -#define POWER_PIN 22 /*!< The sensor power pin (or -1 if not switching power) */ - -/** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); -int32_t min_wake_delay = 0; /*!< The min time to test wake after a line break. */ -int32_t increment_wake = 100; /*!< The time to lengthen waits between reps. */ -int32_t max_wake_delay = 100; /*!< The max time to test wake (should be <=100). */ -int32_t min_power_delay = 100L; /*!< The min time to test wake after power on. */ -int32_t increment_power = 100; /*!< The time to lengthen waits between reps. */ -int32_t max_power_delay = 10000L; /*!< The max time to test wake after power on. */ - -int32_t power_delay = min_power_delay; -int32_t wake_delay = min_wake_delay; - -/** - * @brief gets identification information from a sensor, and prints it to the serial - * port - * - * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. - */ -bool printInfo(char i, bool printCommands = true) { - String command = ""; - command += (char)i; - command += "I!"; - mySDI12.sendCommand(command, wake_delay); - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - delay(100); - - String sdiResponse = mySDI12.readStringUntil('\n'); - sdiResponse.trim(); - // allccccccccmmmmmmvvvxxx...xx - if (printCommands) { - Serial.print("<<<"); - Serial.println(sdiResponse); - } - - Serial.print("Address: "); - Serial.print(sdiResponse.substring(0, 1)); // address - Serial.print(", SDI-12 Version: "); - Serial.print(sdiResponse.substring(1, 3).toFloat() / 10); // SDI-12 version number - Serial.print(", Vendor ID: "); - Serial.print(sdiResponse.substring(3, 11)); // vendor id - Serial.print(", Sensor Model: "); - Serial.print(sdiResponse.substring(11, 17)); // sensor model - Serial.print(", Sensor Version: "); - Serial.print(sdiResponse.substring(17, 20)); // sensor version - Serial.print(", Sensor ID: "); - Serial.print(sdiResponse.substring(20)); // sensor id - Serial.println(); - - if (sdiResponse.length() < 3) { return false; }; - return true; -} - -// this checks for activity at a particular address -// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' -boolean checkActive(char i, int8_t numPings = 3, bool printCommands = false) { - String command = ""; - command += (char)i; // sends basic 'acknowledge' command [address][!] - command += "!"; - - for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts - if (printCommands) { - Serial.print(">>>"); - Serial.println(command); - } - mySDI12.sendCommand(command, wake_delay); - delay(100); - if (mySDI12.available()) { // If we here anything, assume we have an active sensor - if (printCommands) { - Serial.print("<<<"); - while (mySDI12.available()) { - Serial.write(mySDI12.read()); - delay(10); - } - } else { - mySDI12.clearBuffer(); - } - return true; - } - } - mySDI12.clearBuffer(); - return false; -} - -void setup() { - Serial.begin(SERIAL_BAUD); - while (!Serial) - ; - - Serial.println("Opening SDI-12 bus..."); - mySDI12.begin(); - delay(500); // allow things to settle - - Serial.println("Timeout value: "); - Serial.println(mySDI12.TIMEOUT); -} - -void loop() { - // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering down sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, LOW); - delay(2500L); - } - - // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering up sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); - delay(power_delay); - } - - if (checkActive(SENSOR_ADDRESS, 5, true)) { - Serial.print("Got some response after "); - Serial.print(power_delay); - Serial.print("ms after power with "); - Serial.print(wake_delay); - Serial.println("ms with wake delay"); - if (printInfo(SENSOR_ADDRESS, true)) { - // if we got sensor info, stop - Serial.println("Looks good. Stopping."); - while (1) - ; - } else { - Serial.println("Sensor info not valid!"); - } - } else { - Serial.print("No response after "); - Serial.print(power_delay); - Serial.print("ms after power with "); - Serial.print(wake_delay); - Serial.println("ms with wake delay"); - } - Serial.println("-------------------------------------------------------------------" - "------------"); - if (power_delay > max_power_delay) { - power_delay = min_power_delay; - wake_delay = wake_delay + increment_wake; - } else { - power_delay = power_delay + increment_power; - } - if (wake_delay > max_wake_delay) { - Serial.println("FINISHED!!"); - while (1) {} - } -} diff --git a/tools/printCommands/printCommands.ino b/tools/printCommands/printCommands.ino deleted file mode 100644 index b8690fd..0000000 --- a/tools/printCommands/printCommands.ino +++ /dev/null @@ -1,399 +0,0 @@ -/** - * @file d_simple_logger.ino - * @copyright (c) 2013-2020 Stroud Water Research Center (SWRC) - * and the EnviroDIY Development Team - * This example is published under the BSD-3 license. - * @author Kevin M.Smith - * @date August 2013 - * - * @brief Example D: Check all Addresses for Active Sensors and Log Data - * - * This is a simple demonstration of the SDI-12 library for Arduino. - * - * It discovers the address of all sensors active on a single bus and takes measurements - * from them. - * - * 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. - * - * 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. - * - * To address a sensor, please see Example B: b_address_change.ino - */ - -#include - -uint32_t SERIAL_BAUD = 115200; /*!< The baud rate for the output serial port */ -uint8_t DATA_PIN = 7; /*!< The pin of the SDI-12 data bus */ -uint8_t POWER_PIN = 22; /*!< The sensor power pin (or -1 if not switching power) */ -int8_t FIRST_ADDRESS = 1; -int8_t LAST_ADDRESS = 6; // 62 -int8_t WAKE_DELAY = 0; /*!< The extra time needed for the board to wake. */ -int8_t COMMANDS_TO_TEST = - 1; /*!< The number of measurement commands to test, between 1 and 11. */ - -/** Define the SDI-12 bus */ -SDI12 mySDI12(DATA_PIN); - -/// variable that alternates output type back and forth between parsed and raw -boolean flip = 0; - -String commands[] = {"", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; - -// keeps track of active addresses -bool isActive[62]; - -// keeps track of the wait time for each active addresses -uint32_t meas_time_ms[62]; - -// keeps track of the time each sensor was started -uint32_t millisStarted[62]; - -// keeps track of the time each sensor will be ready -uint32_t millisReady[62]; - -// keeps track of the number of results expected -uint8_t returnedResults[62]; - -String prev_result[62]; -String this_result[62]; -uint8_t numSensors = 0; - -/** - * @brief converts allowable address characters ('0'-'9', 'a'-'z', 'A'-'Z') to a - * decimal number between 0 and 61 (inclusive) to cover the 62 possible - * addresses. - */ -byte charToDec(char i) { - if ((i >= '0') && (i <= '9')) return i - '0'; - if ((i >= 'a') && (i <= 'z')) return i - 'a' + 10; - if ((i >= 'A') && (i <= 'Z')) - return i - 'A' + 36; - else - return i; -} - -/** - * @brief maps a decimal number between 0 and 61 (inclusive) to allowable - * address characters '0'-'9', 'a'-'z', 'A'-'Z', - * - * THIS METHOD IS UNUSED IN THIS EXAMPLE, BUT IT MAY BE HELPFUL. - */ -char decToChar(byte i) { - if (i < 10) return i + '0'; - if ((i >= 10) && (i < 36)) return i + 'a' - 10; - if ((i >= 36) && (i <= 62)) - return i + 'A' - 36; - else - return i; -} - -/** - * @brief gets identification information from a sensor, and prints it to the serial - * port - * - * @param i a character between '0'-'9', 'a'-'z', or 'A'-'Z'. - */ -void printInfo(char i) { - String command = ""; - command += (char)i; - command += "I!"; - mySDI12.sendCommand(command, WAKE_DELAY); - Serial.print("\n>>>"); - Serial.println(command); - delay(15); - - String sdiResponse = mySDI12.readStringUntil('\n'); - // sdiResponse.trim(); - // allccccccccmmmmmmvvvxxx...xx - Serial.print("\n<<<"); - Serial.println(sdiResponse); -} - -bool getResults(char addr, int resultsExpected) { - uint8_t resultsReceived = 0; - uint8_t cmd_number = 0; - String str_result = ""; - while (resultsReceived < resultsExpected && cmd_number <= 9) { - // while (resultsReceived < resultsExpected && cmd_number <= 1) { - String command = ""; - // in this example we will only take the 'DO' measurement - command = ""; - command += addr; - command += "D"; - command += cmd_number; - command += "!"; // SDI-12 command to get data [address][D][dataOption][!] - mySDI12.sendCommand(command, WAKE_DELAY); - Serial.print("\n>>>"); - Serial.println(command); - - uint32_t start = millis(); - while (mySDI12.available() < 3 && (millis() - start) < 150) {} - Serial.print("\n<<<"); - Serial.write(mySDI12.read()); // ignore the repeated SDI12 address - - uint8_t char_rcvd = 0; - while (mySDI12.available()) { - char_rcvd++; - char c = mySDI12.peek(); - if (c == '+') { - Serial.write(mySDI12.read()); - resultsReceived++; - } else if (c >= 0 && c != '\r' && c != '\n') { - Serial.write(mySDI12.read()); - } else { - mySDI12.read(); - Serial.println(); - } - delay(10); // 1 character ~ 7.5ms - } - if (char_rcvd <= 2) { - break; - } // don't do another loop if didn't get more than the address - cmd_number++; - } - mySDI12.clearBuffer(); - - bool success = resultsReceived == resultsExpected; - if (success) { this_result[charToDec(addr)] = str_result; } - return success; -} - -bool getContinuousResults(char i, int resultsExpected) { - uint8_t resultsReceived = 0; - uint8_t cmd_number = 0; - while (resultsReceived < resultsExpected && cmd_number <= 9) { - String command = ""; - // in this example we will only take the 'DO' measurement - command = ""; - command += i; - command += "R"; - command += cmd_number; - command += "!"; // SDI-12 command to get data [address][D][dataOption][!] - mySDI12.sendCommand(command, WAKE_DELAY); - Serial.print("\n>>>"); - Serial.println(command); - - uint32_t start = millis(); - while (mySDI12.available() < 3 && (millis() - start) < 1500) {} - Serial.print("\n<<<"); - Serial.write(mySDI12.read()); // ignore the repeated SDI12 address - - while (mySDI12.available()) { - char c = mySDI12.peek(); - if (c == '-' || (c >= '0' && c <= '9') || c == '.') { - float result = mySDI12.parseFloat(SKIP_NONE); - Serial.print(String(result, 10)); - if (result != -9999) { resultsReceived++; } - } else if (c >= 0 && c != '\r' && c != '\n') { - Serial.write(mySDI12.read()); - } else { - // mySDI12.read(); - Serial.println(); - } - delay(10); // 1 character ~ 7.5ms - } - if (!resultsReceived) { break; } // don't do another loop if we got nothing - cmd_number++; - } - mySDI12.clearBuffer(); - - return resultsReceived == resultsExpected; -} - -int startConcurrentMeasurement(char i, String meas_type = "") { - String command = ""; - command += i; - command += "C"; - command += meas_type; - command += "!"; // SDI-12 concurrent measurement command format [address]['C'][!] - mySDI12.sendCommand(command, WAKE_DELAY); - Serial.print("\n>>>"); - Serial.println(command); - delay(5); - - // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of - // measurments available, 0-9] - String sdiResponse = mySDI12.readStringUntil('\n'); - Serial.print("\n<<<"); - Serial.println(sdiResponse); - mySDI12.clearBuffer(); - - // find out how long we have to wait (in seconds). - uint8_t meas_time_s = sdiResponse.substring(1, 4).toInt(); - - // Set up the number of results to expect - int numResults = sdiResponse.substring(4).toInt(); - - uint8_t sensorNum = charToDec(i); // e.g. convert '0' to 0, 'a' to 10, 'Z' to 61. - meas_time_ms[sensorNum] = ((uint32_t)(meas_time_s)) * 1000; - millisStarted[sensorNum] = millis(); - if (meas_time_ms == 0) { - millisReady[sensorNum] = millis(); - } else { - // give an extra second - millisReady[sensorNum] = millis() + meas_time_ms[sensorNum] + 1000; - } - returnedResults[sensorNum] = numResults; - - return numResults; -} - -bool takeMeasurement(char i, String meas_type = "") { - String command = ""; - command += i; - command += "M"; - command += meas_type; - command += "!"; // SDI-12 measurement command format [address]['M'][!] - mySDI12.sendCommand(command, WAKE_DELAY); - Serial.print("\n>>>"); - Serial.println(command); - delay(15); - - // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of - // measurments available, 0-9] - String sdiResponse = mySDI12.readStringUntil('\n'); - Serial.print("\n<<<"); - Serial.println(sdiResponse); - - // find out how long we have to wait (in seconds). - uint32_t meas_time_s = sdiResponse.substring(1, 4).toInt(); - - // Set up the number of results to expect - int numResults = sdiResponse.substring(4).toInt(); - // if (numResults==0){return false;} - if (numResults == 0) { return ""; } - - unsigned long timerStart = millis(); - while ((millis() - timerStart) < (meas_time_s + 1) * 1000) { - if (mySDI12.available()) // sensor can interrupt us to let us know it is done early - { - unsigned long measTime = millis() - timerStart; - Serial.print("\n<<<"); - Serial.println(mySDI12.readStringUntil('\n')); - break; - } - } - // Wait for anything else and clear it out - delay(30); - mySDI12.clearBuffer(); - - return getResults(i, numResults); -} - -// this checks for activity at a particular address -// expects a char, '0'-'9', 'a'-'z', or 'A'-'Z' -boolean checkActive(char i, int8_t numPings = 3) { - String command = ""; - command += (char)i; // sends basic 'acknowledge' command [address][!] - command += "!"; - - for (int j = 0; j < numPings; j++) { // goes through three rapid contact attempts - Serial.print("\n>>>"); - Serial.println(command); - mySDI12.sendCommand(command, WAKE_DELAY); - delay(15); - if (mySDI12.available()) { // If we here anything, assume we have an active sensor - Serial.print("\n<<<"); - uint8_t i = 0; - while (mySDI12.available() && i < 60) { - Serial.write(mySDI12.read()); - delay(5); - i++; - } - return true; - } - } - mySDI12.clearBuffer(); - return false; -} - -void setup() { - Serial.begin(SERIAL_BAUD); - while (!Serial) - ; - - Serial.println("Opening SDI-12 bus..."); - mySDI12.begin(); - delay(500); // allow things to settle - - Serial.println("Timeout value: "); - Serial.println(mySDI12.TIMEOUT); - - for (int8_t i = 0; i < 62; i++) { - isActive[i] = false; - meas_time_ms[i] = 0; - millisStarted[i] = 0; - millisReady[i] = 0; - returnedResults[i] = 0; - prev_result[i] = ""; - this_result[i] = ""; - } - - // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering down sensors..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, LOW); - // delay(2500L); - delay(250L); - } - - // Power the sensors; - if (POWER_PIN > 0) { - Serial.println("Powering up sensors, wait..."); - pinMode(POWER_PIN, OUTPUT); - digitalWrite(POWER_PIN, HIGH); - // delay(10000L); - delay(125); - } - - // Quickly Scan the Address Space - Serial.println("Scanning all addresses, please wait..."); - - for (int8_t i = FIRST_ADDRESS; i <= LAST_ADDRESS; i++) { - char addr = decToChar(i); - if (checkActive(addr, 5)) { - numSensors++; - isActive[i] = 1; - printInfo(addr); - } else { - } - } - Serial.print("Total number of sensors found: "); - Serial.println(numSensors); - - if (numSensors == 0) { - Serial.println( - "No sensors found, please check connections and restart the Arduino."); - while (true) { delay(10); } // do nothing forever - } - - Serial.println(); - Serial.println("-------------------------------------------------------------------" - "------------"); - - delay(1000); -} - -void loop() { - uint32_t start = millis(); - // measure one at a time - for (int8_t i = 0; i < 62; i++) { - char addr = decToChar(i); - if (isActive[i]) { - printInfo(addr); - for (uint8_t a = 0; a < COMMANDS_TO_TEST; a++) { - takeMeasurement(addr, commands[a]); - } - Serial.println(); - } - } - - Serial.println("-------------------------------------------------------------------" - "------------"); - delay(1000); // wait ten seconds -}