├── .codespellrc ├── .github ├── dependabot.yml └── workflows │ ├── check-arduino.yml │ ├── compile-examples.yml │ ├── report-size-deltas.yml │ ├── spell-check.yml │ └── sync-labels.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── README.md ├── assets │ ├── Arduino-Threads-Parallel.svg │ ├── Arduino-Threads-Sequential.svg │ ├── Arduino-Threads-Tasks-Example.svg │ ├── boardlist.png │ ├── compiled.png │ ├── pathupdate.png │ └── uploaded.png ├── cli-getting-started.md ├── data-exchange.md ├── threading-basics.md ├── threadsafe-serial.md ├── threadsafe-spi.md └── threadsafe-wire.md ├── examples ├── Threading_Basics │ ├── Shared_Counter │ │ ├── Consumer.inot │ │ ├── Producer.inot │ │ ├── SharedVariables.h │ │ └── Shared_Counter.ino │ ├── Source_Sink_Counter │ │ ├── Consumer.inot │ │ ├── Producer.inot │ │ ├── SharedVariables.h │ │ └── Source_Sink_Counter.ino │ ├── Source_Sink_LED │ │ ├── SharedVariables.h │ │ ├── Sink_Thread.inot │ │ ├── Source_Sink_LED.ino │ │ └── Source_Thread.inot │ └── Thermostat │ │ ├── AirConditionerControl.inot │ │ ├── HeatingControl.inot │ │ ├── SharedVariables.h │ │ ├── TemperatureSensor.inot │ │ ├── TemperatureSensorReporter.inot │ │ └── Thermostat.ino └── Threadsafe_IO │ ├── SPI │ └── SPI.ino │ ├── SPI_BusIO │ └── SPI_BusIO.ino │ ├── Serial_GlobalPrefixSuffix │ ├── Serial_GlobalPrefixSuffix.ino │ ├── SharedVariables.h │ ├── Thread_1.inot │ ├── Thread_2.inot │ └── Thread_3.inot │ ├── Serial_ProtocolWrapping │ ├── GPS_Thread.inot │ ├── Serial_ProtocolWrapping.ino │ └── SharedVariables.h │ ├── Serial_Reader │ ├── Serial_Reader.ino │ ├── SharedVariables.h │ ├── Thread_1.inot │ ├── Thread_2.inot │ └── Thread_3.inot │ ├── Serial_Writer │ ├── Serial_Writer.ino │ ├── SharedVariables.h │ ├── Thread_1.inot │ ├── Thread_2.inot │ └── Thread_3.inot │ ├── Wire │ └── Wire.ino │ └── Wire_BusIO │ └── Wire_BusIO.ino ├── keywords.txt ├── library.properties └── src ├── Arduino_Threads.cpp ├── Arduino_Threads.h ├── io ├── BusDevice.cpp ├── BusDevice.h ├── IoTransaction.h ├── serial │ ├── SerialDispatcher.cpp │ └── SerialDispatcher.h ├── spi │ ├── SpiBusDevice.cpp │ ├── SpiBusDevice.h │ ├── SpiBusDeviceConfig.h │ ├── SpiDispatcher.cpp │ └── SpiDispatcher.h ├── util │ ├── util.cpp │ └── util.h └── wire │ ├── WireBusDevice.cpp │ ├── WireBusDevice.h │ ├── WireBusDeviceConfig.h │ ├── WireDispatcher.cpp │ └── WireDispatcher.h └── threading ├── CircularBuffer.hpp ├── Shared.hpp ├── Sink.hpp └── Source.hpp /.codespellrc: -------------------------------------------------------------------------------- 1 | # See: https://github.com/codespell-project/codespell#using-a-config-file 2 | [codespell] 3 | # In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: 4 | ignore-words-list = inot 5 | builtin = clear,informal,en-GB_to_en-US 6 | check-filenames = 7 | check-hidden = 8 | skip = ./.git 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See: https://docs.github.com/en/code-security/supply-chain-security/configuration-options-for-dependency-updates#about-the-dependabotyml-file 2 | version: 2 3 | 4 | updates: 5 | # Configure check for outdated GitHub Actions actions in workflows. 6 | # See: https://docs.github.com/en/code-security/supply-chain-security/keeping-your-actions-up-to-date-with-dependabot 7 | - package-ecosystem: github-actions 8 | directory: / # Check the repository's workflows under /.github/workflows/ 9 | schedule: 10 | interval: daily 11 | labels: 12 | - "topic: infrastructure" 13 | -------------------------------------------------------------------------------- /.github/workflows/check-arduino.yml: -------------------------------------------------------------------------------- 1 | name: Check Arduino 2 | 3 | # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | pull_request: 7 | schedule: 8 | # Run every Tuesday at 8 AM UTC to catch breakage caused by new rules added to Arduino Lint. 9 | - cron: "0 8 * * TUE" 10 | workflow_dispatch: 11 | repository_dispatch: 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Arduino Lint 22 | uses: arduino/arduino-lint-action@v2 23 | with: 24 | compliance: specification 25 | # Change this to "update" once the library is added to the index. 26 | library-manager: update 27 | # Always use this setting for official repositories. Remove for 3rd party projects. 28 | official: true 29 | -------------------------------------------------------------------------------- /.github/workflows/compile-examples.yml: -------------------------------------------------------------------------------- 1 | name: Compile Examples 2 | 3 | # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows 4 | on: 5 | pull_request: 6 | paths: 7 | - ".github/workflows/compile-examples.yml" 8 | - "library.properties" 9 | - "examples/**" 10 | - "src/**" 11 | push: 12 | paths: 13 | - ".github/workflows/compile-examples.yml" 14 | - "library.properties" 15 | - "examples/**" 16 | - "src/**" 17 | schedule: 18 | # Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms). 19 | - cron: "0 8 * * TUE" 20 | workflow_dispatch: 21 | repository_dispatch: 22 | 23 | env: 24 | SKETCHES_REPORTS_PATH: sketches-reports 25 | SKETCHES_REPORTS_ARTIFACT_NAME: sketches-reports 26 | 27 | jobs: 28 | compile-test: 29 | name: ${{ matrix.board.fqbn }} 30 | runs-on: ubuntu-latest 31 | 32 | env: 33 | # libraries to install for all boards 34 | UNIVERSAL_LIBRARIES: | 35 | # Install the Arduino_Threads library from the repository 36 | - source-path: ./ 37 | # sketch paths to compile (recursive) for all boards 38 | UNIVERSAL_SKETCH_PATHS: | 39 | - examples 40 | ARDUINOCORE_MBED_STAGING_PATH: extras/ArduinoCore-mbed 41 | ARDUINOCORE_API_STAGING_PATH: extras/ArduinoCore-API 42 | 43 | strategy: 44 | fail-fast: false 45 | 46 | matrix: 47 | board: 48 | - fqbn: arduino:mbed_nano:nano33ble 49 | platforms: | 50 | - name: arduino:mbed_nano 51 | artifact-name-suffix: arduino-mbed_nano-nano33ble 52 | - fqbn: arduino:mbed_nano:nanorp2040connect 53 | platforms: | 54 | - name: arduino:mbed_nano 55 | artifact-name-suffix: arduino-mbed_nano-nanorp2040connect 56 | - fqbn: arduino:mbed_portenta:envie_m7:target_core=cm4 57 | platforms: | 58 | - name: arduino:mbed_portenta 59 | artifact-name-suffix: arduino-mbed_portenta-envie_m7-target_core-cm4 60 | - fqbn: arduino:mbed_portenta:envie_m7:target_core=cm7 61 | platforms: | 62 | - name: arduino:mbed_portenta 63 | artifact-name-suffix: arduino-mbed_portenta-envie_m7 64 | 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v4 68 | 69 | # it's necessary to checkout the platform before installing it so that the ArduinoCore-API dependency can be added 70 | - name: Checkout ArduinoCore-mbed 71 | # this step only needed when the Arduino Mbed OS Boards platform sourced from the repository is being used 72 | uses: actions/checkout@v4 73 | with: 74 | repository: arduino/ArduinoCore-mbed 75 | # the arduino/compile-sketches action will install the platform from this path 76 | path: ${{ env.ARDUINOCORE_MBED_STAGING_PATH }} 77 | 78 | - name: Checkout ArduinoCore-API 79 | # this step only needed when the Arduino Mbed OS Boards platform sourced from the repository is being used 80 | uses: actions/checkout@v4 81 | with: 82 | repository: arduino/ArduinoCore-API 83 | path: ${{ env.ARDUINOCORE_API_STAGING_PATH }} 84 | 85 | - name: Install ArduinoCore-API 86 | # this step only needed when the Arduino Mbed OS Boards platform sourced from the repository is being used 87 | run: | 88 | mv "${{ env.ARDUINOCORE_API_STAGING_PATH }}/api" "${{ env.ARDUINOCORE_MBED_STAGING_PATH }}/cores/arduino" 89 | 90 | - name: Compile examples 91 | uses: arduino/compile-sketches@v1 92 | with: 93 | cli-version: 'arduino_threads' 94 | fqbn: ${{ matrix.board.fqbn }} 95 | libraries: | 96 | ${{ env.UNIVERSAL_LIBRARIES }} 97 | platforms: ${{ matrix.board.platforms }} 98 | sketch-paths: | 99 | ${{ env.UNIVERSAL_SKETCH_PATHS }} 100 | enable-deltas-report: 'true' 101 | sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} 102 | github-token: ${{ secrets.GITHUB_TOKEN }} 103 | verbose: 'true' 104 | 105 | - name: Save memory usage change report as artifact 106 | uses: actions/upload-artifact@v4 107 | with: 108 | name: sketches-report-${{ matrix.board.artifact-name-suffix }} 109 | if-no-files-found: error 110 | path: ${{ env.SKETCHES_REPORTS_PATH }} 111 | -------------------------------------------------------------------------------- /.github/workflows/report-size-deltas.yml: -------------------------------------------------------------------------------- 1 | name: Report Size Deltas 2 | 3 | # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | paths: 7 | - ".github/workflows/report-size-deltas.ya?ml" 8 | schedule: 9 | # Run at the minimum interval allowed by GitHub Actions. 10 | # Note: GitHub Actions periodically has outages which result in workflow failures. 11 | # In this event, the workflows will start passing again once the service recovers. 12 | - cron: "*/5 * * * *" 13 | workflow_dispatch: 14 | repository_dispatch: 15 | 16 | permissions: 17 | pull-requests: write 18 | 19 | jobs: 20 | report: 21 | # Scheduled workflow runs would cause excess GitHub Actions service charges in a private repo 22 | if: github.repository_visibility == 'public' 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Comment size deltas reports to PRs 26 | uses: arduino/report-size-deltas@v1 27 | with: 28 | # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow 29 | sketches-reports-source: ^sketches-report-.+ 30 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | 3 | # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | pull_request: 7 | schedule: 8 | # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates. 9 | - cron: "0 8 * * TUE" 10 | workflow_dispatch: 11 | repository_dispatch: 12 | 13 | jobs: 14 | spellcheck: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Spell check 22 | uses: codespell-project/actions-codespell@master 23 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md 2 | name: Sync Labels 3 | 4 | # See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows 5 | on: 6 | push: 7 | paths: 8 | - ".github/workflows/sync-labels.ya?ml" 9 | - ".github/label-configuration-files/*.ya?ml" 10 | pull_request: 11 | paths: 12 | - ".github/workflows/sync-labels.ya?ml" 13 | - ".github/label-configuration-files/*.ya?ml" 14 | schedule: 15 | # Run daily at 8 AM UTC to sync with changes to shared label configurations. 16 | - cron: "0 8 * * *" 17 | workflow_dispatch: 18 | repository_dispatch: 19 | 20 | env: 21 | CONFIGURATIONS_FOLDER: .github/label-configuration-files 22 | CONFIGURATIONS_ARTIFACT: label-configuration-files 23 | 24 | jobs: 25 | check: 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Download JSON schema for labels configuration file 33 | id: download-schema 34 | uses: carlosperate/download-file-action@v2 35 | with: 36 | file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json 37 | location: ${{ runner.temp }}/label-configuration-schema 38 | 39 | - name: Install JSON schema validator 40 | run: | 41 | sudo npm install \ 42 | --global \ 43 | ajv-cli \ 44 | ajv-formats 45 | 46 | - name: Validate local labels configuration 47 | run: | 48 | # See: https://github.com/ajv-validator/ajv-cli#readme 49 | ajv validate \ 50 | --all-errors \ 51 | -c ajv-formats \ 52 | -s "${{ steps.download-schema.outputs.file-path }}" \ 53 | -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" 54 | 55 | download: 56 | needs: check 57 | runs-on: ubuntu-latest 58 | 59 | strategy: 60 | matrix: 61 | filename: 62 | # Filenames of the shared configurations to apply to the repository in addition to the local configuration. 63 | # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels 64 | - universal.yml 65 | 66 | steps: 67 | - name: Download 68 | uses: carlosperate/download-file-action@v2 69 | with: 70 | file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} 71 | 72 | - name: Pass configuration files to next job via workflow artifact 73 | uses: actions/upload-artifact@v4 74 | with: 75 | path: | 76 | *.yaml 77 | *.yml 78 | if-no-files-found: error 79 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 80 | 81 | sync: 82 | needs: download 83 | runs-on: ubuntu-latest 84 | 85 | steps: 86 | - name: Set environment variables 87 | run: | 88 | # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 89 | echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" 90 | 91 | - name: Determine whether to dry run 92 | id: dry-run 93 | if: > 94 | github.event_name == 'pull_request' || 95 | ( 96 | ( 97 | github.event_name == 'push' || 98 | github.event_name == 'workflow_dispatch' 99 | ) && 100 | github.ref != format('refs/heads/{0}', github.event.repository.default_branch) 101 | ) 102 | run: | 103 | # Use of this flag in the github-label-sync command will cause it to only check the validity of the 104 | # configuration. 105 | echo "::set-output name=flag::--dry-run" 106 | 107 | - name: Checkout repository 108 | uses: actions/checkout@v4 109 | 110 | - name: Download configuration files artifact 111 | uses: actions/download-artifact@v4 112 | with: 113 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 114 | path: ${{ env.CONFIGURATIONS_FOLDER }} 115 | 116 | - name: Remove unneeded artifact 117 | uses: geekyeggo/delete-artifact@v5 118 | with: 119 | name: ${{ env.CONFIGURATIONS_ARTIFACT }} 120 | 121 | - name: Merge label configuration files 122 | run: | 123 | # Merge all configuration files 124 | shopt -s extglob 125 | cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" 126 | 127 | - name: Install github-label-sync 128 | run: sudo npm install --global github-label-sync 129 | 130 | - name: Sync labels 131 | env: 132 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 133 | run: | 134 | # See: https://github.com/Financial-Times/github-label-sync 135 | github-label-sync \ 136 | --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ 137 | ${{ steps.dry-run.outputs.flag }} \ 138 | ${{ github.repository }} 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | This library enables threadsafe peripheral IO access via pipes. 474 | Copyright (C) 2021 Alexander Entinger / Arduino 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `Arduino_Threads` 4 | ================= 5 | *Note: This library is currently in [beta](#zap-caveats).* 6 | 7 | [![Compile Examples status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/compile-examples.yml) 8 | [![Check Arduino status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/check-arduino.yml) 9 | [![Spell Check status](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/Arduino_Threads/actions/workflows/spell-check.yml) 10 | 11 | This library makes it easy to use the multi-threading capability of [Arduino](https://www.arduino.cc/) boards that use an [Mbed OS](https://os.mbed.com/docs/mbed-os/latest/introduction/index.html)-based core library. Additionally this library provides thread-safe access to `Wire`, `SPI` and `Serial` which is relevant when creating multi-threaded sketches in order to avoid common pitfalls such as race-conditions and invalid state. 12 | 13 | Preliminary **documentation** and download links for **required tooling** are available within the [`/docs`](docs/README.md) subfolder. 14 | 15 | ## :star: Features 16 | ### :thread: Multi-threaded sketch execution 17 | Instead of one big state-machine-of-doom you can split your application into multiple independent threads, each with it's own `setup()` and `loop()` function. Instead of implementing your application in a single `.ino` file, each independent thread is implemented in a dedicated `.inot` file (t suffix stands for **t**hread) representing a clear separation of concerns on a file level. 18 | 19 | ### :calling: Easy communication between multiple threads 20 | Easy inter-thread-communication is facilitated via a `Shared` abstraction object providing thread-safe sink/source semantics allowing to safely exchange data of any type between threads. 21 | 22 | ### :shield: Thread-safe I/O 23 | A key problem of multi-tasking is the **prevention of erroneous state when multiple threads share a single resource**. The following example borrowed from a typical application demonstrates the problems resulting from multiple threads accessing a single resource: 24 | 25 | Imagine an embedded system where multiple `Wire` client devices are physically connected to a single `Wire` server. Each `Wire` client device is managed by a separate software thread. Each thread polls its `Wire` client device periodically. Access to the I2C bus is managed via the `Wire` library and typically follows this pattern: 26 | 27 | ```C++ 28 | /* Wire Write Access */ 29 | Wire.beginTransmission(address); 30 | Wire.write(value); 31 | // Interrupting the current thread e.g. at this point can lead to an erroneous state 32 | // if another thread performs Wire I/O before the transmission ends. 33 | Wire.endTransmission(); 34 | 35 | /* Wire Read Access */ 36 | Wire.requestFrom(address, bytes) 37 | while(Wire.available()) { 38 | int value = Wire.read(); 39 | } 40 | ``` 41 | 42 | Since we are using [ARM Mbed OS](https://os.mbed.com/mbed-os/) which is a [preemptive](https://en.wikipedia.org/wiki/Preemption_(computing)) [RTOS](https://en.wikipedia.org/wiki/Real-time_operating_system) for achieving multi-tasking capability and under the assumption that all threads share the same priority (which leads to a [round-robin](https://en.wikipedia.org/wiki/Round-robin_scheduling) scheduling) it can easily happen that one thread is half-way through its Wire I/O access when the scheduler interrupts its execution and schedules the next thread which in turn starts, continues or ends its own Wire I/O access. 43 | 44 | As a result this interruption by the scheduler will break Wire I/O access for both devices and leave the Wire I/O controller in an undefined state :fire:. 45 | 46 | `Arduino_Threads` solves this problem by encapsulating the complete I/O access (e.g. reading from a `Wire` client device) within a single function call which generates an I/O request to be asynchronously executed by a high-priority I/O thread. The high-priority I/O thread is the **only** instance which directly communicates with physical hardware. 47 | 48 | ### :runner: Asynchronous 49 | The mechanisms implemented in this library allow any thread to dispatch an I/O request asynchronously and either continue its operation or [yield](https://en.wikipedia.org/wiki/Yield_(multithreading)) control to the next scheduled thread. All I/O requests are stored in a queue and are executed within a high-priority I/O thread after a [context-switch](https://en.wikipedia.org/wiki/Context_switch). An example of this can be seen [here](examples/Threadsafe_IO/SPI/SPI.ino). 50 | 51 | ### :relieved: Convenient API 52 | Although you are free to directly manipulate I/O requests and responses (e.g. [Wire](examples/Threadsafe_IO/Wire/Wire.ino)) there are convenient `read`/`write`/`writeThenRead` abstractions inspired by the [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) library (e.g. [Wire_BusIO](examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino)). 53 | 54 | ## :zap: Caveats 55 | 56 | This library is currently in **BETA** phase. This means that neither the API nor the usage patterns are set in stone and are likely to change. We are publishing this library in the full knowledge that we can't foresee every possible use-case and edge-case. Therefore we would like to treat this library, while it's in beta phase, as an experiment and ask for your input for shaping this library. Please help us by providing feedback in the [issues section](https://github.com/arduino-libraries/Arduino_Threads/issues) or participating in our [discussions](https://github.com/arduino-libraries/Arduino_Threads/discussions). 57 | 58 | ## :mag_right: Resources 59 | 60 | * [How to install a library](https://www.arduino.cc/en/guide/libraries) 61 | * [Help Center](https://support.arduino.cc/) - Get help from Arduino's official support team 62 | * [Forum](https://forum.arduino.cc) - Get support from the community 63 | 64 | ## :bug: Bugs & Issues 65 | 66 | If you found an issue in this library, you can submit it to the [issue tracker](issues) of this repository. Remember to include as much detail as you can about your hardware set-up, code and steps for reproducing the issue. To prevent hardware related incompatibilities make sure to use an [original Arduino board](https://support.arduino.cc/hc/en-us/articles/360020652100-How-to-spot-a-counterfeit-Arduino). 67 | 68 | ## :technologist: Contribute 69 | 70 | There are many ways to contribute: 71 | 72 | * Improve documentation and examples 73 | * Fix a bug 74 | * Test open Pull Requests 75 | * Implement a new feature 76 | * Discuss potential ways to improve this library 77 | 78 | You can submit your patches directly to this repository as Pull Requests. Please provide a detailed description of the problem you're trying to solve and make sure you test on real hardware. 79 | 80 | ## :yellow_heart: Donations 81 | 82 | This open-source code is maintained by Arduino with the help of the community. We invest a considerable amount of time in testing code, optimizing it and introducing new features. Please consider [donating](https://www.arduino.cc/en/donate/) or [sponsoring](https://github.com/sponsors/arduino) to support our work, as well as [buying original Arduino boards](https://store.arduino.cc/) which is the best way to make sure our effort can continue in the long term. 83 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | `Arduino_Threads/docs` 4 | ====================== 5 | The Arduino threading APIs bring multi-threading to the world of Arduino. If you're new to the concept of threads we suggest you first take a look at the [Threading Basics](threading-basics.md) document. 6 | 7 | The following Arduino architectures and boards are currently supported: 8 | * `mbed_portenta`: [Portenta H7](https://store.arduino.cc/products/portenta-h7) 9 | * `mbed_nano`: [Nano 33 BLE](https://store.arduino.cc/arduino-nano-33-ble), [Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect) 10 | * `mbed_edge`: [Edge Control](https://store.arduino.cc/products/arduino-edge-control) 11 | * `mbed_nicla`: [Nicla Sense ME](https://store.arduino.cc/products/nicla-sense-me), [Nicla Vision](http://store.arduino.cc/products/nicla-vision) 12 | 13 | ### Tooling 14 | 15 | Threading with the above mentioned Arduino cores can be achieved by leveraging the [Arduino_Threads](https://github.com/bcmi-labs/Arduino_Threads) library in combination with the [Arduino Command Line Interface](https://github.com/facchinm/arduino-cli/commits/arduino_threads_rebased) (arduino-cli). 16 | 17 | For those who are unsure how to use the Arduino CLI in combination with multi-threading take a look a condensed [getting started guide](cli-getting-started.md). There's also the comprehensive [arduino-cli documentation](https://arduino.github.io/arduino-cli/getting-started) to consider. 18 | 19 | Download a preliminary, pre-built `arduino-cli` binary (experimental) or patched `Arduino IDE` (very experimental) for your operating system that supports threading: 20 | | Platform | Download CLI | Download IDE | 21 | |:-:|:-:|:-:| 22 | | MacOS_64Bit | [`arduino-cli`](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_macOS_64bit.tar.gz) | [`Arduino IDE`](https://downloads.arduino.cc/ide_staging/arduino_threads/arduino-PR-ae92bd4498e867b07581d1d4191be14b9ef0f69a-BUILD-65-macosx.zip) | 23 | | Linux_32Bit | [`arduino-cli`](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_32bit.tar.gz) | [`Arduino IDE`](https://downloads.arduino.cc/ide_staging/arduino_threads/arduino-PR-ae92bd4498e867b07581d1d4191be14b9ef0f69a-BUILD-65-linux32.tar.xz) | 24 | | Linux_64Bit | [`arduino-cli`](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_64bit.tar.gz) | [`Arduino IDE`](https://downloads.arduino.cc/ide_staging/arduino_threads/arduino-PR-ae92bd4498e867b07581d1d4191be14b9ef0f69a-BUILD-65-linux64.tar.xz) | 25 | | Linux_ARMv6 | [`arduino-cli`](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_ARMv6.tar.gz) | | 26 | | Linux_ARMv7 | [`arduino-cli`](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Linux_ARMv7.tar.gz) | [`Arduino IDE`](https://downloads.arduino.cc/ide_staging/arduino_threads/arduino-PR-ae92bd4498e867b07581d1d4191be14b9ef0f69a-BUILD-65-linuxarm.tar.xz) | 27 | | Windows_32Bit | [`arduino-cli`](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Windows_32bit.zip) | | 28 | | Windows_64Bit | [`arduino-cli`](https://downloads.arduino.cc/tools/arduino-cli/inot_support/arduino-cli_git-snapshot_Windows_64bit.zip) | [`Arduino IDE`](https://downloads.arduino.cc/ide_staging/arduino_threads/arduino-PR-ae92bd4498e867b07581d1d4191be14b9ef0f69a-BUILD-65-windows.zip) | 29 | 30 | ### Table of Contents 31 | 32 | In the following documents you will find more information about the topics covered by this library. 33 | 34 | 1. [Threading Basics](threading-basics.md) 35 | 2. [Data exchange between threads](data-exchange.md) 36 | 3. [Threadsafe `Serial`](threadsafe-serial.md) 37 | 4. [Threadsafe `Wire`](threadsafe-wire.md) 38 | 5. [Threadsafe `SPI`](threadsafe-spi.md) 39 | -------------------------------------------------------------------------------- /docs/assets/boardlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/docs/assets/boardlist.png -------------------------------------------------------------------------------- /docs/assets/compiled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/docs/assets/compiled.png -------------------------------------------------------------------------------- /docs/assets/pathupdate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/docs/assets/pathupdate.png -------------------------------------------------------------------------------- /docs/assets/uploaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/docs/assets/uploaded.png -------------------------------------------------------------------------------- /docs/cli-getting-started.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | How to get started with the `Arduino CLI` and `Arduino_Threads` 4 | =============================================================== 5 | ## Introduction 6 | For now, you need to use the Arduino CLI to upload sketches made with the `Arduino_Threads` library. It can be intimidating at first, but once you get the hang of it, it is not that different from working in the IDE. 7 | 8 | This document will help you get started, showing you how to find all the information you need to get off the ground without needing to go in depth and learn everything there is to know about the CLI. 9 | 10 | ## Installing 11 | To use the `Arduino_Threads` library, a special version of the Arduino CLI needs to be downloaded and installed. Installing the Arduino CLI is really just as simple as downloading it and extracting the files from the .zip archive, once it is on your computer it is installed. However, to keep your workflow tidy and organized, we recommend moving it to a directory with a short path. If you have been using the Arduino IDE, most likely the files are stored in your `Documents` folder, so let's put the CLI files in the same place. 12 | 13 | Make a new folder in your `Documents` folder, and name it `CLI`, extract the files from the .zip archive to this folder. 14 | 15 | ## Navigating With the Command Prompt and Launching the Arduino CLI 16 | Since the Arduino CLI is a command line tool, it is not a program that you can launch by double clicking on the `.exe` file. Instead, we need to start it from the command prompt. Using the command prompt can be thought of like using the file explorer, you are in a folder, can move around to other folders, create new files, delete files, etc. The difference is that you do all of these things with commands instead of buttons. 17 | 18 | Unlike the file explorer where you click the folder you want to go to, navigating in the command prompt is done by first writing the command letting the program know that we want to move somewhere new, then telling it where we want to go. 19 | 20 | When you open up a command prompt window, you should be started in the root directory of your user profile, you can see in what folder you are by looking at the path text right where you enter your commands. Because you're started at the root of your user profile, the path should say `C:\Users\` 21 | 22 | Inside of this root folder there are other folders, if you want to go into the `Documents` folder, instead of clicking on it like you would do in the file explorer, you write the command: 23 | ``` 24 | cd Documents 25 | ``` 26 | and press enter. Notice how, after executing this command, the path updates to include the folder name of the folder you just went inside of. This part is super important, and it is also where many make small mistakes that keep anything from working the way it should, but if you pay close attention to what folder you are inside of, you can avoid these mistakes. 27 | 28 | ![Path update](./assets/pathupdate.png) 29 | 30 | If you want to go back one step, to the folder you came from, you write this command: 31 | 32 | ``` 33 | cd .. 34 | ``` 35 | But we don't need to do that now, we want to be inside of the `CLI` folder, that is inside of the `Documents` folder. 36 | 37 | Navigate to it with the command: 38 | ``` 39 | cd Documents/CLI 40 | ``` 41 | 42 | Once inside of `CLI`, you're ready to launch the Arduino CLI. Launching it this way, ensures that you are launching the special version of the CLI that can handle the `Arduino_Threads` library. Execute this command: 43 | ``` 44 | start arduino-cli.exe 45 | ``` 46 | 47 | If it seems like nothing happened - great! Then it probably worked. If you got an error, such as a message saying the file could not be found, a few things to check are: 48 | 49 | - That you are in the right path, if you've copied the setup suggested in this guide, the directory you want to be in should have the path `C:\Users\\Documents\CLI` 50 | - That the arduino-cli.exe file is inside of this folder 51 | - That there aren't any small spelling-errors 52 | 53 | Once you believe that you have successfully started the Arduino CLI, confirm by executing the command 54 | ``` 55 | arduino-cli 56 | ``` 57 | If you get a list of all the available commands, everything is as it should, and you can move on to the next step. 58 | 59 | ## Finding Information About your Board 60 | To compile sketches and to upload to your Arduino board, you need to know some information about the board you have connected. In this step we're going to find all the information we need. Connect your Arduino board to your computer, and execute the command: 61 | ``` 62 | arduino-cli board list 63 | ``` 64 | If your board was found, you will get a bunch of information about it. The information we're interested in is the `FQBN`, and the port. `FQBN` stands for "Fully Qualified Board Name", and is how the computer refers to the board. The port is like an address for the board. 65 | 66 | ![Board list](./assets/boardlist.png) 67 | 68 | If you are, like we are, using an Arduino Nano 33 BLE Sense, the `FQBN` will be `arduino:mbed_nano:nano33ble`. We need to specify every time we are compiling and uploading sketches that they are to be compiled for this board, just like we do in the IDE. 69 | 70 | ## Installing Cores 71 | If you have already used your Arduino board with the IDE, you can skip this step entirely. Otherwise, there are some files you may need to install to be able to use the board. But don't worry, it's just one command. The Arduino Nano 33 BLE Sense uses the `arduino:mbed_nano` core, which is another piece of information that we got from the board list command we executed, so we need to install that before using the board. 72 | 73 | If you are unsure whether or not you have the core installed, go through with this step. If it is already installed, it won't do any harm to do it again. 74 | 75 | To install the core, execute this command: 76 | ``` 77 | arduino-cli core install arduino:mbed_nano 78 | ``` 79 | If you are using a board with another core, replace `arduino:mbed_nano` in the command with whatever core you got from the board list. 80 | 81 | ## Make a Sketch 82 | Now it is time to either make a new sketch, or to place a sketch that you have already made or want to test in the `CLI` folder. 83 | 84 | If you want to make a new sketch, use the command: 85 | ``` 86 | arduino-cli sketch new MySketch 87 | ``` 88 | and a new folder will be created inside of the `CLI` folder, this new folder is named `MySketch` and contains a file named `MySketch.ino`. This .ino file can be opened, and edited in your text-editor of choice. Once you are happy with the sketch, move on to the next step. 89 | 90 | If you already have a sketch that you want to upload, find it and move it into the `CLI` folder. Be sure to move the folder containing the `.ino` file and any extra files included in the sketch, and not the `.ino` file on its own. 91 | 92 | ## Compile and Upload 93 | 94 | The final steps to do are to compile and to upload the sketch. We'll do this with two separate commands. To compile, we need to pass the FQBN in the command to specify what board we want to compile it for, and we need to tell it what sketch we want to compile. We'll compile the sketch we created earlier to be uploaded to an Arduino Nano 33 BLE Sense. We can do this with the command: 95 | ``` 96 | arduino-cli compile --fqbn arduino:mbed_nano:nano33ble MySketch 97 | ``` 98 | Once you execute this command, it's going to start compiling unless something went wrong. If it looks like nothing is happening - Great! That probably means that everything is going as it should. Depending on the core, compiling might take a few minutes. Be patient, it may seem like it's taking too long, but give it time. Eventually, you'll get a statement about the memory use, libraries included, and platform used. 99 | 100 | ![Compiled sketch statement](./assets/compiled.png) 101 | 102 | Now that the sketch is compiled, let's upload it! For this, we need to specify the FQBN again, as well as the port the board is connected to, so it's uploaded to the right place. To find the port, go back to the board list we got earlier, ours is connected to port `COM14`, but replace this part of the command with whatever port your list says. Now execute the command: 103 | ``` 104 | arduino-cli upload -p COM14 --fqbn arduino:mbed_nano:nano33ble MySketch 105 | ``` 106 | If everything went as it should, the sketch is now uploading to the board, and once that is done, you've successfully used the Arduino CLI to upload a sketch to your board. 107 | 108 | ![Uploaded sketch](./assets/uploaded.png) 109 | 110 | Now that you know how to do this, it might be a good opportunity to take the deep dive in the DIY-spirit, experiment with different workflows and interfaces for the terminal to tailor your Arduino experience to your liking. 111 | -------------------------------------------------------------------------------- /docs/data-exchange.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Data Exchange between Threads 4 | ============================= 5 | ## Introduction 6 | When an Arduino sketch formerly consisting of just a single `loop()` is split into multiple `loop()` functions contained in multiple `*.inot` files it becomes necessary to define a thread-safe mechanism to exchange data between those threads. For example, one `*.inot` file could calculate a math formula and send the result back to the main `*.ino` file to act upon it. Meanwhile the main `*.ino` file can take care of other tasks. 7 | 8 | `Arduino_Threads` supports two different mechanisms for data exchange between threads: `Shared` variables and `Sink`/`Source` semantics. Both have their pros and cons as described below. 9 | 10 | ## `Shared` 11 | A `Shared` variable is a global variable accessible to all threads. It can be declared within a header file named `SharedVariables.h` which is automatically included at the top of each `*.inot`-file : 12 | ```C++ 13 | /* SharedVariables.h */ 14 | SHARED(counter, int); /* A globally available, threadsafe, shared variable of type 'int'. */ 15 | /* ... or ... */ 16 | SHARED(counter, int, 8); /* Same as before, but now the internal queue size is defined as 8. */ 17 | ``` 18 | Writing to and reading from the shared variable may not always happen concurrently. I.e. a thread reading sensor data may update the shared variable faster than a slower reader thread would extract those values. Therefore the shared variable is modeled as a queue which can store (buffer) a certain number of entries. That way the slower reader thread can access all the values in the same order as they have been written. 19 | New values can be inserted by using the `push` function that you may know from other data structures. 20 | 21 | ```C++ 22 | /* Thread_1.inot */ 23 | counter.push(10); /* Store a value into the shared variable in a threadsafe manner. */ 24 | ``` 25 | If the internal queue is full the oldest element is discarded and the latest element is inserted into the queue. 26 | 27 | Stored data can be retrieved by using the `pop` function: 28 | ```C++ 29 | /* Thread_2.inot */ 30 | Serial.println(counter.pop()); /* Retrieves a value from the shared variable in a threadsafe manner. */ 31 | ``` 32 | 33 | Should the internal queue be empty when trying to read the latest available value then the thread reading the shared variable will be suspended and the next available thread will be scheduled. Once a new value is stored inside the shared variable the suspended thread resumes operation and consumes the value which has been stored in the internal queue. 34 | Since shared variables are globally accessible from every thread, each thread can read from or write to the shared variable. The user is responsible for using the shared variable in a responsible and sensible way, i.e. reading a shared variable from different threads is generally a bad idea, as on every read an item is removed from the queue within the shared variable and other threads can't access the read value anymore . 35 | 36 | ## `Sink`/`Source` 37 | The idea behind the `Sink`/`Source` semantics is to model data exchange between one data producer (`Source`) and one or multiple data consumers (`Sink`). A data producer or `Source` can be declared in any `*.ino` or `*.inot`-file using the `SOURCE` macro: 38 | ```C++ 39 | /* DataProducerThread.inot */ 40 | SOURCE(counter, int); /* Declaration of a data source of type `int`. */ 41 | ``` 42 | In a similar way, a data consumer can be declared in any `*.ino` or `*.inot`-file using the `SINK` macro. In difference to `Shared` where the size of the internal queue is globally set for all shared variables you can define your desired internal buffer size separately for each `Sink`. 43 | ```C++ 44 | /* DataConsumerThread_1.inot */ 45 | SINK(counter, int); /* Declaration of a data sink of type `int` with a internal queue size of '1'. */ 46 | ``` 47 | ```C++ 48 | /* DataConsumerThread_2.inot */ 49 | SINK(counter, int, 10); /* Declaration of a data sink of type `int` with a internal queue size of '10'. */ 50 | ``` 51 | In order to actually facilitate the flow of data from a source to a sink the sinks must be connected to the desired data source. This is done within the main `ino`-file: 52 | ```C++ 53 | /* MySinkSourceDemo.ino */ 54 | CONNECT(DataProducerThread, counter, DataConsumerThread_1, counter); 55 | CONNECT(DataProducerThread, counter, DataConsumerThread_2, counter); 56 | ``` 57 | Whenever a new value is assigned to a data source, i.e. 58 | ```C++ 59 | /* DataProducerThread.inot */ 60 | counter.push(10); 61 | ``` 62 | data is automatically copied and stored within the internal queues of all connected data sinks, from where it can be retrieved, i.e. 63 | ```C++ 64 | /* DataConsumerThread_1.inot */ 65 | Serial.println(counter.pop()); 66 | ``` 67 | ```C++ 68 | /* DataConsumerThread_2.inot */ 69 | Serial.println(counter.pop()); 70 | ``` 71 | If a thread tries to read from an empty `Sink` the thread is suspended and the next ready thread is scheduled. When a new value is written to a `Source` and consequently copied to a `Sink` the suspended thread is resumed and continuous execution (i.e. read the data and act upon it). 72 | 73 | Since the data added to the source is copied multiple threads can read data from a single source without data being lost. This is an advantage compared to a simple shared variable. Furthermore you cannot accidentally write to a `Sink` or read from a `Source`. Attempting to do so results in a compilation error. 74 | 75 | ## Comparison 76 | | | :+1: | :-1: | 77 | |:---:|:---:|:---:| 78 | | `Shared` | :+1: Needs to be declared only once (in `SharedVariables.h`). | :-1: Basically a global variable, with all the disadvantages those entail.
:-1: Size of internal queue fixed for ALL shared variables.
:-1: No protection against misuse (i.e. reading from multiple threads).
| 79 | | `Sink`/`Source` | :+1: Define internal queue size separately for each `Sink`.
:+1: Supports multiple data consumers for a single data producer.
:+1: Read/Write protection: Can't read from `Source`, can't write to `Sink`.
:+1: Mandatory connecting (plumbing) within main `*.ino`-file makes data flows easily visible.
| :-1: Needs manual connection (plumbing) to connect `Sink`'s to `Source`'s. | 80 | -------------------------------------------------------------------------------- /docs/threading-basics.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threading Basics 4 | ================ 5 | ## Introduction 6 | Threading is a concept that is used on many operating systems to run tasks in parallel. An Arduino example of two such tasks could be to read the position of a potentiometer knob while controlling a servo motor to follow that position. Running such tasks in parallel is also called multitasking. 7 | 8 | Previously Arduino sketches didn't support the concept of multitasking, unless you took specific measures to support it. With this so called single-threaded approach instructions in your Arduino sketch are executed sequentially one by one. If an instruction or a function call respectively makes the runtime environment wait for its execution to complete, it's called a "blocking" function. You may have encountered the limitations of this when interacting with multiple sensors and actuators at once. For example if you let a servo motor react to the data read from a potentiometer as mentioned above. While the servo motor is moving to its target position no further reading of the potentiometer can be done because the program waits until the servo is done moving. To solve this issue multitasking can be used which allows to "simultaneously" execute multiple task such as reading from a sensor and controlling a motor. In the context of multitasking a thread is basically a mechanism (provided usually by the operating system) to encapsulate a task to be run concurrently with others. 9 | 10 | ![Example of two tasks that ideally run in parallel.](assets/Arduino-Threads-Tasks-Example.svg) 11 | 12 | In the historic single-threaded execution of Arduino sketches the complete program logic is contained within the `*.ino` file. It contains both a `setup()` function, which is executed only once at program start, and a `loop()` function, which is executed indefinitely. A single-threaded Arduino project can theoretically contain multiple `*.ino` files but you can define `setup()` or `loop()` only once. 13 | In order to support multi-threaded (or parallel) sketch execution a new file type called the `*.inot` file is introduced. 14 | 15 | The advantage of this approach is that a complex and lengthy `loop()` function (potentially consisting of nested [finite state machines](https://en.wikipedia.org/wiki/Finite-state_machine)) found in typical Arduino sketches can be broken up into several, parallelly executed `loop()` functions with a much smaller scope. This not only increases program readability and maintainability but as a result leads to a reduction of software errors (bugs). 16 | 17 | #### Example (Single-Threaded): 18 | This sketch demonstrates how one would implement a program which requires the execution of three different actions on three different periodic intervals. In this example we blink three different LEDs at three different intervals. 19 | 20 | ![Diagram showing the sequential execution of the tasks](assets/Arduino-Threads-Sequential.svg) 21 | 22 | **Blink_Three_LEDs.ino**: 23 | 24 | ```C++ 25 | void setup() 26 | { 27 | pinMode(LED_RED, OUTPUT); 28 | pinMode(LED_GREEN, OUTPUT); 29 | pinMode(LED_BLUE, OUTPUT); 30 | } 31 | 32 | int const DELAY_RED_msec = 900; 33 | int const DELAY_GREEN_msec = 500; 34 | int const DELAY_BLUE_msec = 750; 35 | 36 | void loop() 37 | { 38 | static unsigned long prev_red = millis(); 39 | static unsigned long prev_green = millis(); 40 | static unsigned long prev_blue = millis(); 41 | 42 | unsigned long const now = millis(); 43 | 44 | if ((now - prev_red) > DELAY_RED_msec) { 45 | prev_red = now; 46 | digitalWrite(LED_RED, !digitalRead(LED_RED)); 47 | } 48 | 49 | if ((now - prev_green) > DELAY_GREEN_msec) { 50 | prev_green = now; 51 | digitalWrite(LED_GREEN, !digitalRead(LED_GREEN)); 52 | } 53 | 54 | if ((now - prev_blue) > DELAY_BLUE_msec) { 55 | prev_blue = now; 56 | digitalWrite(LED_BLUE, !digitalRead(LED_BLUE)); 57 | } 58 | } 59 | ``` 60 | You can imagine that with increasing complexity of a sketch it gets quite difficult to keep track of the different states used for the different sub-tasks (here: blinking each of the LEDs). 61 | 62 | #### Example (Multi-Threaded): 63 | 64 | The same functionality can be provided via multi-threaded execution in a much cleaner way by splitting up the tasks into separate files / threads. 65 | 66 | ![Diagram showing the parallel execution of the tasks](assets/Arduino-Threads-Parallel.svg) 67 | 68 | **Blink_Three_LEDs.ino** 69 | 70 | ```C++ 71 | void setup() { 72 | // Start the task defined in the corresponding .inot file 73 | LedRed.start(); 74 | LedGreen.start(); 75 | LedBlue.start(); 76 | } 77 | 78 | void loop() { 79 | } 80 | ``` 81 | **LedRed.inot** 82 | ```C++ 83 | void setup() { 84 | pinMode(LED_RED, OUTPUT); 85 | } 86 | 87 | int const DELAY_RED_msec = 900; 88 | 89 | void loop() { 90 | digitalWrite(LED_RED, !digitalRead(LED_RED)); 91 | delay(DELAY_RED_msec); 92 | } 93 | ``` 94 | **LedGreen.inot** 95 | ```C++ 96 | void setup() { 97 | pinMode(LED_GREEN, OUTPUT); 98 | } 99 | 100 | int const DELAY_GREEN_msec = 500; 101 | 102 | void loop() { 103 | digitalWrite(LED_GREEN, !digitalRead(LED_GREEN)); 104 | delay(DELAY_GREEN_msec); 105 | } 106 | ``` 107 | **LedBlue.inot** 108 | ```C++ 109 | void setup() { 110 | pinMode(LED_BLUE, OUTPUT); 111 | } 112 | 113 | int const DELAY_BLUE_msec = 750; 114 | 115 | void loop() { 116 | digitalWrite(LED_BLUE, !digitalRead(LED_BLUE)); 117 | delay(DELAY_BLUE_msec); 118 | } 119 | ``` 120 | As you can see from the example the name of the `*.inot`-file is used to generate a class and instantiate an object with the **same name** as the `*.inot`-file. Hence the `*.inot`-file can be only named in concordance with the rules to declare a variable in C++. `*.inot`-file names: 121 | * must begin with a letter of the alphabet or an underscore (_). 122 | * can contain letters and numbers after the first initial letter. 123 | * are case sensitive. 124 | * cannot contain spaces or special characters. 125 | * cannot be a C++ keyword (i.e. `register`, `volatile`, `while`, etc.). 126 | 127 | To be consistent with the Arduino programming style we recommend using [camel case](https://en.wikipedia.org/wiki/Camel_case) for the file names. 128 | -------------------------------------------------------------------------------- /docs/threadsafe-serial.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Thread-safe `Serial` 4 | =================== 5 | ## Introduction 6 | While both `SPI` and `Wire` are communication protocols which explicitly exist to facilitate communication between one server device and multiple client devices there are no such considerations for the `Serial` interface. `Serial` communication usually exists in a one-to-one mapping between two communication partners of equal power (both can initiate communication on their own right, there is no server/client relationship). 7 | 8 | One example would be an Arduino sketch sending AT commands to a modem over a Serial connection and interpreting the result of those commands. Another example would be a GPS unit sending NMEA encoded location data to the Arduino for parsing. In both cases the only sensible software representation for such functionality (AT command module or NMEA message parser) is a single thread. Also in both cases it is undesirable for other threads to inject other kind of data into the serial communication as this would only confuse i.e. the AT controlled modem which reads that data. 9 | 10 | A good example for multiple threads writing to `Serial` would be logging where mixing messages from different sources doesn't cause any harm. A possible example for multiple threads reading from `Serial` would be to i.e. split an NMEA parser across multiple threads, i.e. one thread only parses RMC-messages, another parses GGA-messages, etc. In any case the thread-safe `Serial` supports both single-writer/single-reader and multiple-write/multiple-reader scenarios. 11 | 12 | ## Initialize Serial with `begin()` 13 | In order to initialize the serial interface for any given thread `Serial.begin(baudrate)` needs to be called in each thread's `setup()` which desires to have **writing** access to the serial interface. Since it does not make sense to support multiple different baudrates (i.e. Thread_A writing with 9600 baud and Thread_B writing with 115200 baud - if you really need this, spread the attached serial devices to different serial ports), the first thread to call `Serial.begin()` locks in the baud rate configuration for all other threads. A sensible approach is to call `Serial.begin()` within the main `*.ino`-file and only then start the other threads, i.e. 14 | ```C++ 15 | /* MyThreadsafeSerialDemo.ino */ 16 | void setup() 17 | { 18 | Serial.begin(9600); 19 | while (!Serial) { } 20 | /* ... */ 21 | Thread_1.start(); 22 | Thread_2.start(); 23 | /* ... */ 24 | } 25 | ``` 26 | ```C++ 27 | /* Thread_1.inot */ 28 | void setup() { 29 | Serial.begin(9600); // Baud rate will be neglected as it's set in the main file 30 | } 31 | ``` 32 | ```C++ 33 | /* Thread_2.inot */ 34 | void setup() { 35 | Serial.begin(9600); // Baud rate will be neglected as it's set in the main file 36 | } 37 | ``` 38 | 39 | ## Write to Serial using `print()`/`println()` 40 | ([`examples/Threadsafe_IO/Serial_Writer`](../examples/Threadsafe_IO/Serial_Writer)) 41 | 42 | Since the thread-safe `Serial` is derived from [`arduino::HardwareSerial`](https://github.com/arduino/ArduinoCore-API/blob/master/api/HardwareSerial.h) it supports the complete `Serial` API. This means that you can simply write to the `Serial` interface as you would do with a single threaded application. 43 | ```C++ 44 | Serial.print("This is a test message #"); 45 | Serial.println(counter); 46 | ``` 47 | 48 | ### Prevent message break-up using `block()`/`unblock()` 49 | ([`examples/Threadsafe_IO/Serial_Writer`](../examples/Threadsafe_IO/Serial_Writer)) 50 | 51 | Due to the preemptive nature of the underlying mbed-os threading mechanism a multi-line sequence of `Serial.print/println()` could be interrupted at any point in time. When multiple threads are writing to the same Serial interface, this can lead to jumbled-up messages. 52 | 53 | ```C++ 54 | /* Thread_1.inot */ 55 | void loop() { 56 | Serial.print("This "); 57 | Serial.print("is "); 58 | Serial.print("a "); 59 | Serial.print("multi-line "); 60 | Serial.print("log "); 61 | /* Interruption by scheduler and context switch. */ 62 | Serial.print("message "); 63 | Serial.print("from "); 64 | Serial.print("thread #1."); 65 | Serial.println(); 66 | } 67 | ``` 68 | ```C++ 69 | /* Thread_2.inot */ 70 | void loop() { 71 | Serial.print("This "); 72 | Serial.print("is "); 73 | Serial.print("a "); 74 | Serial.print("multi-line "); 75 | Serial.print("log "); 76 | Serial.print("message "); 77 | Serial.print("from "); 78 | Serial.print("thread #2."); 79 | Serial.println(); 80 | } 81 | ``` 82 | The resulting serial output of `Thread_1` being interrupted at the marked spot and `Thread_2` being scheduled can be seen below: 83 | ```bash 84 | This is a multi-line log This is a multi-line log message from thread #2. 85 | message from thread #1. 86 | 87 | ``` 88 | In order to prevent such break-ups a `block()`/`unblock()` API is introduced which ensures that the messages are printed in the intended order, i.e. 89 | ```C++ 90 | /* Thread_1.inot */ 91 | void loop() { 92 | Serial.block(); 93 | Serial.print("This "); 94 | Serial.print("is "); 95 | /* ... */ 96 | Serial.print("thread #1."); 97 | Serial.println(); 98 | Serial.unblock(); 99 | } 100 | ``` 101 | ```C++ 102 | /* Thread_2.inot */ 103 | void loop() { 104 | Serial.block(); 105 | Serial.print("This "); 106 | Serial.print("is "); 107 | /* ... */ 108 | Serial.print("thread #2."); 109 | Serial.println(); 110 | Serial.unblock(); 111 | } 112 | ``` 113 | Now the thread messages are printed in the order one would expect: 114 | ```bash 115 | This is a multi-line log message from thread #2. 116 | This is a multi-line log message from thread #1. 117 | ``` 118 | 119 | ## Read from `Serial` 120 | ([`examples/Threadsafe_IO/Serial_Reader`](../examples/Threadsafe_IO/Serial_Reader)) 121 | 122 | Reading from the `Serial` interface can be accomplished using the `read()`, `peek()` and `available()` APIs. 123 | ```C++ 124 | /* Thread_1.inot */ 125 | void loop() 126 | { 127 | /* Read data from the serial interface into a String. */ 128 | String serial_msg; 129 | while (Serial.available()) 130 | serial_msg += (char)Serial.read(); 131 | /* ... */ 132 | } 133 | ``` 134 | Whenever a thread first calls any of those three APIs a thread-specific receive ring buffer is created. From that moment on any incoming serial communication is copied into that buffer. Maintaining a copy of the serial data in a dedicated buffer per thread prevents "data stealing" from other threads in a multiple reader scenario, where the first thread to call `read()` would in fact receive the data and all other threads would miss out on it. 135 | -------------------------------------------------------------------------------- /docs/threadsafe-spi.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Thread-safe `SPI` 4 | =============== 5 | ## Introduction 6 | `SPI` communication shares the same problems that [have been outlined](threadsafe-wire.md) for using `Wire` in a multithreaded environment. This is due to the fact that every `SPI` transaction consists of multiple function calls, i.e. 7 | 8 | ```C++ 9 | digitalWrite(cs, LOW); 10 | SPI.beginTransaction(SPI_SETTINGS); 11 | rx = SPI.transfer(tx); 12 | SPI.endTransaction(); 13 | digitalWrite(cs, HIGH); 14 | ``` 15 | Due to the preemptive nature of the underlying RTOS the execution of the `SPI` transaction can be interrupted at any point in time. This would leave both the `SPI` driver code and the client device in a undefined state. Similar to what has been implemented for `Wire`, Arduino Parallela solves this by introducing a `BusDevice` which allows for single-function calls. The Parallela API allows for thread-safe, atomic SPI transactions. A `BusDevice` is declared simply by specifying the type of the interface and its parameters: 16 | ```C++ 17 | int const DEVICE_CS_PIN = 4; 18 | void device_cs_select() { /* ... */ } 19 | void device_cs_deselect() { /* ... */ } 20 | /* ... */ 21 | BusDevice bmp388(SPI, DEVICE_CS_PIN, spi_settings); 22 | /* or */ 23 | BusDevice bmp388(SPI, DEVICE_CS_PIN, spi_clock, spi_bit_order, spi_bit_mode); 24 | /* or */ 25 | BusDevice bmp388(SPI, device_cs_select, device_cs_deselect, spi_settings); 26 | ``` 27 | 28 | ### Asynchronous thread-safe `SPI` access with `transfer`/`wait` 29 | Once a `BusDevice` is declared it can be used to transfer data to and from the peripheral by means of the `transfer()` API. As opposed to the traditional Arduino bus APIs, `transfer()` is asynchronous and thus won't block execution unless the `wait()` function is called. 30 | Note that we are in a parallel programming environment which means that calls to `transfer()` on the same bus from different sketches will be arbitrated. 31 | 32 | ```C++ 33 | byte bmp388_read_reg(byte const reg_addr) 34 | { 35 | /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ 36 | byte read_write_buffer[] = {0x80 | reg_addr, 0, 0}; 37 | 38 | IoRequest request(read_write_buffer, sizeof(read_write_buffer), nullptr, 0); 39 | IoResponse response = bmp388.transfer(request); 40 | /* Do other stuff */ 41 | response->wait(); /* Wait for the completion of the IO Request. */ 42 | auto value = read_write_buffer[2]; 43 | return value; 44 | } 45 | ``` 46 | 47 | ### Synchronous thread-safe `SPI` access with `transferAndWait` 48 | ([`examples/Threadsafe_IO/SPI`](../examples/Threadsafe_IO/SPI)) 49 | 50 | As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transferAndWait`. 51 | ```C++ 52 | byte bmp388_read_reg(byte const reg_addr) 53 | { 54 | /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ 55 | byte read_write_buffer[] = {0x80 | reg_addr, 0, 0}; 56 | 57 | IoRequest request(read_write_buffer, sizeof(read_write_buffer), nullptr, 0); 58 | IoResponse response = transferAndWait(bmp388, request); 59 | 60 | auto value = read_write_buffer[2]; 61 | return value; 62 | } 63 | ``` 64 | 65 | ### `Adafruit_BusIO` style **synchronous** thread-safe `SPI` access 66 | ([`examples/Threadsafe_IO/SPI_BusIO`](../examples/Threadsafe_IO/SPI_BusIO)) 67 | 68 | For a further simplification [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) style APIs are provided: 69 | 70 | ```C++ 71 | byte bmp388_read_reg(byte const reg_addr) 72 | { 73 | /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ 74 | byte write_buffer[2] = {0x80 | reg_addr, 0}; 75 | byte read_buffer = 0; 76 | 77 | bmp388.spi().writeThenRead(write_buffer, sizeof(write_buffer), &read_buffer, sizeof(read_buffer)); 78 | return read_buffer; 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/threadsafe-wire.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Thread-safe `Wire` 4 | ================= 5 | ## Introduction 6 | A common problem of multi-tasking is the prevention of erroneous state when multiple threads share a single resource. The following example borrowed from a typical application demonstrates these problems: 7 | 8 | Imagine an embedded system where multiple `Wire` client devices are physically connected to a single `Wire` server. Each `Wire` client device is managed by a dedicated software thread. Each thread polls its `Wire` client device periodically. Access to the `Wire` bus is managed via the `Wire` library and typically follows the pattern described below: 9 | ```C++ 10 | /* Wire Write Access */ 11 | Wire.beginTransmission(address); 12 | Wire.write(value); 13 | Wire.endTransmission(); 14 | 15 | /* Wire Read Access */ 16 | Wire.requestFrom(address, bytes) 17 | while(Wire.available()) { 18 | int value = Wire.read(); 19 | } 20 | ``` 21 | Since we are using the [preemptive](https://os.mbed.com/docs/mbed-os/v6.11/program-setup/concepts.html#threads) [RTOS](https://en.wikipedia.org/wiki/Real-time_operating_system) [ARM Mbed OS](https://os.mbed.com/mbed-os/) with a tick time of 10 ms for achieving multi-tasking capability and under the assumption that all threads share the same priority (which leads to a [round-robin scheduling](https://en.wikipedia.org/wiki/Round-robin_scheduling)) it can easily happen that one thread is half-way through its `Wire` access when the scheduler interrupts it and schedules the next thread which in turn starts/continues/ends its own `Wire` access. 22 | 23 | As a result this interruption by the scheduler will break `Wire` access for both devices and leave the `Wire` controller in an undefined state. 24 | 25 | In Arduino Parallela we introduced the concept of `BusDevice`s which are meant to unify the way sketches access peripherals through heterogeneous busses such as `Wire`, `SPI` and `Serial`. A `BusDevice` is declared simply by specifying the type of interface and its parameters: 26 | ```C++ 27 | BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS); 28 | /* or */ 29 | BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, true /* restart */); 30 | /* or */ 31 | BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS, false /* restart */, true, /* stop */); 32 | ``` 33 | 34 | ### Asynchronous thread-safe `Wire` access with `transfer`/`wait` 35 | Once a `BusDevice` is declared it can be used to transfer data to and from the peripheral by means of the `transfer()` API. As opposed to the traditional Arduino bus APIs, `transfer()` is asynchronous and thus won't block execution unless the `wait()` function is called. 36 | Note that we are in a parallel programming environment which means that calls to `transfer()` on the same bus from different sketches will be arbitrated. 37 | 38 | ```C++ 39 | byte lsm6dsox_read_reg(byte const reg_addr) 40 | { 41 | byte write_buffer = reg_addr; 42 | byte read_buffer = 0; 43 | 44 | IoRequest request(write_buffer, read_buffer); 45 | IoResponse response = lsm6dsox.transfer(request); 46 | 47 | /* Wait for the completion of the IO Request. 48 | Allows other threads to run */ 49 | response->wait(); 50 | 51 | return read_buffer; 52 | } 53 | ``` 54 | 55 | ### Synchronous thread-safe `Wire` access with `transferAndWait` 56 | ([`examples/Threadsafe_IO/Wire`](../examples/Threadsafe_IO/Wire)) 57 | 58 | As the use of the `transfer` API might be difficult to grasp there's also a synchronous API call combining the request of the transfer and waiting for its result using `transferAndWait`. 59 | ```C++ 60 | byte lsm6dsox_read_reg(byte const reg_addr) 61 | { 62 | byte write_buffer = reg_addr; 63 | byte read_buffer = 0; 64 | 65 | IoRequest request(write_buffer, read_buffer); 66 | IoResponse response = transferAndWait(lsm6dsox, request); /* Transmit IO request for execution and wait for completion of request. */ 67 | 68 | return read_buffer; 69 | } 70 | ``` 71 | 72 | ### `Adafruit_BusIO` style **synchronous** thread-safe `Wire` access 73 | ([`examples/Threadsafe_IO/Wire_BusIO`](../examples/Threadsafe_IO/Wire_BusIO)) 74 | 75 | For further simplification [Adafruit_BusIO](https://github.com/adafruit/Adafruit_BusIO) style APIs are provided: 76 | 77 | ```C++ 78 | byte lsm6dsox_read_reg(byte reg_addr) 79 | { 80 | byte read_buffer = 0; 81 | lsm6dsox.wire().writeThenRead(®_addr, 1, &read_buffer, 1); 82 | return read_buffer; 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Shared_Counter/Consumer.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | while(!Serial) { } 5 | } 6 | 7 | void loop() 8 | { 9 | /* If a value is available for reading within the internal 10 | * queue then the value is removed from the queue and made 11 | * available to the calling function. Should no data be 12 | * available, then this thread is suspended until new data 13 | * is available for reading. 14 | */ 15 | Serial.println(counter.pop()); 16 | } 17 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Shared_Counter/Producer.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | 4 | } 5 | 6 | void loop() 7 | { 8 | static int i = 0; 9 | /* Every 100 ms a new value is inserted into the shared variable 10 | * 'counter'. Internally this is stored within a queue in a FIFO 11 | * (First-In/First-Out) manner. 12 | */ 13 | counter.push(i); 14 | i++; 15 | delay(100); 16 | } 17 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Shared_Counter/SharedVariables.h: -------------------------------------------------------------------------------- 1 | /* Define a shared variable named 'counter' of type 'int'. */ 2 | SHARED(counter, int); 3 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Shared_Counter/Shared_Counter.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates data exchange between 2 | * threads using a shared counter variable defined 3 | * within 'SharedVariables.h'. 4 | */ 5 | 6 | void setup() 7 | { 8 | Producer.start(); 9 | Consumer.start(); 10 | } 11 | 12 | void loop() 13 | { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_Counter/Consumer.inot: -------------------------------------------------------------------------------- 1 | /* Define a data sink named 'counter' of type 'int' with a internal queue size of 10. */ 2 | SINK(counter, int, 10); 3 | 4 | void setup() 5 | { 6 | Serial.begin(9600); 7 | while(!Serial) { } 8 | } 9 | 10 | void loop() 11 | { 12 | Serial.println(counter.pop()); 13 | } 14 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_Counter/Producer.inot: -------------------------------------------------------------------------------- 1 | /* Define a data source named 'counter' of type 'int'. */ 2 | SOURCE(counter, int); 3 | 4 | void setup() 5 | { 6 | 7 | } 8 | 9 | void loop() 10 | { 11 | static int i = 0; 12 | counter.push(i); 13 | i++; 14 | } 15 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_Counter/SharedVariables.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/examples/Threading_Basics/Source_Sink_Counter/SharedVariables.h -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_Counter/Source_Sink_Counter.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates the SOURCE/SINK abstraction. Each thread 2 | * may have any number of SOURCES and SINKS that can be connected 3 | * together using the 'connectTo' method. 4 | */ 5 | 6 | void setup() 7 | { 8 | CONNECT(Producer, counter, Consumer, counter); 9 | Producer.start(); 10 | Consumer.start(); 11 | } 12 | 13 | void loop() 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_LED/SharedVariables.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/examples/Threading_Basics/Source_Sink_LED/SharedVariables.h -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_LED/Sink_Thread.inot: -------------------------------------------------------------------------------- 1 | /* Define a data sink named 'led' of type 'bool' with a internal queue size of 1. */ 2 | SINK(led, bool); 3 | 4 | void setup() 5 | { 6 | pinMode(LED_BUILTIN, OUTPUT); 7 | } 8 | 9 | void loop() 10 | { 11 | /* Read a 'bool' value from the SINK and discard it. Since there is no delay in the loop 12 | * this call will block until new data is inserted from the connected SOURCE. This means 13 | * that the pace is dictated by the SOURCE that sends data every 100 ms. 14 | */ 15 | digitalWrite(LED_BUILTIN, led.pop()); 16 | } 17 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_LED/Source_Sink_LED.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates the SOURCE/SINK abstraction. Each thread 2 | * may have any number of SOURCES and SINKS that can be connected 3 | * together using the 'connectTo' method. 4 | */ 5 | 6 | void setup() 7 | { 8 | CONNECT(Source_Thread, led, Sink_Thread, led); 9 | Sink_Thread.start(); 10 | Source_Thread.start(); 11 | } 12 | 13 | void loop() 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Source_Sink_LED/Source_Thread.inot: -------------------------------------------------------------------------------- 1 | /* Define a data source named 'led' of type 'bool'. */ 2 | SOURCE(led, bool); 3 | 4 | void setup() 5 | { 6 | 7 | } 8 | 9 | void loop() 10 | { 11 | led.push(true); 12 | delay(100); 13 | led.push(false); 14 | delay(100); 15 | } 16 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Thermostat/AirConditionerControl.inot: -------------------------------------------------------------------------------- 1 | /* Define a data sink named 'temperature' of type 'float'. */ 2 | SINK(temperature, float, 10); 3 | 4 | void setup() 5 | { 6 | Serial.begin(9600); 7 | while (!Serial) { } 8 | } 9 | 10 | bool is_ac_on = false; 11 | 12 | void loop() 13 | { 14 | float current_temperature_deg = temperature.pop(); 15 | 16 | /* Check if the temperature reported by the thermostat is above 17 | * or below 26.0 °C. If the temperature is above 26.0 °C, turn 18 | * on the AC. 19 | */ 20 | bool turn_ac_on = false, 21 | turn_ac_off = false; 22 | 23 | if (current_temperature_deg > 26.0f) 24 | turn_ac_on = true; 25 | else 26 | turn_ac_off = true; 27 | 28 | /* Only perform a simulated turning on of 29 | * the air conditioner if the heating is 30 | * air conditioner is currently turned off. 31 | */ 32 | if (is_ac_on && turn_ac_off) 33 | { 34 | is_ac_on = false; 35 | 36 | Serial.block(); 37 | Serial.print("AC OFF ("); 38 | Serial.print(current_temperature_deg); 39 | Serial.println(" °C)"); 40 | Serial.unblock(); 41 | } 42 | 43 | if (!is_ac_on && turn_ac_on) 44 | { 45 | is_ac_on = true; 46 | 47 | Serial.block(); 48 | Serial.print("AC ON ("); 49 | Serial.print(current_temperature_deg); 50 | Serial.println(" °C)"); 51 | Serial.unblock(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Thermostat/HeatingControl.inot: -------------------------------------------------------------------------------- 1 | /* Define a data sink named 'temperature' of type 'float'. */ 2 | SINK(temperature, float, 10); 3 | 4 | void setup() 5 | { 6 | Serial.begin(9600); 7 | while (!Serial) { } 8 | } 9 | 10 | bool is_heating_on = false; 11 | 12 | void loop() 13 | { 14 | float current_temperature_deg = temperature.pop(); 15 | 16 | /* Check if the temperature reported by the thermostat is above 17 | * or below 22.0 °C. If the temperature is below 22.0 °C, turn 18 | * on the heating. 19 | */ 20 | bool turn_heating_on = false, 21 | turn_heating_off = false; 22 | 23 | if (current_temperature_deg < 22.0f) 24 | turn_heating_on = true; 25 | else 26 | turn_heating_off = true; 27 | 28 | /* Only perform a simulated turning on of 29 | * the heating if the heating is currently 30 | * turned off. 31 | */ 32 | if (is_heating_on && turn_heating_off) 33 | { 34 | is_heating_on = false; 35 | 36 | Serial.block(); 37 | Serial.print("Heating OFF ("); 38 | Serial.print(current_temperature_deg); 39 | Serial.println(" °C)"); 40 | Serial.unblock(); 41 | } 42 | 43 | if (!is_heating_on && turn_heating_on) 44 | { 45 | is_heating_on = true; 46 | 47 | Serial.block(); 48 | Serial.print("Heating ON ("); 49 | Serial.print(current_temperature_deg); 50 | Serial.println(" °C)"); 51 | Serial.unblock(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Thermostat/SharedVariables.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/examples/Threading_Basics/Thermostat/SharedVariables.h -------------------------------------------------------------------------------- /examples/Threading_Basics/Thermostat/TemperatureSensor.inot: -------------------------------------------------------------------------------- 1 | /* Define a data source named 'temperature' of type 'float'. */ 2 | SOURCE(temperature, float); 3 | 4 | void setup() 5 | { 6 | 7 | } 8 | 9 | void loop() 10 | { 11 | /* Read temperature - since this is just a simulation 12 | * we take a random value between 15 and 30 °C. 13 | */ 14 | float const temperature_deg = (rand() % 16) + 15; 15 | /* Store in temperature source variable. */ 16 | temperature.push(temperature_deg); 17 | /* Do only one temperature sensor measurement per second. */ 18 | delay(5000); 19 | } 20 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Thermostat/TemperatureSensorReporter.inot: -------------------------------------------------------------------------------- 1 | /* Define a data sink named 'temperature' of type 'float'. */ 2 | SINK(temperature, float); 3 | 4 | void setup() 5 | { 6 | Serial.begin(9600); 7 | while (!Serial) { } 8 | } 9 | 10 | void loop() 11 | { 12 | float const current_temperature_deg = temperature.pop(); 13 | 14 | Serial.block(); 15 | Serial.print("Temperature = "); 16 | Serial.print(current_temperature_deg); 17 | Serial.println(" °C"); 18 | Serial.unblock(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/Threading_Basics/Thermostat/Thermostat.ino: -------------------------------------------------------------------------------- 1 | /* This example emulates a thermostat system which consists of 2 | * a single temperature sensor and multiple heating devices 3 | * or air-conditioners. The temperature sensor provides periodic 4 | * temperature sensor measurements and acts as a temperature source. 5 | * This temperature is consumed by various TemperatureControl_ threads 6 | * which perform the act of actual room temperature control. 7 | * 8 | * Note: While there is only a single temperature "source" there are 9 | * multiple temperature "sinks". The source/sink paradigm is constructed 10 | * in such a way, that each sink is guaranteed to see every value provided 11 | * by a source. This is something that can not be modeled using the shared 12 | * variable abstraction. 13 | */ 14 | 15 | void setup() 16 | { 17 | /* Connect the temperature sensor thread providing temperature readings 18 | * with the various temperature control unit threads. 19 | */ 20 | CONNECT(TemperatureSensor, temperature, HeatingControl, temperature); 21 | CONNECT(TemperatureSensor, temperature, AirConditionerControl, temperature); 22 | CONNECT(TemperatureSensor, temperature, TemperatureSensorReporter, temperature); 23 | 24 | /* Start the individual threads for sensing the temperature 25 | * and controlling heating/air-conditioning based on the sensed 26 | * temperature on a per-room basis. 27 | */ 28 | TemperatureSensor.start(); 29 | HeatingControl.start(); 30 | AirConditionerControl.start(); 31 | TemperatureSensorReporter.start(); 32 | } 33 | 34 | void loop() 35 | { 36 | 37 | } 38 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/SPI/SPI.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how multiple threads can communicate 2 | * with a single SPI client device using the BusDevice abstraction 3 | * for SPI. In a similar way multiple threads can interface 4 | * with different client devices on the same SPI bus. 5 | */ 6 | 7 | /************************************************************************************** 8 | * INCLUDE 9 | **************************************************************************************/ 10 | 11 | #include 12 | 13 | /************************************************************************************** 14 | * CONSTANTS 15 | **************************************************************************************/ 16 | 17 | static int const BMP388_CS_PIN = 2; 18 | static int const BMP388_INT_PIN = 6; 19 | static byte const BMP388_CHIP_ID_REG_ADDR = 0x00; 20 | 21 | static size_t constexpr NUM_THREADS = 20; 22 | 23 | /************************************************************************************** 24 | * FUNCTION DECLARATION 25 | **************************************************************************************/ 26 | 27 | byte bmp388_read_reg(byte const reg_addr); 28 | void bmp388_thread_func(); 29 | 30 | /************************************************************************************** 31 | * GLOBAL VARIABLES 32 | **************************************************************************************/ 33 | 34 | BusDevice bmp388(SPI, BMP388_CS_PIN, 1000000, MSBFIRST, SPI_MODE0); 35 | 36 | static char thread_name[NUM_THREADS][32]; 37 | 38 | /************************************************************************************** 39 | * SETUP/LOOP 40 | **************************************************************************************/ 41 | 42 | void setup() 43 | { 44 | Serial.begin(9600); 45 | while (!Serial) { } 46 | 47 | pinMode(BMP388_CS_PIN, OUTPUT); 48 | digitalWrite(BMP388_CS_PIN, HIGH); 49 | 50 | for(size_t i = 0; i < NUM_THREADS; i++) 51 | { 52 | snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); 53 | rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); 54 | t->start(bmp388_thread_func); 55 | } 56 | } 57 | 58 | void loop() 59 | { 60 | 61 | } 62 | 63 | /************************************************************************************** 64 | * FUNCTION DEFINITION 65 | **************************************************************************************/ 66 | 67 | byte bmp388_read_reg(byte const reg_addr) 68 | { 69 | /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ 70 | byte read_write_buf[] = {static_cast(0x80 | reg_addr), 0, 0}; 71 | 72 | IoRequest req(read_write_buf, sizeof(read_write_buf), nullptr, 0); 73 | IoResponse rsp = transferAndWait(bmp388, req); 74 | 75 | return read_write_buf[2]; 76 | } 77 | 78 | void bmp388_thread_func() 79 | { 80 | Serial.begin(9600); 81 | while(!Serial) { } 82 | 83 | for(;;) 84 | { 85 | /* Sleep between 5 and 500 ms */ 86 | rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); 87 | /* Try to read some data from the BMP3888. */ 88 | byte const chip_id = bmp388_read_reg(BMP388_CHIP_ID_REG_ADDR); 89 | /* Print thread ID and chip ID value to serial. */ 90 | char msg[64] = {0}; 91 | snprintf(msg, sizeof(msg), "%s: Chip ID = 0x%X", rtos::ThisThread::get_name(), chip_id); 92 | Serial.println(msg); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/SPI_BusIO/SPI_BusIO.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how multiple threads can communicate 2 | * with a single SPI client device using the BusDevice abstraction 3 | * for SPI. In a similar way multiple threads can interface 4 | * with different client devices on the same SPI bus. 5 | * 6 | * This example uses Adafruit_BusIO style read(), write(), 7 | * writeThenRead() APIs. 8 | */ 9 | 10 | /************************************************************************************** 11 | * INCLUDE 12 | **************************************************************************************/ 13 | 14 | #include 15 | 16 | /************************************************************************************** 17 | * CONSTANTS 18 | **************************************************************************************/ 19 | 20 | static int const BMP388_CS_PIN = 2; 21 | static int const BMP388_INT_PIN = 6; 22 | static byte const BMP388_CHIP_ID_REG_ADDR = 0x00; 23 | 24 | static size_t constexpr NUM_THREADS = 20; 25 | 26 | /************************************************************************************** 27 | * FUNCTION DECLARATION 28 | **************************************************************************************/ 29 | 30 | byte bmp388_read_reg(byte const reg_addr); 31 | void bmp388_thread_func(); 32 | 33 | /************************************************************************************** 34 | * GLOBAL VARIABLES 35 | **************************************************************************************/ 36 | 37 | BusDevice bmp388(SPI, BMP388_CS_PIN, 1000000, MSBFIRST, SPI_MODE0); 38 | 39 | static char thread_name[NUM_THREADS][32]; 40 | 41 | /************************************************************************************** 42 | * SETUP/LOOP 43 | **************************************************************************************/ 44 | 45 | void setup() 46 | { 47 | pinMode(BMP388_CS_PIN, OUTPUT); 48 | digitalWrite(BMP388_CS_PIN, HIGH); 49 | 50 | for(size_t i = 0; i < NUM_THREADS; i++) 51 | { 52 | snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); 53 | rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); 54 | t->start(bmp388_thread_func); 55 | } 56 | } 57 | 58 | void loop() 59 | { 60 | 61 | } 62 | 63 | /************************************************************************************** 64 | * FUNCTION DEFINITION 65 | **************************************************************************************/ 66 | 67 | byte bmp388_read_reg(byte const reg_addr) 68 | { 69 | /* REG_ADDR | DUMMY_BYTE | REG_VAL is on SDO */ 70 | byte write_buf[2] = {static_cast(0x80 | reg_addr), 0}; 71 | byte read_buf = 0; 72 | 73 | bmp388.spi().writeThenRead(write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf)); 74 | return read_buf; 75 | } 76 | 77 | void bmp388_thread_func() 78 | { 79 | Serial.begin(9600); 80 | while(!Serial) { } 81 | 82 | for(;;) 83 | { 84 | /* Sleep between 5 and 500 ms */ 85 | rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); 86 | /* Try to read some data from the BMP3888. */ 87 | byte const chip_id = bmp388_read_reg(BMP388_CHIP_ID_REG_ADDR); 88 | /* Print thread ID and chip ID value to serial. */ 89 | char msg[64] = {0}; 90 | snprintf(msg, sizeof(msg), "%s: Chip ID = 0x%X", rtos::ThisThread::get_name(), chip_id); 91 | Serial.println(msg); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Serial_GlobalPrefixSuffix.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how every Serial message can be prefixed 2 | * as well as suffixed by a user-configurable message. In this example 3 | * this functionality is used for appending the current timestamp and 4 | * prepending a line feed. Other uses might be to prepend the thread 5 | * from which a given serial message is originating. 6 | */ 7 | 8 | /************************************************************************************** 9 | * INCLUDE 10 | **************************************************************************************/ 11 | 12 | #include 13 | /************************************************************************************** 14 | * FUNCTION DECLARATION 15 | **************************************************************************************/ 16 | 17 | String serial_log_message_prefix(String const & /* msg */); 18 | String serial_log_message_suffix(String const & prefix, String const & msg); 19 | 20 | /************************************************************************************** 21 | * SETUP/LOOP 22 | **************************************************************************************/ 23 | 24 | void setup() 25 | { 26 | Serial.begin(9600); 27 | while (!Serial) { } 28 | 29 | Serial.globalPrefix(serial_log_message_prefix); 30 | Serial.globalSuffix(serial_log_message_suffix); 31 | 32 | Thread_1.start(); 33 | Thread_2.start(); 34 | Thread_3.start(); 35 | } 36 | 37 | void loop() 38 | { 39 | Serial.block(); 40 | Serial.println("Thread #0: Lorem ipsum ..."); 41 | Serial.unblock(); 42 | } 43 | 44 | /************************************************************************************** 45 | * FUNCTION DEFINITION 46 | **************************************************************************************/ 47 | 48 | String serial_log_message_prefix(String const & /* msg */) 49 | { 50 | char msg[32] = {0}; 51 | snprintf(msg, sizeof(msg), "[%05lu] ", millis()); 52 | return String(msg); 53 | } 54 | 55 | String serial_log_message_suffix(String const & prefix, String const & msg) 56 | { 57 | return String("\r\n"); 58 | } 59 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/SharedVariables.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/SharedVariables.h -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_1.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | } 5 | 6 | void loop() 7 | { 8 | Serial.block(); 9 | Serial.println("Thread #1: Lorem ipsum ..."); 10 | Serial.unblock(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_2.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | } 5 | 6 | void loop() 7 | { 8 | Serial.block(); 9 | Serial.println("Thread #2: Lorem ipsum ..."); 10 | Serial.unblock(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_GlobalPrefixSuffix/Thread_3.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | } 5 | 6 | void loop() 7 | { 8 | Serial.block(); 9 | Serial.println("Thread #3: Lorem ipsum ..."); 10 | Serial.unblock(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_ProtocolWrapping/GPS_Thread.inot: -------------------------------------------------------------------------------- 1 | /************************************************************************************** 2 | * FUNCTION DEFINITION 3 | **************************************************************************************/ 4 | 5 | static String nmea_message_prefix(String const & /* msg */) 6 | { 7 | return String("$"); 8 | } 9 | 10 | static String nmea_message_suffix(String const & prefix, String const & msg) 11 | { 12 | /* NMEA checksum is calculated over the complete message 13 | * starting with '$' and ending with the end of the message. 14 | */ 15 | byte checksum = 0; 16 | std::for_each(msg.c_str(), 17 | msg.c_str() + msg.length(), 18 | [&checksum](char const c) 19 | { 20 | checksum ^= static_cast(c); 21 | }); 22 | /* Assemble the footer of the NMEA message. */ 23 | char footer[16] = {0}; 24 | snprintf(footer, sizeof(footer), "*%02X\r\n", checksum); 25 | return String(footer); 26 | } 27 | 28 | /************************************************************************************** 29 | * SETUP/LOOP 30 | **************************************************************************************/ 31 | 32 | void setup() 33 | { 34 | Serial.begin(9600); 35 | 36 | Serial.prefix(nmea_message_prefix); 37 | Serial.suffix(nmea_message_suffix); 38 | } 39 | 40 | void loop() 41 | { 42 | /* Sleep between 5 and 500 ms */ 43 | rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); 44 | 45 | /* Print a fake NMEA GPRMC message: 46 | * $GPRMC,062101.714,A,5001.869,N,01912.114,E,955535.7,116.2,290520,000.0,W*45\r\n 47 | */ 48 | Serial.block(); 49 | 50 | Serial.print("GPRMC,"); 51 | Serial.print(millis()); 52 | Serial.print(",A,"); 53 | Serial.print("5001.869,"); 54 | Serial.print("N,"); 55 | Serial.print("01912.114,"); 56 | Serial.print("E,"); 57 | Serial.print("955535.7,"); 58 | Serial.print("116.2,"); 59 | Serial.print("290520,"); 60 | Serial.print("000.0,"); 61 | Serial.print("W"); 62 | 63 | Serial.unblock(); 64 | } 65 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_ProtocolWrapping/Serial_ProtocolWrapping.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how every Serial message can be prefixed 2 | * as well as suffixed by a user-configurable message. In this example 3 | * this functionality is used for prepending the right header for a 4 | * pseudo NMEA-encoded (think GPS) message as well as for calculating 5 | * and appending the checksum at the end. 6 | */ 7 | 8 | /************************************************************************************** 9 | * INCLUDE 10 | **************************************************************************************/ 11 | 12 | #include 13 | 14 | /************************************************************************************** 15 | * SETUP/LOOP 16 | **************************************************************************************/ 17 | 18 | void setup() 19 | { 20 | Serial.begin(9600); 21 | while (!Serial) { } 22 | 23 | GPS_Thread.start(); 24 | } 25 | 26 | void loop() 27 | { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_ProtocolWrapping/SharedVariables.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/examples/Threadsafe_IO/Serial_ProtocolWrapping/SharedVariables.h -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Reader/Serial_Reader.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how multiple threads can subscribe to 2 | * reading from the same physical Serial interface. Incoming data 3 | * is copied into per-thread receive buffers so that no thread 4 | * can "steal" data from another thread by reading it first. 5 | */ 6 | 7 | /************************************************************************************** 8 | * INCLUDE 9 | **************************************************************************************/ 10 | 11 | #include 12 | 13 | /************************************************************************************** 14 | * SETUP/LOOP 15 | **************************************************************************************/ 16 | 17 | void setup() 18 | { 19 | Serial.begin(9600); 20 | while (!Serial) { } 21 | 22 | Thread_1.start(); 23 | Thread_2.start(); 24 | Thread_3.start(); 25 | 26 | Serial.block(); 27 | Serial.println("Thread #0 started."); 28 | Serial.unblock(); 29 | } 30 | 31 | void loop() 32 | { 33 | /* Read data from the serial interface into a String. */ 34 | String serial_msg; 35 | while (Serial.available()) 36 | serial_msg += (char)Serial.read(); 37 | 38 | /* Print thread ID and chip ID value to serial. */ 39 | if (serial_msg.length()) 40 | { 41 | Serial.block(); 42 | Serial.print("["); 43 | Serial.print(millis()); 44 | Serial.print("] Thread #0: "); 45 | Serial.print(serial_msg); 46 | Serial.println(); 47 | Serial.unblock(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Reader/SharedVariables.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/examples/Threadsafe_IO/Serial_Reader/SharedVariables.h -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Reader/Thread_1.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | 5 | Serial.block(); 6 | Serial.println("Thread #1 started."); 7 | Serial.unblock(); 8 | } 9 | 10 | void loop() 11 | { 12 | /* Read data from the serial interface into a String. */ 13 | String serial_msg; 14 | while (Serial.available()) 15 | serial_msg += (char)Serial.read(); 16 | 17 | /* Print thread ID and chip ID value to serial. */ 18 | if (serial_msg.length()) 19 | { 20 | Serial.block(); 21 | Serial.print("["); 22 | Serial.print(millis()); 23 | Serial.print("] Thread #1: "); 24 | Serial.print(serial_msg); 25 | Serial.println(); 26 | Serial.unblock(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Reader/Thread_2.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | 5 | Serial.block(); 6 | Serial.println("Thread #2 started."); 7 | Serial.unblock(); 8 | } 9 | 10 | void loop() 11 | { 12 | /* Read data from the serial interface into a String. */ 13 | String serial_msg; 14 | while (Serial.available()) 15 | serial_msg += (char)Serial.read(); 16 | 17 | /* Print thread ID and chip ID value to serial. */ 18 | if (serial_msg.length()) 19 | { 20 | Serial.block(); 21 | Serial.print("["); 22 | Serial.print(millis()); 23 | Serial.print("] Thread #2: "); 24 | Serial.print(serial_msg); 25 | Serial.println(); 26 | Serial.unblock(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Reader/Thread_3.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | 5 | Serial.block(); 6 | Serial.println("Thread #3 started."); 7 | Serial.unblock(); 8 | } 9 | 10 | void loop() 11 | { 12 | /* Read data from the serial interface into a String. */ 13 | String serial_msg; 14 | while (Serial.available()) 15 | serial_msg += (char)Serial.read(); 16 | 17 | /* Print thread ID and chip ID value to serial. */ 18 | if (serial_msg.length()) 19 | { 20 | Serial.block(); 21 | Serial.print("["); 22 | Serial.print(millis()); 23 | Serial.print("] Thread #3: "); 24 | Serial.print(serial_msg); 25 | Serial.println(); 26 | Serial.unblock(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Writer/Serial_Writer.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how multiple threads can write to 2 | * the same physical Serial interface without interfering with 3 | * one another. 4 | */ 5 | 6 | /************************************************************************************** 7 | * INCLUDE 8 | **************************************************************************************/ 9 | 10 | #include 11 | 12 | /************************************************************************************** 13 | * SETUP/LOOP 14 | **************************************************************************************/ 15 | 16 | void setup() 17 | { 18 | Serial.begin(9600); 19 | while (!Serial) { } 20 | 21 | Thread_1.start(); 22 | Thread_2.start(); 23 | Thread_3.start(); 24 | } 25 | 26 | void loop() 27 | { 28 | Serial.block(); 29 | Serial.print("["); 30 | Serial.print(millis()); 31 | Serial.print("] Thread #0: Lorem ipsum ..."); 32 | Serial.println(); 33 | Serial.unblock(); 34 | } 35 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Writer/SharedVariables.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arduino-libraries/Arduino_Threads/327204a3b3d423eedab0c807278bcafd252e6a15/examples/Threadsafe_IO/Serial_Writer/SharedVariables.h -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Writer/Thread_1.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | } 5 | 6 | void loop() 7 | { 8 | Serial.block(); 9 | Serial.print("["); 10 | Serial.print(millis()); 11 | Serial.print("] Thread #1: Lorem ipsum ..."); 12 | Serial.println(); 13 | Serial.unblock(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Writer/Thread_2.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | } 5 | 6 | void loop() 7 | { 8 | Serial.block(); 9 | Serial.print("["); 10 | Serial.print(millis()); 11 | Serial.print("] Thread #2: Lorem ipsum ..."); 12 | Serial.println(); 13 | Serial.unblock(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Serial_Writer/Thread_3.inot: -------------------------------------------------------------------------------- 1 | void setup() 2 | { 3 | Serial.begin(9600); 4 | } 5 | 6 | void loop() 7 | { 8 | Serial.block(); 9 | Serial.print("["); 10 | Serial.print(millis()); 11 | Serial.print("] Thread #3: Lorem ipsum ..."); 12 | Serial.println(); 13 | Serial.unblock(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Wire/Wire.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how multiple threads can communicate 2 | * with a single Wire client device using the BusDevice abstraction 3 | * for Wire. In a similar way multiple threads can interface 4 | * with different client devices on the same Wire bus. 5 | */ 6 | 7 | /************************************************************************************** 8 | * INCLUDE 9 | **************************************************************************************/ 10 | 11 | #include 12 | 13 | /************************************************************************************** 14 | * CONSTANTS 15 | **************************************************************************************/ 16 | 17 | static byte constexpr LSM6DSOX_ADDRESS = 0x6A; 18 | static byte constexpr LSM6DSOX_WHO_AM_I_REG = 0x0F; 19 | 20 | static size_t constexpr NUM_THREADS = 20; 21 | 22 | /************************************************************************************** 23 | * FUNCTION DECLARATION 24 | **************************************************************************************/ 25 | 26 | byte lsm6dsox_read_reg(byte const reg_addr); 27 | void lsm6dsox_thread_func(); 28 | 29 | /************************************************************************************** 30 | * GLOBAL VARIABLES 31 | **************************************************************************************/ 32 | 33 | BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS); 34 | 35 | static char thread_name[NUM_THREADS][32]; 36 | 37 | /************************************************************************************** 38 | * SETUP/LOOP 39 | **************************************************************************************/ 40 | 41 | void setup() 42 | { 43 | /* Fire up some threads all accessing the LSM6DSOX */ 44 | for(size_t i = 0; i < NUM_THREADS; i++) 45 | { 46 | snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); 47 | rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); 48 | t->start(lsm6dsox_thread_func); 49 | } 50 | } 51 | 52 | void loop() 53 | { 54 | 55 | } 56 | 57 | /************************************************************************************** 58 | * FUNCTION DEFINITION 59 | **************************************************************************************/ 60 | 61 | byte lsm6dsox_read_reg(byte const reg_addr) 62 | { 63 | /* As we need only 1 byte large write/read buffers for this IO transaction 64 | * the buffers are not arrays but rather simple variables. 65 | */ 66 | byte write_buf = reg_addr; 67 | byte read_buf = 0; 68 | 69 | IoRequest req(write_buf, read_buf); 70 | IoResponse rsp = transferAndWait(lsm6dsox, req); 71 | 72 | return read_buf; 73 | } 74 | 75 | 76 | void lsm6dsox_thread_func() 77 | { 78 | Serial.begin(9600); 79 | while(!Serial) { } 80 | 81 | for(;;) 82 | { 83 | /* Sleep between 5 and 500 ms */ 84 | rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); 85 | /* Try to read some data from the LSM6DSOX. */ 86 | byte const who_am_i = lsm6dsox_read_reg(LSM6DSOX_WHO_AM_I_REG); 87 | /* Print thread ID and chip ID value to serial. */ 88 | char msg[64] = {0}; 89 | snprintf(msg, sizeof(msg), "%s: LSM6DSOX[WHO_AM_I] = 0x%X", rtos::ThisThread::get_name(), who_am_i); 90 | Serial.println(msg); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/Threadsafe_IO/Wire_BusIO/Wire_BusIO.ino: -------------------------------------------------------------------------------- 1 | /* This example demonstrates how multiple threads can communicate 2 | * with a single Wire client device using the BusDevice abstraction 3 | * for Wire. In a similar way multiple threads can interface 4 | * with different client devices on the same Wire bus. 5 | * 6 | * This example uses Adafruit_BusIO style read(), write(), 7 | * writeThenRead() APIs. 8 | */ 9 | 10 | /************************************************************************************** 11 | * INCLUDE 12 | **************************************************************************************/ 13 | 14 | #include 15 | 16 | /************************************************************************************** 17 | * CONSTANTS 18 | **************************************************************************************/ 19 | 20 | static byte constexpr LSM6DSOX_ADDRESS = 0x6A; 21 | static byte constexpr LSM6DSOX_WHO_AM_I_REG = 0x0F; 22 | 23 | static size_t constexpr NUM_THREADS = 20; 24 | 25 | /************************************************************************************** 26 | * FUNCTION DECLARATION 27 | **************************************************************************************/ 28 | 29 | byte lsm6dsox_read_reg(byte const reg_addr); 30 | void lsm6dsox_thread_func(); 31 | 32 | /************************************************************************************** 33 | * GLOBAL VARIABLES 34 | **************************************************************************************/ 35 | 36 | BusDevice lsm6dsox(Wire, LSM6DSOX_ADDRESS); 37 | 38 | static char thread_name[NUM_THREADS][32]; 39 | 40 | /************************************************************************************** 41 | * SETUP/LOOP 42 | **************************************************************************************/ 43 | 44 | void setup() 45 | { 46 | /* Fire up some threads all accessing the LSM6DSOX */ 47 | for(size_t i = 0; i < NUM_THREADS; i++) 48 | { 49 | snprintf(thread_name[i], sizeof(thread_name[i]), "Thread #%02d", i); 50 | rtos::Thread * t = new rtos::Thread(osPriorityNormal, OS_STACK_SIZE, nullptr, thread_name[i]); 51 | t->start(lsm6dsox_thread_func); 52 | } 53 | } 54 | 55 | void loop() 56 | { 57 | 58 | } 59 | 60 | /************************************************************************************** 61 | * FUNCTION DEFINITION 62 | **************************************************************************************/ 63 | 64 | byte lsm6dsox_read_reg(byte reg_addr) 65 | { 66 | byte read_buf = 0; 67 | lsm6dsox.wire().writeThenRead(®_addr, 1, &read_buf, 1); 68 | return read_buf; 69 | } 70 | 71 | void lsm6dsox_thread_func() 72 | { 73 | Serial.begin(9600); 74 | while(!Serial) { } 75 | 76 | for(;;) 77 | { 78 | /* Sleep between 5 and 500 ms */ 79 | rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(random(5,500))); 80 | /* Try to read some data from the LSM6DSOX. */ 81 | byte const who_am_i = lsm6dsox_read_reg(LSM6DSOX_WHO_AM_I_REG); 82 | /* Print thread ID and chip ID value to serial. */ 83 | char msg[64] = {0}; 84 | snprintf(msg, sizeof(msg), "%s: LSM6DSOX[WHO_AM_I] = 0x%X", rtos::ThisThread::get_name(), who_am_i); 85 | Serial.println(msg); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for Arduino_Threads 3 | ####################################### 4 | 5 | ####################################### 6 | # Class (KEYWORD1) 7 | ####################################### 8 | 9 | SINK KEYWORD1 10 | SOURCE KEYWORD1 11 | SHARED KEYWORD1 12 | 13 | IoRequest KEYWORD1 14 | IoResponse KEYWORD1 15 | SpiBusDevice KEYWORD1 16 | SpiBusDeviceConfig KEYWORD1 17 | WireBusDevice KEYWORD1 18 | WireBusDeviceConfig KEYWORD1 19 | BusDevice KEYWORD1 20 | 21 | ####################################### 22 | # Methods and Functions (KEYWORD2) 23 | ####################################### 24 | 25 | start KEYWORD2 26 | terminate KEYWORD2 27 | broadcastEvent KEYWORD2 28 | sendEvent KEYWORD2 29 | setLoopDelay KEYWORD2 30 | 31 | transfer KEYWORD2 32 | create KEYWORD2 33 | block KEYWORD2 34 | unblock KEYWORD2 35 | prefix KEYWORD2 36 | suffix KEYWORD2 37 | globalPrefix KEYWORD2 38 | globalSuffix KEYWORD2 39 | spi KEYWORD2 40 | wire KEYWORD2 41 | read KEYWORD2 42 | write KEYWORD2 43 | writeThenRead KEYWORD2 44 | transferAndWait KEYWORD2 45 | 46 | ####################################### 47 | # Constants (LITERAL1) 48 | ####################################### 49 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Arduino_Threads 2 | version=0.3.0 3 | author=Arduino 4 | maintainer=Arduino 5 | sentence=Easy multi-threading for your Mbed OS-based Arduino. 6 | paragraph=This library allows an easy access to the multi-threading capability inherent in all Mbed OS-based Arduino boards. 7 | category=Other 8 | url=https://github.com/bcmi-labs/Arduino_Threads 9 | architectures=mbed,mbed_portenta,mbed_nano 10 | includes=Arduino_Threads.h 11 | -------------------------------------------------------------------------------- /src/Arduino_Threads.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "Arduino_Threads.h" 24 | 25 | /************************************************************************************** 26 | * STATIC MEMBER DECLARATION 27 | **************************************************************************************/ 28 | 29 | rtos::EventFlags Arduino_Threads::_global_events; 30 | 31 | /************************************************************************************** 32 | * CTOR/DTOR 33 | **************************************************************************************/ 34 | 35 | Arduino_Threads::Arduino_Threads() 36 | : _start_flags{0} 37 | , _stop_flags{0} 38 | , _loop_delay_ms{0} 39 | { 40 | 41 | } 42 | 43 | Arduino_Threads::~Arduino_Threads() 44 | { 45 | terminate(); 46 | } 47 | 48 | /************************************************************************************** 49 | * PUBLIC MEMBER FUNCTIONS 50 | **************************************************************************************/ 51 | 52 | void Arduino_Threads::start(int const stack_size, uint32_t const start_flags, uint32_t const stop_flags) 53 | { 54 | _start_flags = start_flags; 55 | _stop_flags = stop_flags; 56 | _thread.reset(new rtos::Thread(osPriorityNormal, stack_size, nullptr, _tabname)); 57 | _thread->start(mbed::callback(this, &Arduino_Threads::threadFunc)); 58 | } 59 | 60 | void Arduino_Threads::terminate() 61 | { 62 | _thread->terminate(); 63 | _thread->join(); 64 | } 65 | 66 | void Arduino_Threads::sendEvent(uint32_t const event) 67 | { 68 | _thread->flags_set(event); 69 | } 70 | 71 | void Arduino_Threads::setLoopDelay(uint32_t const delay) 72 | { 73 | _loop_delay_ms = delay; 74 | } 75 | 76 | void Arduino_Threads::broadcastEvent(uint32_t const event) 77 | { 78 | _global_events.set(event); 79 | } 80 | 81 | /************************************************************************************** 82 | * PRIVATE MEMBER FUNCTIONS 83 | **************************************************************************************/ 84 | 85 | void Arduino_Threads::threadFunc() 86 | { 87 | setup(); 88 | /* If _start_flags have been passed then wait until all the flags are set 89 | * before starting the loop. this is used to synchronize loops from multiple 90 | * sketches. 91 | */ 92 | if (_start_flags != 0) 93 | _global_events.wait_all(_start_flags); 94 | 95 | /* if _stop_flags have been passed stop when all the flags are set 96 | * otherwise loop forever 97 | */ 98 | for (;;) 99 | { 100 | loop(); 101 | /* On exit clear the flags that have forced us to stop. 102 | * note that if two groups of sketches stop on common flags 103 | * the first group will clear them so the second group may never 104 | * exit. 105 | */ 106 | if (_stop_flags!=0) 107 | { 108 | if ((_global_events.get() & _stop_flags) != _stop_flags) 109 | { 110 | _global_events.clear(_stop_flags); 111 | return; 112 | } 113 | 114 | if ((rtos::ThisThread::flags_get() & _stop_flags) != _stop_flags) 115 | { 116 | rtos::ThisThread::flags_clear(_stop_flags); 117 | return; 118 | } 119 | } 120 | 121 | /* Sleep for the time we've been asked to insert between loops. 122 | */ 123 | rtos::ThisThread::sleep_for(rtos::Kernel::Clock::duration_u32(_loop_delay_ms)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Arduino_Threads.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef ARDUINO_THREADS_H_ 20 | #define ARDUINO_THREADS_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | #include 28 | 29 | #include "threading/Sink.hpp" 30 | #include "threading/Source.hpp" 31 | #include "threading/Shared.hpp" 32 | 33 | #include "io/BusDevice.h" 34 | #include "io/util/util.h" 35 | #include "io/spi/SpiBusDevice.h" 36 | #include "io/wire/WireBusDevice.h" 37 | #include "io/serial/SerialDispatcher.h" 38 | 39 | /************************************************************************************** 40 | * DEFINE 41 | **************************************************************************************/ 42 | 43 | #define SOURCE(name, type) \ 44 | Source name; 45 | 46 | /* We need to call the SinkBlocking(size_t const size) 47 | * non-default constructor using size as parameter. 48 | 49 | * This is achieved via 50 | * SinkBlocking name{size}; 51 | * instead of 52 | * SinkBlocking name(size); 53 | * otherwise the compiler will read it as a declaration 54 | * of a method called "name" and we get a syntax error. 55 | * 56 | * This is called "C++11 uniform init" (using "{}" instead 57 | * of "()" without "="... yikes!) 58 | * https://chromium.googlesource.com/chromium/src/+/master/styleguide/c++/c++-dos-and-donts.md 59 | */ 60 | 61 | #define SINK_2_ARG(name, type) \ 62 | SinkBlocking name{1} 63 | 64 | #define SINK_3_ARG(name, type, size) \ 65 | SinkBlocking name{size} 66 | 67 | /* Black C macro magic enabling "macro overloading" 68 | * with same name macro, but multiple arguments. 69 | * https://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments 70 | */ 71 | #define GET_SINK_MACRO(_1,_2,_3,NAME,...) NAME 72 | #define SINK(...) GET_SINK_MACRO(__VA_ARGS__, SINK_3_ARG, SINK_2_ARG)(__VA_ARGS__) 73 | 74 | #define SINK_NON_BLOCKING(name, type) \ 75 | SinkNonBlocking name{} 76 | 77 | #define CONNECT(source_thread, source_name, sink_thread, sink_name) \ 78 | source_thread##Private::source_name.connectTo(sink_thread##Private::sink_name) 79 | 80 | #define SHARED_2_ARG(name, type) \ 81 | Shared name; 82 | 83 | #define SHARED_3_ARG(name, type, size) \ 84 | Shared name; 85 | 86 | #define GET_SHARED_MACRO(_1,_2,_3,NAME,...) NAME 87 | #define SHARED(...) GET_SHARED_MACRO(__VA_ARGS__, SHARED_3_ARG, SHARED_2_ARG)(__VA_ARGS__) 88 | 89 | 90 | #define ARDUINO_THREADS_CONCAT_(x,y) x##y 91 | #define ARDUINO_THREADS_CONCAT(x,y) ARDUINO_THREADS_CONCAT_(x,y) 92 | 93 | #define ARDUINO_THREADS_TO_STRING(sequence) #sequence 94 | 95 | /************************************************************************************** 96 | * CLASS DECLARATION 97 | **************************************************************************************/ 98 | 99 | class Arduino_Threads 100 | { 101 | public: 102 | 103 | Arduino_Threads(); 104 | virtual ~Arduino_Threads(); 105 | 106 | 107 | void start (int const stack_size = 4096, uint32_t const start_flags = 0, uint32_t const stop_flags = 0); 108 | void terminate (); 109 | void setLoopDelay(uint32_t const delay); 110 | void sendEvent (uint32_t const event); 111 | 112 | static void broadcastEvent(uint32_t event); 113 | 114 | 115 | protected: 116 | 117 | char * _tabname; 118 | 119 | virtual void setup() = 0; 120 | virtual void loop () = 0; 121 | 122 | private: 123 | 124 | static rtos::EventFlags _global_events; 125 | mbed::SharedPtr _thread; 126 | uint32_t _start_flags, _stop_flags; 127 | uint32_t _loop_delay_ms; 128 | 129 | void threadFunc(); 130 | }; 131 | 132 | #define THD_SETUP(ns) ns::setup() 133 | #define THD_LOOP(ns) ns::loop() 134 | 135 | #define THD_ENTER(tabname) \ 136 | namespace ARDUINO_THREADS_CONCAT(tabname,Private)\ 137 | {\ 138 | void setup();\ 139 | void loop();\ 140 | }\ 141 | class ARDUINO_THREADS_CONCAT(tabname, Class) : public Arduino_Threads\ 142 | {\ 143 | public:\ 144 | ARDUINO_THREADS_CONCAT(tabname, Class)() { _tabname = ARDUINO_THREADS_TO_STRING(tabname); }\ 145 | protected:\ 146 | virtual void setup() override { THD_SETUP(ARDUINO_THREADS_CONCAT(tabname,Private)); }\ 147 | virtual void loop() override { THD_LOOP(ARDUINO_THREADS_CONCAT(tabname,Private)); }\ 148 | };\ 149 | namespace ARDUINO_THREADS_CONCAT(tabname,Private)\ 150 | { 151 | 152 | #define THD_DONE(tabname)\ 153 | };\ 154 | ARDUINO_THREADS_CONCAT(tabname,Class) tabname; 155 | 156 | #endif /* ARDUINO_THREADS_H_ */ 157 | -------------------------------------------------------------------------------- /src/io/BusDevice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "BusDevice.h" 24 | 25 | #include "spi/SpiBusDevice.h" 26 | #include "wire/WireBusDevice.h" 27 | 28 | /************************************************************************************** 29 | * BusDeviceBase PUBLIC MEMBER FUNCTIONS 30 | **************************************************************************************/ 31 | 32 | BusDevice BusDeviceBase::create(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol) 33 | { 34 | return BusDevice(new SpiBusDevice(SpiBusDeviceConfig{spi, 35 | spi_settings, 36 | cs_pin, 37 | fill_symbol 38 | })); 39 | } 40 | 41 | BusDevice BusDeviceBase::create(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol) 42 | { 43 | return BusDevice(new SpiBusDevice(SpiBusDeviceConfig{spi, 44 | SPISettings(spi_clock, spi_bit_order, spi_bit_mode), 45 | cs_pin, 46 | fill_symbol 47 | })); 48 | } 49 | 50 | BusDevice BusDeviceBase::create(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol) 51 | { 52 | return BusDevice(new SpiBusDevice(SpiBusDeviceConfig{spi, spi_settings, spi_select, spi_deselect, fill_symbol})); 53 | } 54 | 55 | BusDevice BusDeviceBase::create(arduino::HardwareI2C & wire, byte const slave_addr) 56 | { 57 | return create(wire, slave_addr, true, true); 58 | } 59 | 60 | BusDevice BusDeviceBase::create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart) 61 | { 62 | return create(wire, slave_addr, restart, true); 63 | } 64 | 65 | BusDevice BusDeviceBase::create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop) 66 | { 67 | return BusDevice(new WireBusDevice(WireBusDeviceConfig{wire, slave_addr, restart, stop})); 68 | } 69 | 70 | /************************************************************************************** 71 | * BusDevice CTOR/DTOR 72 | **************************************************************************************/ 73 | 74 | BusDevice::BusDevice(BusDeviceBase * dev) 75 | : _dev{dev} 76 | { } 77 | 78 | BusDevice::BusDevice(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol) 79 | { 80 | *this = BusDeviceBase::create(spi, cs_pin, spi_settings, fill_symbol); 81 | } 82 | 83 | BusDevice::BusDevice(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol) 84 | { 85 | *this = BusDeviceBase::create(spi, cs_pin, spi_clock, spi_bit_order, spi_bit_mode, fill_symbol); 86 | } 87 | 88 | BusDevice::BusDevice(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol) 89 | { 90 | *this = BusDeviceBase::create(spi, spi_select, spi_deselect, spi_settings, fill_symbol); 91 | } 92 | 93 | BusDevice::BusDevice(arduino::HardwareI2C & wire, byte const slave_addr) 94 | { 95 | *this = BusDeviceBase::create(wire, slave_addr); 96 | } 97 | 98 | BusDevice::BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart) 99 | { 100 | *this = BusDeviceBase::create(wire, slave_addr, restart); 101 | } 102 | 103 | BusDevice::BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop) 104 | { 105 | *this = BusDeviceBase::create(wire, slave_addr, restart, stop); 106 | } 107 | 108 | IoResponse BusDevice::transfer(IoRequest & req) 109 | { 110 | return _dev->transfer(req); 111 | } 112 | 113 | SpiBusDevice & BusDevice::spi() 114 | { 115 | return *reinterpret_cast(_dev.get()); 116 | } 117 | 118 | WireBusDevice & BusDevice::wire() 119 | { 120 | return *reinterpret_cast(_dev.get()); 121 | } 122 | -------------------------------------------------------------------------------- /src/io/BusDevice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef BUS_DEVICE_H_ 20 | #define BUS_DEVICE_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include "IoTransaction.h" 27 | 28 | #include "spi/SpiBusDeviceConfig.h" 29 | 30 | /************************************************************************************** 31 | * FORWARD DECLARATION 32 | **************************************************************************************/ 33 | 34 | namespace arduino 35 | { 36 | class HardwareSPI; 37 | class HardwareI2C; 38 | } 39 | 40 | class BusDevice; 41 | class SpiBusDevice; 42 | class WireBusDevice; 43 | 44 | /************************************************************************************** 45 | * CLASS DECLARATION 46 | **************************************************************************************/ 47 | 48 | class BusDeviceBase 49 | { 50 | public: 51 | 52 | virtual ~BusDeviceBase() { } 53 | 54 | virtual IoResponse transfer(IoRequest & req) = 0; 55 | 56 | 57 | static BusDevice create(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol = 0xFF); 58 | static BusDevice create(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol = 0xFF); 59 | static BusDevice create(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol = 0xFF); 60 | 61 | static BusDevice create(arduino::HardwareI2C & wire, byte const slave_addr); 62 | static BusDevice create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart); 63 | static BusDevice create(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop); 64 | 65 | }; 66 | 67 | class BusDevice 68 | { 69 | public: 70 | 71 | BusDevice(BusDeviceBase * dev); 72 | 73 | BusDevice(arduino::HardwareSPI & spi, int const cs_pin, SPISettings const & spi_settings, byte const fill_symbol = 0xFF); 74 | BusDevice(arduino::HardwareSPI & spi, int const cs_pin, uint32_t const spi_clock, BitOrder const spi_bit_order, SPIMode const spi_bit_mode, byte const fill_symbol = 0xFF); 75 | BusDevice(arduino::HardwareSPI & spi, SpiBusDeviceConfig::SpiSelectFunc spi_select, SpiBusDeviceConfig::SpiDeselectFunc spi_deselect, SPISettings const & spi_settings, byte const fill_symbol = 0xFF); 76 | 77 | BusDevice(arduino::HardwareI2C & wire, byte const slave_addr); 78 | BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart); 79 | BusDevice(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop); 80 | 81 | IoResponse transfer(IoRequest & req); 82 | 83 | 84 | SpiBusDevice & spi(); 85 | WireBusDevice & wire(); 86 | 87 | 88 | private: 89 | 90 | mbed::SharedPtr _dev; 91 | 92 | }; 93 | 94 | #endif /* BUS_DEVICE_H_ */ 95 | -------------------------------------------------------------------------------- /src/io/IoTransaction.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef IO_TRANSACTION_H_ 20 | #define IO_TRANSACTION_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | #include 29 | 30 | #include 31 | 32 | /************************************************************************************** 33 | * CLASS DECLARATION 34 | **************************************************************************************/ 35 | 36 | /************************************************************************************** 37 | * IoRequest 38 | **************************************************************************************/ 39 | 40 | class IoRequest 41 | { 42 | public: 43 | 44 | IoRequest(byte * write_buf_, size_t const bytes_to_write_, byte * read_buf_, size_t const bytes_to_read_) 45 | : write_buf{write_buf_} 46 | , bytes_to_write{bytes_to_write_} 47 | , read_buf{read_buf_} 48 | , bytes_to_read{bytes_to_read_} 49 | { } 50 | 51 | IoRequest(byte & write_buf_, byte & read_buf_) 52 | : IoRequest{&write_buf_, 1, &read_buf_, 1} 53 | { } 54 | 55 | byte * write_buf{nullptr}; 56 | size_t const bytes_to_write{0}; 57 | byte * read_buf{nullptr}; 58 | size_t const bytes_to_read{0}; 59 | 60 | }; 61 | 62 | /************************************************************************************** 63 | * IoResponse 64 | **************************************************************************************/ 65 | 66 | namespace impl 67 | { 68 | 69 | class IoResponse 70 | { 71 | public: 72 | 73 | IoResponse() 74 | : bytes_written{0} 75 | , bytes_read{0} 76 | , _cond{_mutex} 77 | , _is_done{false} 78 | { } 79 | 80 | size_t bytes_written{0}; 81 | size_t bytes_read{0}; 82 | 83 | void done() 84 | { 85 | _mutex.lock(); 86 | _is_done = true; 87 | _cond.notify_all(); 88 | _mutex.unlock(); 89 | } 90 | 91 | void wait() 92 | { 93 | _mutex.lock(); 94 | while (!_is_done) { 95 | _cond.wait(); 96 | } 97 | _mutex.unlock(); 98 | } 99 | 100 | private: 101 | 102 | rtos::Mutex _mutex; 103 | rtos::ConditionVariable _cond; 104 | bool _is_done{false}; 105 | 106 | }; 107 | 108 | } /* namespace impl */ 109 | 110 | typedef mbed::SharedPtr IoResponse; 111 | 112 | /************************************************************************************** 113 | * IoTransaction 114 | **************************************************************************************/ 115 | 116 | class IoTransaction 117 | { 118 | public: 119 | 120 | IoTransaction(IoRequest * q, IoResponse * p) : req{q}, rsp{p} { } 121 | IoRequest * req{nullptr}; 122 | IoResponse * rsp{nullptr}; 123 | }; 124 | 125 | #endif /* IO_TRANSACTION_H_ */ 126 | -------------------------------------------------------------------------------- /src/io/serial/SerialDispatcher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "SerialDispatcher.h" 24 | 25 | /************************************************************************************** 26 | * CTOR/DTOR 27 | **************************************************************************************/ 28 | 29 | SerialDispatcher::SerialDispatcher(arduino::HardwareSerial & serial) 30 | : _is_initialized{false} 31 | , _mutex{} 32 | , _serial{serial} 33 | , _thread(osPriorityRealtime, 4096, nullptr, "SerialDispatcher") 34 | , _has_tread_started{false} 35 | , _terminate_thread{false} 36 | , _global_prefix_callback{nullptr} 37 | , _global_suffix_callback{nullptr} 38 | { 39 | 40 | } 41 | 42 | /************************************************************************************** 43 | * PUBLIC MEMBER FUNCTIONS 44 | **************************************************************************************/ 45 | 46 | void SerialDispatcher::begin(unsigned long baudrate) 47 | { 48 | begin(baudrate, SERIAL_8N1); 49 | } 50 | 51 | void SerialDispatcher::begin(unsigned long baudrate, uint16_t config) 52 | { 53 | mbed::ScopedLock lock(_mutex); 54 | 55 | if (!_is_initialized) 56 | { 57 | _serial.begin(baudrate, config); 58 | _is_initialized = true; 59 | _thread.start(mbed::callback(this, &SerialDispatcher::threadFunc)); /* TODO: Check return code */ 60 | while (!_has_tread_started) { } 61 | } 62 | 63 | /* Check if the thread calling begin is already in the list. */ 64 | osThreadId_t const current_thread_id = rtos::ThisThread::get_id(); 65 | if (findThreadCustomerDataById(rtos::ThisThread::get_id()) == std::end(_thread_customer_list)) 66 | { 67 | /* Since the thread is not in the list yet we are 68 | * going to create a new entry to the list. 69 | */ 70 | uint32_t const thread_event_flag = (1<<(_thread_customer_list.size())); 71 | ThreadCustomerData data{current_thread_id, thread_event_flag}; 72 | _thread_customer_list.push_back(data); 73 | } 74 | } 75 | 76 | void SerialDispatcher::end() 77 | { 78 | mbed::ScopedLock lock(_mutex); 79 | 80 | /* Retrieve the current thread ID and remove 81 | * the thread data from the thread data list. 82 | */ 83 | osThreadId_t const current_thread_id = rtos::ThisThread::get_id(); 84 | std::remove_if(std::begin(_thread_customer_list), 85 | std::end (_thread_customer_list), 86 | [current_thread_id](ThreadCustomerData const d) -> bool 87 | { 88 | return (d.thread_id == current_thread_id); 89 | }); 90 | 91 | /* If no thread consumers are left also end 92 | * the serial device altogether. 93 | */ 94 | if (_thread_customer_list.size() == 0) 95 | { 96 | _terminate_thread = true; 97 | _thread.join(); 98 | _serial.end(); 99 | } 100 | } 101 | 102 | int SerialDispatcher::available() 103 | { 104 | mbed::ScopedLock lock(_mutex); 105 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 106 | assert(iter != std::end(_thread_customer_list)); 107 | 108 | prepareSerialReader(iter); 109 | handleSerialReader(); 110 | 111 | return iter->rx_buffer->available(); 112 | } 113 | 114 | int SerialDispatcher::peek() 115 | { 116 | mbed::ScopedLock lock(_mutex); 117 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 118 | assert(iter != std::end(_thread_customer_list)); 119 | 120 | prepareSerialReader(iter); 121 | handleSerialReader(); 122 | 123 | return iter->rx_buffer->peek(); 124 | } 125 | 126 | int SerialDispatcher::read() 127 | { 128 | mbed::ScopedLock lock(_mutex); 129 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 130 | assert(iter != std::end(_thread_customer_list)); 131 | 132 | prepareSerialReader(iter); 133 | handleSerialReader(); 134 | 135 | return iter->rx_buffer->read_char(); 136 | } 137 | 138 | void SerialDispatcher::flush() 139 | { 140 | mbed::ScopedLock lock(_mutex); 141 | _serial.flush(); 142 | } 143 | 144 | size_t SerialDispatcher::write(uint8_t const b) 145 | { 146 | return write(&b, 1); 147 | } 148 | 149 | size_t SerialDispatcher::write(const uint8_t * data, size_t len) 150 | { 151 | mbed::ScopedLock lock(_mutex); 152 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 153 | assert(iter != std::end(_thread_customer_list)); 154 | 155 | size_t bytes_written = 0; 156 | for (; (bytes_written < len) && iter->tx_buffer.availableForStore(); bytes_written++) 157 | iter->tx_buffer.store_char(data[bytes_written]); 158 | 159 | /* Inform the worker thread that new data has 160 | * been written to a Serial transmit buffer. 161 | */ 162 | _data_available_for_transmit.set(iter->thread_event_flag); 163 | 164 | return bytes_written; 165 | } 166 | 167 | void SerialDispatcher::block() 168 | { 169 | mbed::ScopedLock lock(_mutex); 170 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 171 | assert(iter != std::end(_thread_customer_list)); 172 | 173 | iter->block_tx_buffer = true; 174 | } 175 | 176 | void SerialDispatcher::unblock() 177 | { 178 | mbed::ScopedLock lock(_mutex); 179 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 180 | assert(iter != std::end(_thread_customer_list)); 181 | 182 | iter->block_tx_buffer = false; 183 | 184 | _data_available_for_transmit.set(iter->thread_event_flag); 185 | } 186 | 187 | void SerialDispatcher::prefix(PrefixInjectorCallbackFunc func) 188 | { 189 | mbed::ScopedLock lock(_mutex); 190 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 191 | assert(iter != std::end(_thread_customer_list)); 192 | 193 | iter->prefix_func = func; 194 | } 195 | 196 | void SerialDispatcher::suffix(SuffixInjectorCallbackFunc func) 197 | { 198 | mbed::ScopedLock lock(_mutex); 199 | auto iter = findThreadCustomerDataById(rtos::ThisThread::get_id()); 200 | assert(iter != std::end(_thread_customer_list)); 201 | 202 | iter->suffix_func = func; 203 | } 204 | 205 | void SerialDispatcher::globalPrefix(PrefixInjectorCallbackFunc func) 206 | { 207 | mbed::ScopedLock lock(_mutex); 208 | _global_prefix_callback = func; 209 | } 210 | 211 | void SerialDispatcher::globalSuffix(SuffixInjectorCallbackFunc func) 212 | { 213 | mbed::ScopedLock lock(_mutex); 214 | _global_suffix_callback = func; 215 | } 216 | 217 | /************************************************************************************** 218 | * PRIVATE MEMBER FUNCTIONS 219 | **************************************************************************************/ 220 | 221 | void SerialDispatcher::threadFunc() 222 | { 223 | _has_tread_started = true; 224 | 225 | while(!_terminate_thread) 226 | { 227 | /* Wait for data to be available in a transmit buffer. */ 228 | static uint32_t constexpr ALL_EVENT_FLAGS = 0x7fffffff; 229 | _data_available_for_transmit.wait_any(ALL_EVENT_FLAGS, osWaitForever, /* clear */ true); 230 | 231 | /* Iterate over all list entries. */ 232 | std::for_each(std::begin(_thread_customer_list), 233 | std::end (_thread_customer_list), 234 | [this](ThreadCustomerData & d) 235 | { 236 | if (d.block_tx_buffer) 237 | return; 238 | 239 | /* Return if there's no data to be written to the 240 | * serial interface. This statement is necessary 241 | * because otherwise the prefix/suffix functions 242 | * will be invoked and will be printing something, 243 | * even though no data is actually to be printed for 244 | * most threads. 245 | */ 246 | if (!d.tx_buffer.available()) 247 | return; 248 | 249 | /* Retrieve all data stored in the transmit ringbuffer 250 | * and store it into a String for usage by both suffix 251 | * prefix callback functions. 252 | */ 253 | String msg; 254 | while(d.tx_buffer.available()) 255 | msg += static_cast(d.tx_buffer.read_char()); 256 | 257 | /* The prefix callback function allows the 258 | * user to insert a custom message before 259 | * a new message is written to the serial 260 | * driver. This is useful e.g. for wrapping 261 | * protocol (e.g. the 'AT' protocol) or providing 262 | * a timestamp, a log level, ... 263 | */ 264 | String prefix; 265 | if (d.prefix_func) 266 | prefix = d.prefix_func(msg); 267 | /* A prefix callback function defined per thread 268 | * takes precedence over a globally defined prefix 269 | * callback function. 270 | */ 271 | else if (_global_prefix_callback) 272 | prefix = _global_prefix_callback(msg); 273 | 274 | /* Similar to the prefix function this callback 275 | * allows the user to specify a specific message 276 | * to be appended to each message, e.g. '\r\n'. 277 | */ 278 | String suffix; 279 | if (d.suffix_func) 280 | suffix = d.suffix_func(prefix, msg); 281 | /* A suffix callback function defined per thread 282 | * takes precedence over a globally defined suffix 283 | * callback function. 284 | */ 285 | else if (_global_suffix_callback) 286 | suffix = _global_suffix_callback(prefix, msg); 287 | 288 | /* Now it's time to actually write the message 289 | * conveyed by the user via Serial.print/println. 290 | */ 291 | _serial.write(prefix.c_str()); 292 | _serial.write(msg.c_str()); 293 | _serial.write(suffix.c_str()); 294 | }); 295 | } 296 | } 297 | 298 | std::list::iterator SerialDispatcher::findThreadCustomerDataById(osThreadId_t const thread_id) 299 | { 300 | return std::find_if(std::begin(_thread_customer_list), 301 | std::end (_thread_customer_list), 302 | [thread_id](ThreadCustomerData const d) -> bool 303 | { 304 | return (d.thread_id == thread_id); 305 | }); 306 | } 307 | 308 | void SerialDispatcher::prepareSerialReader(std::list::iterator & iter) 309 | { 310 | if (!iter->rx_buffer) 311 | iter->rx_buffer.reset(new arduino::RingBuffer()); 312 | } 313 | 314 | void SerialDispatcher::handleSerialReader() 315 | { 316 | while (_serial.available()) 317 | { 318 | int const c = _serial.read(); 319 | 320 | std::for_each(std::begin(_thread_customer_list), 321 | std::end (_thread_customer_list), 322 | [c](ThreadCustomerData & d) 323 | { 324 | if (!d.rx_buffer) 325 | return; 326 | 327 | if (!d.rx_buffer->availableForStore()) 328 | return; 329 | 330 | d.rx_buffer->store_char(c); 331 | }); 332 | } 333 | } 334 | 335 | /************************************************************************************** 336 | * INCLUDE 337 | **************************************************************************************/ 338 | 339 | #include 340 | 341 | /************************************************************************************** 342 | * GLOBAL VARIABLE DECLARATION 343 | **************************************************************************************/ 344 | 345 | #ifdef ARDUINO_GENERIC_STM32H747_M4 346 | SerialDispatcher Serial(Serial1); /* No SerialUSB for Portenta H7 / M4 Core */ 347 | #else 348 | SerialDispatcher Serial(SerialUSB); 349 | #endif 350 | -------------------------------------------------------------------------------- /src/io/serial/SerialDispatcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef SERIAL_DISPATCHER_H_ 20 | #define SERIAL_DISPATCHER_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include "api/HardwareSerial.h" 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | /************************************************************************************** 36 | * CLASS DECLARATION 37 | **************************************************************************************/ 38 | 39 | class SerialDispatcher : public arduino::HardwareSerial 40 | { 41 | 42 | public: 43 | 44 | SerialDispatcher(arduino::HardwareSerial & serial); 45 | 46 | 47 | virtual void begin(unsigned long baudrate) override; 48 | virtual void begin(unsigned long baudrate, uint16_t config) override; 49 | virtual void end() override; 50 | virtual int available() override; 51 | virtual int peek() override; 52 | virtual int read() override; 53 | virtual void flush() override; 54 | virtual size_t write(uint8_t const b) override; 55 | virtual size_t write(const uint8_t * data, size_t len) override; 56 | using Print::write; 57 | virtual operator bool() override { return _serial; } 58 | 59 | void block(); 60 | void unblock(); 61 | 62 | typedef std::function PrefixInjectorCallbackFunc; 63 | typedef std::function SuffixInjectorCallbackFunc; 64 | void prefix(PrefixInjectorCallbackFunc func); 65 | void suffix(SuffixInjectorCallbackFunc func); 66 | void globalPrefix(PrefixInjectorCallbackFunc func); 67 | void globalSuffix(SuffixInjectorCallbackFunc func); 68 | 69 | 70 | private: 71 | 72 | bool _is_initialized; 73 | rtos::Mutex _mutex; 74 | rtos::EventFlags _data_available_for_transmit; 75 | arduino::HardwareSerial & _serial; 76 | 77 | rtos::Thread _thread; 78 | bool _has_tread_started; 79 | bool _terminate_thread; 80 | 81 | PrefixInjectorCallbackFunc _global_prefix_callback; 82 | SuffixInjectorCallbackFunc _global_suffix_callback; 83 | 84 | static int constexpr THREADSAFE_SERIAL_TRANSMIT_RINGBUFFER_SIZE = 128; 85 | typedef arduino::RingBufferN SerialTransmitRingbuffer; 86 | 87 | class ThreadCustomerData 88 | { 89 | public: 90 | ThreadCustomerData(osThreadId_t const t, uint32_t const t_event_flag) 91 | : thread_id{t} 92 | , thread_event_flag{t_event_flag} 93 | , tx_buffer{} 94 | , block_tx_buffer{false} 95 | , rx_buffer{} 96 | , prefix_func{nullptr} 97 | , suffix_func{nullptr} 98 | { } 99 | 100 | osThreadId_t thread_id; 101 | uint32_t thread_event_flag; 102 | SerialTransmitRingbuffer tx_buffer; 103 | bool block_tx_buffer; 104 | mbed::SharedPtr rx_buffer; /* Only when a thread has expressed interested to read from serial a receive ringbuffer is allocated. */ 105 | PrefixInjectorCallbackFunc prefix_func; 106 | SuffixInjectorCallbackFunc suffix_func; 107 | }; 108 | 109 | std::list _thread_customer_list; 110 | 111 | void threadFunc(); 112 | std::list::iterator findThreadCustomerDataById(osThreadId_t const thread_id); 113 | void prepareSerialReader(std::list::iterator & iter); 114 | void handleSerialReader(); 115 | }; 116 | 117 | /************************************************************************************** 118 | * EXTERN DECLARATION 119 | **************************************************************************************/ 120 | 121 | #undef Serial 122 | extern SerialDispatcher Serial; 123 | 124 | #endif /* SERIAL_DISPATCHER_H_ */ 125 | -------------------------------------------------------------------------------- /src/io/spi/SpiBusDevice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "SpiBusDevice.h" 24 | 25 | #include "SpiDispatcher.h" 26 | 27 | /************************************************************************************** 28 | * CTOR/DTOR 29 | **************************************************************************************/ 30 | 31 | SpiBusDevice::SpiBusDevice(SpiBusDeviceConfig const & config) 32 | : _config{config} 33 | { 34 | 35 | } 36 | 37 | /************************************************************************************** 38 | * PUBLIC MEMBER FUNCTIONS 39 | **************************************************************************************/ 40 | 41 | IoResponse SpiBusDevice::transfer(IoRequest & req) 42 | { 43 | return SpiDispatcher::instance().dispatch(&req, &_config); 44 | } 45 | 46 | bool SpiBusDevice::read(uint8_t * buffer, size_t len, uint8_t sendvalue) 47 | { 48 | SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.selectFunc(), _config.deselectFunc(), sendvalue); 49 | IoRequest req(nullptr, 0, buffer, len); 50 | IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &config); 51 | rsp->wait(); 52 | return true; 53 | } 54 | 55 | bool SpiBusDevice::write(uint8_t * buffer, size_t len) 56 | { 57 | IoRequest req(buffer, len, nullptr, 0); 58 | IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &_config); 59 | rsp->wait(); 60 | return true; 61 | } 62 | 63 | bool SpiBusDevice::writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue) 64 | { 65 | SpiBusDeviceConfig config(_config.spi(), _config.settings(), _config.selectFunc(), _config.deselectFunc(), sendvalue); 66 | IoRequest req(write_buffer, write_len, read_buffer, read_len); 67 | IoResponse rsp = SpiDispatcher::instance().dispatch(&req, &config); 68 | rsp->wait(); 69 | return true; 70 | } 71 | -------------------------------------------------------------------------------- /src/io/spi/SpiBusDevice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef SPI_BUS_DEVICE_H_ 20 | #define SPI_BUS_DEVICE_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include "../BusDevice.h" 27 | 28 | #include "SpiBusDeviceConfig.h" 29 | 30 | /************************************************************************************** 31 | * CLASS DECLARATION 32 | **************************************************************************************/ 33 | 34 | class SpiBusDevice : public BusDeviceBase 35 | { 36 | public: 37 | 38 | SpiBusDevice(SpiBusDeviceConfig const & config); 39 | virtual ~SpiBusDevice() { } 40 | 41 | 42 | virtual IoResponse transfer(IoRequest & req) override; 43 | 44 | 45 | bool read(uint8_t * buffer, size_t len, uint8_t sendvalue = 0xFF); 46 | bool write(uint8_t * buffer, size_t len); 47 | bool writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, uint8_t sendvalue = 0xFF); 48 | 49 | 50 | private: 51 | 52 | SpiBusDeviceConfig _config; 53 | 54 | }; 55 | 56 | #endif /* SPI_BUS_DEVICE_H_ */ 57 | -------------------------------------------------------------------------------- /src/io/spi/SpiBusDeviceConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef SPI_BUS_DEVICE_CONFIG_H_ 20 | #define SPI_BUS_DEVICE_CONFIG_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | #include 29 | 30 | #include 31 | 32 | /************************************************************************************** 33 | * CLASS DECLARATION 34 | **************************************************************************************/ 35 | 36 | class SpiBusDeviceConfig 37 | { 38 | public: 39 | 40 | typedef std::function SpiSelectFunc; 41 | typedef std::function SpiDeselectFunc; 42 | 43 | 44 | SpiBusDeviceConfig(arduino::HardwareSPI & spi, SPISettings const & spi_settings, SpiSelectFunc spi_select, SpiDeselectFunc spi_deselect, byte const fill_symbol = 0xFF) 45 | : _spi{spi} 46 | , _spi_settings{spi_settings} 47 | , _spi_select{spi_select} 48 | , _spi_deselect{spi_deselect} 49 | , _fill_symbol{fill_symbol} 50 | { } 51 | 52 | SpiBusDeviceConfig(arduino::HardwareSPI & spi, SPISettings const & spi_settings, int const cs_pin, byte const fill_symbol = 0xFF) 53 | : SpiBusDeviceConfig 54 | {spi, 55 | spi_settings, 56 | [cs_pin](){ digitalWrite(cs_pin, LOW); }, 57 | [cs_pin](){ digitalWrite(cs_pin, HIGH); }, 58 | fill_symbol 59 | } 60 | { } 61 | 62 | 63 | arduino::HardwareSPI & spi() { return _spi; } 64 | SPISettings settings () const { return _spi_settings; } 65 | void select () const { if (_spi_select) _spi_select(); } 66 | void deselect () const { if (_spi_deselect) _spi_deselect(); } 67 | byte fillSymbol () const { return _fill_symbol; } 68 | 69 | SpiSelectFunc selectFunc () const { return _spi_select; } 70 | SpiDeselectFunc deselectFunc() const { return _spi_deselect; } 71 | 72 | private: 73 | 74 | arduino::HardwareSPI & _spi; 75 | SPISettings _spi_settings; 76 | SpiSelectFunc _spi_select{nullptr}; 77 | SpiDeselectFunc _spi_deselect{nullptr}; 78 | byte _fill_symbol{0xFF}; 79 | 80 | }; 81 | 82 | #endif /* SPI_BUS_DEVICE_CONFIG_H_ */ 83 | -------------------------------------------------------------------------------- /src/io/spi/SpiDispatcher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "SpiDispatcher.h" 24 | 25 | #include 26 | 27 | /************************************************************************************** 28 | * STATIC MEMBER DEFINITION 29 | **************************************************************************************/ 30 | 31 | SpiDispatcher * SpiDispatcher::_p_instance{nullptr}; 32 | rtos::Mutex SpiDispatcher::_mutex; 33 | 34 | /************************************************************************************** 35 | * CTOR/DTOR 36 | **************************************************************************************/ 37 | 38 | SpiDispatcher::SpiDispatcher() 39 | : _thread(osPriorityRealtime, 4096, nullptr, "SpiDispatcher") 40 | , _has_tread_started{false} 41 | , _terminate_thread{false} 42 | { 43 | begin(); 44 | } 45 | 46 | SpiDispatcher::~SpiDispatcher() 47 | { 48 | end(); 49 | } 50 | 51 | /************************************************************************************** 52 | * PUBLIC MEMBER FUNCTIONS 53 | **************************************************************************************/ 54 | 55 | SpiDispatcher & SpiDispatcher::instance() 56 | { 57 | mbed::ScopedLock lock(_mutex); 58 | if (!_p_instance) { 59 | _p_instance = new SpiDispatcher(); 60 | } 61 | return *_p_instance; 62 | } 63 | 64 | void SpiDispatcher::destroy() 65 | { 66 | mbed::ScopedLock lock(_mutex); 67 | delete _p_instance; 68 | _p_instance = nullptr; 69 | } 70 | 71 | IoResponse SpiDispatcher::dispatch(IoRequest * req, SpiBusDeviceConfig * config) 72 | { 73 | mbed::ScopedLock lock(_mutex); 74 | 75 | SpiIoTransaction * spi_io_transaction = _spi_io_transaction_mailbox.try_alloc(); 76 | if (!spi_io_transaction) 77 | return nullptr; 78 | 79 | IoResponse rsp(new impl::IoResponse()); 80 | 81 | spi_io_transaction->req = req; 82 | spi_io_transaction->rsp = rsp; 83 | spi_io_transaction->config = config; 84 | 85 | _spi_io_transaction_mailbox.put(spi_io_transaction); 86 | 87 | return rsp; 88 | } 89 | 90 | /************************************************************************************** 91 | * PRIVATE MEMBER FUNCTIONS 92 | **************************************************************************************/ 93 | 94 | void SpiDispatcher::begin() 95 | { 96 | SPI.begin(); 97 | _thread.start(mbed::callback(this, &SpiDispatcher::threadFunc)); /* TODO: Check return code */ 98 | /* It is necessary to wait until the SpiDispatcher::threadFunc() 99 | * has started, otherwise other threads might trigger IO requests 100 | * before this thread is actually running. 101 | */ 102 | while (!_has_tread_started) { } 103 | } 104 | 105 | void SpiDispatcher::end() 106 | { 107 | _terminate_thread = true; 108 | _thread.join(); /* TODO: Check return code */ 109 | SPI.end(); 110 | } 111 | 112 | void SpiDispatcher::threadFunc() 113 | { 114 | _has_tread_started = true; 115 | 116 | while(!_terminate_thread) 117 | { 118 | /* Wait blocking for the next IO transaction 119 | * request to be posted to the mailbox. 120 | */ 121 | SpiIoTransaction * spi_io_transaction = _spi_io_transaction_mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever); 122 | if (spi_io_transaction) 123 | { 124 | processSpiIoRequest(spi_io_transaction); 125 | /* Free the allocated memory (memory allocated 126 | * during dispatch(...) 127 | */ 128 | _spi_io_transaction_mailbox.free(spi_io_transaction); 129 | } 130 | } 131 | } 132 | 133 | void SpiDispatcher::processSpiIoRequest(SpiIoTransaction * spi_io_transaction) 134 | { 135 | IoRequest * io_request = spi_io_transaction->req; 136 | IoResponse io_response = spi_io_transaction->rsp; 137 | SpiBusDeviceConfig * config = spi_io_transaction->config; 138 | 139 | config->select(); 140 | 141 | config->spi().beginTransaction(config->settings()); 142 | 143 | /* In a first step transmit the complete write buffer and 144 | * write back the receive data directly into the write buffer 145 | */ 146 | size_t bytes_sent = 0; 147 | for(; bytes_sent < io_request->bytes_to_write; bytes_sent++) 148 | { 149 | uint8_t const tx_byte = io_request->write_buf[bytes_sent]; 150 | uint8_t const rx_byte = config->spi().transfer(tx_byte); 151 | 152 | io_request->write_buf[bytes_sent] = rx_byte; 153 | } 154 | 155 | /* In a second step, transmit the fill symbol and write the 156 | * received data into the read buffer. 157 | */ 158 | size_t bytes_received = 0; 159 | for(; bytes_received < io_request->bytes_to_read; bytes_received++) 160 | { 161 | uint8_t const tx_byte = config->fillSymbol(); 162 | uint8_t const rx_byte = config->spi().transfer(tx_byte); 163 | 164 | io_request->read_buf[bytes_received] = rx_byte; 165 | } 166 | 167 | config->spi().endTransaction(); 168 | 169 | config->deselect(); 170 | 171 | io_response->bytes_written = bytes_sent; 172 | io_response->bytes_read = bytes_received; 173 | 174 | io_response->done(); 175 | } 176 | -------------------------------------------------------------------------------- /src/io/spi/SpiDispatcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef SPI_DISPATCHER_H_ 20 | #define SPI_DISPATCHER_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | #include "../IoTransaction.h" 29 | 30 | #include "SpiBusDeviceConfig.h" 31 | 32 | /************************************************************************************** 33 | * CLASS DECLARATION 34 | **************************************************************************************/ 35 | 36 | class SpiDispatcher 37 | { 38 | public: 39 | 40 | SpiDispatcher(SpiDispatcher &) = delete; 41 | void operator = (SpiDispatcher &) = delete; 42 | 43 | static SpiDispatcher & instance(); 44 | static void destroy(); 45 | 46 | IoResponse dispatch(IoRequest * req, SpiBusDeviceConfig * config); 47 | 48 | private: 49 | 50 | static SpiDispatcher * _p_instance; 51 | static rtos::Mutex _mutex; 52 | 53 | rtos::Thread _thread; 54 | bool _has_tread_started; 55 | bool _terminate_thread; 56 | 57 | typedef struct 58 | { 59 | IoRequest * req; 60 | IoResponse rsp; 61 | SpiBusDeviceConfig * config; 62 | } SpiIoTransaction; 63 | 64 | static size_t constexpr REQUEST_QUEUE_SIZE = 32; 65 | rtos::Mail _spi_io_transaction_mailbox; 66 | 67 | SpiDispatcher(); 68 | ~SpiDispatcher(); 69 | 70 | void begin(); 71 | void end(); 72 | void threadFunc(); 73 | void processSpiIoRequest(SpiIoTransaction * spi_io_transaction); 74 | }; 75 | 76 | #endif /* SPI_DISPATCHER_H_ */ 77 | -------------------------------------------------------------------------------- /src/io/util/util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "util.h" 24 | 25 | /************************************************************************************** 26 | * FUNCTION DEFINITION 27 | **************************************************************************************/ 28 | 29 | IoResponse transferAndWait(BusDevice & dev, IoRequest & req) 30 | { 31 | IoResponse rsp = dev.transfer(req); 32 | rsp->wait(); 33 | return rsp; 34 | } 35 | -------------------------------------------------------------------------------- /src/io/util/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef ARDUINO_THREADS_UTIL_H_ 20 | #define ARDUINO_THREADS_UTIL_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include "../BusDevice.h" 27 | #include "../IoTransaction.h" 28 | 29 | /************************************************************************************** 30 | * FUNCTION DECLARATION 31 | **************************************************************************************/ 32 | 33 | IoResponse transferAndWait(BusDevice & dev, IoRequest & req); 34 | 35 | #endif /* ARDUINO_THREADS_UTIL_H_ */ -------------------------------------------------------------------------------- /src/io/wire/WireBusDevice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "WireBusDevice.h" 24 | 25 | #include "WireDispatcher.h" 26 | 27 | /************************************************************************************** 28 | * CTOR/DTOR 29 | **************************************************************************************/ 30 | 31 | WireBusDevice::WireBusDevice(WireBusDeviceConfig const & config) 32 | : _config{config} 33 | { 34 | 35 | } 36 | 37 | /************************************************************************************** 38 | * PUBLIC MEMBER FUNCTIONS 39 | **************************************************************************************/ 40 | 41 | IoResponse WireBusDevice::transfer(IoRequest & req) 42 | { 43 | return WireDispatcher::instance().dispatch(&req, &_config); 44 | } 45 | 46 | bool WireBusDevice::read(uint8_t * buffer, size_t len, bool stop) 47 | { 48 | WireBusDeviceConfig config(_config.wire(), _config.slaveAddr(), _config.restart(), stop); 49 | IoRequest req(nullptr, 0, buffer, len); 50 | IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config); 51 | rsp->wait(); 52 | return true; 53 | } 54 | 55 | bool WireBusDevice::write(uint8_t * buffer, size_t len, bool stop) 56 | { 57 | bool const restart = !stop; 58 | WireBusDeviceConfig config(_config.wire(), _config.slaveAddr(), restart, _config.stop()); 59 | IoRequest req(buffer, len, nullptr, 0); 60 | IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config); 61 | rsp->wait(); 62 | return true; 63 | } 64 | 65 | bool WireBusDevice::writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop) 66 | { 67 | /* Copy the Wire parameters from the device and modify only those 68 | * which can be modified via the parameters of this function. 69 | */ 70 | bool const restart = !stop; 71 | WireBusDeviceConfig config(_config.wire(), _config.slaveAddr(), restart, _config.stop()); 72 | /* Fire off the IO request and await its response. */ 73 | IoRequest req(write_buffer, write_len, read_buffer, read_len); 74 | IoResponse rsp = WireDispatcher::instance().dispatch(&req, &config); 75 | rsp->wait(); 76 | /* TODO: Introduce error codes within the IoResponse and evaluate 77 | * them here. 78 | */ 79 | return true; 80 | } 81 | -------------------------------------------------------------------------------- /src/io/wire/WireBusDevice.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef WIRE_BUS_DEVICE_H_ 20 | #define WIRE_BUS_DEVICE_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include "../BusDevice.h" 27 | 28 | #include "WireBusDeviceConfig.h" 29 | 30 | /************************************************************************************** 31 | * CLASS DECLARATION 32 | **************************************************************************************/ 33 | 34 | class WireBusDevice : public BusDeviceBase 35 | { 36 | public: 37 | 38 | WireBusDevice(WireBusDeviceConfig const & config); 39 | virtual ~WireBusDevice() { } 40 | 41 | 42 | virtual IoResponse transfer(IoRequest & req) override; 43 | 44 | 45 | bool read(uint8_t * buffer, size_t len, bool stop = true); 46 | bool write(uint8_t * buffer, size_t len, bool stop = true); 47 | bool writeThenRead(uint8_t * write_buffer, size_t write_len, uint8_t * read_buffer, size_t read_len, bool stop = false); 48 | 49 | 50 | private: 51 | 52 | WireBusDeviceConfig _config; 53 | 54 | }; 55 | 56 | #endif /* WIRE_BUS_DEVICE_H_ */ 57 | -------------------------------------------------------------------------------- /src/io/wire/WireBusDeviceConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef WIRE_BUS_DEVICE_CONFIG_H_ 20 | #define WIRE_BUS_DEVICE_CONFIG_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | #include 29 | 30 | /************************************************************************************** 31 | * CLASS DECLARATION 32 | **************************************************************************************/ 33 | 34 | class WireBusDeviceConfig 35 | { 36 | public: 37 | 38 | WireBusDeviceConfig(arduino::HardwareI2C & wire, byte const slave_addr, bool const restart, bool const stop) 39 | : _wire{wire} 40 | , _slave_addr{slave_addr} 41 | , _restart{restart} 42 | , _stop{stop} 43 | { } 44 | 45 | 46 | inline arduino::HardwareI2C & wire() { return _wire; } 47 | inline byte slaveAddr() const { return _slave_addr; } 48 | inline bool restart() const { return _restart; } 49 | inline bool stop() const { return _stop; } 50 | 51 | 52 | private: 53 | 54 | arduino::HardwareI2C & _wire; 55 | byte _slave_addr{0x00}; 56 | bool _restart{true}, _stop{true}; 57 | 58 | }; 59 | 60 | #endif /* WIRE_BUS_DEVICE_CONFIG_H_ */ 61 | -------------------------------------------------------------------------------- /src/io/wire/WireDispatcher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | /************************************************************************************** 20 | * INCLUDE 21 | **************************************************************************************/ 22 | 23 | #include "WireDispatcher.h" 24 | 25 | #include 26 | 27 | /************************************************************************************** 28 | * STATIC MEMBER DEFINITION 29 | **************************************************************************************/ 30 | 31 | WireDispatcher * WireDispatcher::_p_instance{nullptr}; 32 | rtos::Mutex WireDispatcher::_mutex; 33 | 34 | /************************************************************************************** 35 | * CTOR/DTOR 36 | **************************************************************************************/ 37 | 38 | WireDispatcher::WireDispatcher() 39 | : _thread(osPriorityRealtime, 4096, nullptr, "WireDispatcher") 40 | , _has_tread_started{false} 41 | , _terminate_thread{false} 42 | { 43 | begin(); 44 | } 45 | 46 | WireDispatcher::~WireDispatcher() 47 | { 48 | end(); 49 | } 50 | 51 | /************************************************************************************** 52 | * PUBLIC MEMBER FUNCTIONS 53 | **************************************************************************************/ 54 | 55 | WireDispatcher & WireDispatcher::instance() 56 | { 57 | mbed::ScopedLock lock(_mutex); 58 | if (!_p_instance) { 59 | _p_instance = new WireDispatcher(); 60 | } 61 | return *_p_instance; 62 | } 63 | 64 | void WireDispatcher::destroy() 65 | { 66 | mbed::ScopedLock lock(_mutex); 67 | delete _p_instance; 68 | _p_instance = nullptr; 69 | } 70 | 71 | IoResponse WireDispatcher::dispatch(IoRequest * req, WireBusDeviceConfig * config) 72 | { 73 | mbed::ScopedLock lock(_mutex); 74 | 75 | WireIoTransaction * wire_io_transaction = _wire_io_transaction_mailbox.try_alloc(); 76 | if (!wire_io_transaction) 77 | return nullptr; 78 | 79 | IoResponse rsp(new impl::IoResponse()); 80 | 81 | wire_io_transaction->req = req; 82 | wire_io_transaction->rsp = rsp; 83 | wire_io_transaction->config = config; 84 | 85 | _wire_io_transaction_mailbox.put(wire_io_transaction); 86 | 87 | return rsp; 88 | } 89 | 90 | /************************************************************************************** 91 | * PRIVATE MEMBER FUNCTIONS 92 | **************************************************************************************/ 93 | 94 | void WireDispatcher::begin() 95 | { 96 | Wire.begin(); 97 | _thread.start(mbed::callback(this, &WireDispatcher::threadFunc)); /* TODO: Check return code */ 98 | /* It is necessary to wait until the WireDispatcher::threadFunc() 99 | * has started, otherwise other threads might trigger IO requests 100 | * before this thread is actually running. 101 | */ 102 | while (!_has_tread_started) { } 103 | } 104 | 105 | void WireDispatcher::end() 106 | { 107 | _terminate_thread = true; 108 | _thread.join(); /* TODO: Check return code */ 109 | Wire.end(); 110 | } 111 | 112 | void WireDispatcher::threadFunc() 113 | { 114 | _has_tread_started = true; 115 | 116 | while(!_terminate_thread) 117 | { 118 | /* Wait blocking for the next IO transaction 119 | * request to be posted to the mailbox. 120 | */ 121 | WireIoTransaction * wire_io_transaction = _wire_io_transaction_mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever); 122 | if (wire_io_transaction) 123 | { 124 | processWireIoRequest(wire_io_transaction); 125 | /* Free the allocated memory (memory allocated 126 | * during dispatch(...) 127 | */ 128 | _wire_io_transaction_mailbox.free(wire_io_transaction); 129 | } 130 | } 131 | } 132 | 133 | void WireDispatcher::processWireIoRequest(WireIoTransaction * wire_io_transaction) 134 | { 135 | IoRequest * io_request = wire_io_transaction->req; 136 | IoResponse io_response = wire_io_transaction->rsp; 137 | WireBusDeviceConfig * config = wire_io_transaction->config; 138 | 139 | if (io_request->bytes_to_write > 0) 140 | { 141 | config->wire().beginTransmission(config->slaveAddr()); 142 | 143 | size_t bytes_written = 0; 144 | for (; bytes_written < io_request->bytes_to_write; bytes_written++) 145 | { 146 | config->wire().write(io_request->write_buf[bytes_written]); 147 | } 148 | io_response->bytes_written = bytes_written; 149 | 150 | if (config->restart() && (io_request->bytes_to_read > 0)) 151 | config->wire().endTransmission(false /* stop */); 152 | else 153 | config->wire().endTransmission(true /* stop */); 154 | } 155 | 156 | if (io_request->bytes_to_read > 0) 157 | { 158 | config->wire().requestFrom(config->slaveAddr(), io_request->bytes_to_read, config->stop()); 159 | 160 | while(config->wire().available() != static_cast(io_request->bytes_to_read)) 161 | { 162 | /* TODO: Insert a timeout. */ 163 | } 164 | 165 | size_t bytes_read = 0; 166 | for (; bytes_read < io_request->bytes_to_read; bytes_read++) 167 | { 168 | io_request->read_buf[bytes_read] = config->wire().read(); 169 | } 170 | io_response->bytes_read = bytes_read; 171 | } 172 | 173 | io_response->done(); 174 | } 175 | -------------------------------------------------------------------------------- /src/io/wire/WireDispatcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef WIRE_DISPATCHER_H_ 20 | #define WIRE_DISPATCHER_H_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | #include "../IoTransaction.h" 29 | 30 | #include "WireBusDeviceConfig.h" 31 | 32 | /************************************************************************************** 33 | * CLASS DECLARATION 34 | **************************************************************************************/ 35 | 36 | class WireDispatcher 37 | { 38 | public: 39 | 40 | WireDispatcher(WireDispatcher &) = delete; 41 | void operator = (WireDispatcher &) = delete; 42 | 43 | static WireDispatcher & instance(); 44 | static void destroy(); 45 | 46 | 47 | IoResponse dispatch(IoRequest * req, WireBusDeviceConfig * config); 48 | 49 | 50 | private: 51 | 52 | static WireDispatcher * _p_instance; 53 | static rtos::Mutex _mutex; 54 | 55 | rtos::Thread _thread; 56 | bool _has_tread_started; 57 | bool _terminate_thread; 58 | 59 | typedef struct 60 | { 61 | IoRequest * req; 62 | IoResponse rsp; 63 | WireBusDeviceConfig * config; 64 | } WireIoTransaction; 65 | 66 | static size_t constexpr REQUEST_QUEUE_SIZE = 32; 67 | rtos::Mail _wire_io_transaction_mailbox; 68 | 69 | WireDispatcher(); 70 | ~WireDispatcher(); 71 | 72 | void begin(); 73 | void end(); 74 | void threadFunc(); 75 | void processWireIoRequest(WireIoTransaction * wire_io_transaction); 76 | }; 77 | 78 | #endif /* WIRE_DISPATCHER_H_ */ 79 | -------------------------------------------------------------------------------- /src/threading/CircularBuffer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef ARDUINO_THREADS_RINGBUFFER_HPP_ 20 | #define ARDUINO_THREADS_RINGBUFFER_HPP_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | /************************************************************************************** 29 | * CLASS DECLARATION 30 | **************************************************************************************/ 31 | 32 | template 33 | class CircularBuffer 34 | { 35 | public: 36 | 37 | CircularBuffer(size_t const size); 38 | 39 | void store(T const data); 40 | T read(); 41 | bool isFull() const; 42 | bool isEmpty() const; 43 | 44 | 45 | private: 46 | 47 | mbed::SharedPtr _data; 48 | size_t const _size; 49 | size_t _head, _tail, _num_elems; 50 | 51 | size_t next(size_t const idx); 52 | }; 53 | 54 | /************************************************************************************** 55 | * CTOR/DTOR 56 | **************************************************************************************/ 57 | 58 | template 59 | CircularBuffer::CircularBuffer(size_t const size) 60 | : _data{new T[size]} 61 | , _size{size} 62 | , _head{0} 63 | , _tail{0} 64 | , _num_elems{0} 65 | { 66 | } 67 | 68 | /************************************************************************************** 69 | * PUBLIC MEMBER FUNCTIONS 70 | **************************************************************************************/ 71 | 72 | template 73 | void CircularBuffer::store(T const data) 74 | { 75 | if (!isFull()) 76 | { 77 | _data.get()[_head] = data; 78 | _head = next(_head); 79 | _num_elems++; 80 | } 81 | } 82 | 83 | template 84 | T CircularBuffer::read() 85 | { 86 | if (isEmpty()) 87 | return T{0}; 88 | 89 | T const value = _data.get()[_tail]; 90 | _tail = next(_tail); 91 | _num_elems--; 92 | 93 | return value; 94 | } 95 | 96 | template 97 | bool CircularBuffer::isFull() const 98 | { 99 | return (_num_elems == _size); 100 | } 101 | 102 | template 103 | bool CircularBuffer::isEmpty() const 104 | { 105 | return (_num_elems == 0); 106 | } 107 | 108 | /************************************************************************************** 109 | * PRIVATE MEMBER FUNCTIONS 110 | **************************************************************************************/ 111 | 112 | template 113 | size_t CircularBuffer::next(size_t const idx) 114 | { 115 | return ((idx + 1) % _size); 116 | } 117 | 118 | #endif /* ARDUINO_THREADS_RINGBUFFER_HPP_ */ 119 | -------------------------------------------------------------------------------- /src/threading/Shared.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef ARDUINO_THREADS_SHARED_HPP_ 20 | #define ARDUINO_THREADS_SHARED_HPP_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | /************************************************************************************** 29 | * CONSTANT 30 | **************************************************************************************/ 31 | 32 | static size_t constexpr SHARED_QUEUE_SIZE = 16; 33 | 34 | /************************************************************************************** 35 | * CLASS DECLARATION 36 | **************************************************************************************/ 37 | 38 | template 39 | class Shared 40 | { 41 | public: 42 | 43 | T pop(); 44 | void push(T const & val); 45 | inline T peek() const { return _val; } 46 | 47 | private: 48 | 49 | T _val; 50 | rtos::Mail _mailbox; 51 | 52 | }; 53 | 54 | /************************************************************************************** 55 | * PUBLIC MEMBER FUNCTIONS 56 | **************************************************************************************/ 57 | 58 | template 59 | T Shared::pop() 60 | { 61 | T * val_ptr = _mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever); 62 | if (val_ptr) 63 | { 64 | T const tmp_val = *val_ptr; 65 | _mailbox.free(val_ptr); 66 | return tmp_val; 67 | } 68 | return _val; 69 | } 70 | 71 | template 72 | void Shared::push(T const & val) 73 | { 74 | /* If the mailbox is full we are discarding the 75 | * oldest element and then push the new one into 76 | * the queue. 77 | **/ 78 | if (_mailbox.full()) 79 | { 80 | T * val_ptr = _mailbox.try_get_for(rtos::Kernel::wait_for_u32_forever); 81 | _mailbox.free(val_ptr); 82 | } 83 | 84 | _val = val; 85 | 86 | T * val_ptr = _mailbox.try_alloc(); 87 | if (val_ptr) 88 | { 89 | *val_ptr = val; 90 | _mailbox.put(val_ptr); 91 | } 92 | } 93 | 94 | #endif /* ARDUINO_THREADS_SHARED_HPP_ */ 95 | -------------------------------------------------------------------------------- /src/threading/Sink.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef ARDUINO_THREADS_SINK_HPP_ 20 | #define ARDUINO_THREADS_SINK_HPP_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | 28 | #include "CircularBuffer.hpp" 29 | 30 | /************************************************************************************** 31 | * CLASS DECLARATION 32 | **************************************************************************************/ 33 | 34 | template 35 | class SinkBase 36 | { 37 | public: 38 | 39 | virtual ~SinkBase() { } 40 | 41 | virtual T pop() = 0; 42 | virtual void inject(T const & value) = 0; 43 | }; 44 | 45 | template 46 | class SinkNonBlocking : public SinkBase 47 | { 48 | public: 49 | 50 | SinkNonBlocking() { } 51 | virtual ~SinkNonBlocking() { } 52 | 53 | virtual T pop() override; 54 | virtual void inject(T const & value) override; 55 | 56 | 57 | private: 58 | 59 | T _data; 60 | rtos::Mutex _mutex; 61 | 62 | }; 63 | 64 | template 65 | class SinkBlocking : public SinkBase 66 | { 67 | public: 68 | 69 | SinkBlocking(size_t const size); 70 | virtual ~SinkBlocking() { } 71 | 72 | virtual T pop() override; 73 | virtual void inject(T const & value) override; 74 | 75 | 76 | private: 77 | 78 | CircularBuffer _data; 79 | rtos::Mutex _mutex; 80 | rtos::ConditionVariable _cond_data_available; 81 | rtos::ConditionVariable _cond_slot_available; 82 | 83 | }; 84 | 85 | /************************************************************************************** 86 | * PUBLIC MEMBER FUNCTIONS - SinkNonBlocking 87 | **************************************************************************************/ 88 | 89 | template 90 | T SinkNonBlocking::pop() 91 | { 92 | _mutex.lock(); 93 | return _data; 94 | _mutex.unlock(); 95 | } 96 | 97 | template 98 | void SinkNonBlocking::inject(T const & value) 99 | { 100 | _mutex.lock(); 101 | _data = value; 102 | _mutex.unlock(); 103 | } 104 | 105 | /************************************************************************************** 106 | * PUBLIC MEMBER FUNCTIONS - SinkBlocking 107 | **************************************************************************************/ 108 | 109 | template 110 | SinkBlocking::SinkBlocking(size_t const size) 111 | : _data(size) 112 | , _cond_data_available(_mutex) 113 | , _cond_slot_available(_mutex) 114 | { } 115 | 116 | template 117 | T SinkBlocking::pop() 118 | { 119 | _mutex.lock(); 120 | while (_data.isEmpty()) 121 | _cond_data_available.wait(); 122 | T const d = _data.read(); 123 | _cond_slot_available.notify_all(); 124 | _mutex.unlock(); 125 | return d; 126 | } 127 | 128 | template 129 | void SinkBlocking::inject(T const & value) 130 | { 131 | _mutex.lock(); 132 | while (_data.isFull()) 133 | _cond_slot_available.wait(); 134 | _data.store(value); 135 | _cond_data_available.notify_all(); 136 | _mutex.unlock(); 137 | } 138 | 139 | #endif /* ARDUINO_THREADS_SINK_HPP_ */ 140 | -------------------------------------------------------------------------------- /src/threading/Source.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the Arduino_ThreadsafeIO library. 3 | * Copyright (c) 2021 Arduino SA. 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2.1 of the License, or (at your option) any later version. 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef ARDUINO_THREADS_SOURCE_HPP_ 20 | #define ARDUINO_THREADS_SOURCE_HPP_ 21 | 22 | /************************************************************************************** 23 | * INCLUDE 24 | **************************************************************************************/ 25 | 26 | #include 27 | #include 28 | 29 | /************************************************************************************** 30 | * FORWARD DECLARATION 31 | **************************************************************************************/ 32 | 33 | template 34 | class SinkBase; 35 | 36 | /************************************************************************************** 37 | * CLASS DECLARATION 38 | **************************************************************************************/ 39 | 40 | template 41 | class Source 42 | { 43 | public: 44 | 45 | void connectTo(SinkBase & sink); 46 | void push(T const & val); 47 | 48 | private: 49 | std::list *> _sink_list; 50 | }; 51 | 52 | /************************************************************************************** 53 | * PUBLIC MEMBER FUNCTIONS 54 | **************************************************************************************/ 55 | 56 | template 57 | void Source::connectTo(SinkBase & sink) 58 | { 59 | _sink_list.push_back(&sink); 60 | } 61 | 62 | template 63 | void Source::push(T const & val) 64 | { 65 | std::for_each(std::begin(_sink_list), 66 | std::end (_sink_list), 67 | [val](SinkBase * sink) 68 | { 69 | sink->inject(val); 70 | }); 71 | } 72 | 73 | #endif /* ARDUINO_THREADS_SOURCE_HPP_ */ 74 | --------------------------------------------------------------------------------