├── .circleci └── config.yml ├── .credo.exs ├── .formatter.exs ├── .github └── dependabot.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSES ├── Apache-2.0.txt ├── CC-BY-4.0.txt ├── CC0-1.0.txt ├── GPL-2.0-only.txt ├── Linux-syscall-note.txt └── SHL-0.51.txt ├── Makefile ├── NOTICE ├── PORTING.md ├── README.md ├── RELEASE.md ├── REUSE.toml ├── assets └── images │ ├── schematic-gpio-button.png │ └── schematic-gpio-led.png ├── c_src ├── gpio_nif.c ├── gpio_nif.h ├── hal_cdev_gpio.c ├── hal_cdev_gpio.h ├── hal_cdev_gpio_interrupts.c ├── hal_stub.c ├── linux │ └── gpio.h └── nif_utils.c ├── lib ├── gpio.ex └── gpio │ ├── backend.ex │ ├── cdev.ex │ ├── diagnostics.ex │ ├── gpio_nif.ex │ ├── handle.ex │ └── nil_backend.ex ├── mix.exs ├── mix.lock ├── notebooks └── basics.livemd └── test ├── circuits_gpio_test.exs ├── gpio └── diagnostics_test.exs └── test_helper.exs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | latest: &latest 4 | pattern: "^1.18.*-erlang-27.*$" 5 | 6 | tags: &tags 7 | [ 8 | 1.18.2-erlang-27.2.1-alpine-3.21.2, 9 | 1.17.3-erlang-27.2-alpine-3.20.3, 10 | 1.16.3-erlang-26.2.5-alpine-3.19.1, 11 | 1.15.7-erlang-26.2.4-alpine-3.18.6, 12 | 1.14.5-erlang-25.3.2.11-alpine-3.17.7, 13 | 1.13.4-erlang-24.3.4-alpine-3.15.3 14 | ] 15 | 16 | jobs: 17 | check-license: 18 | docker: 19 | - image: fsfe/reuse:latest 20 | steps: 21 | - checkout 22 | - run: reuse lint 23 | 24 | build-test: 25 | parameters: 26 | tag: 27 | type: string 28 | docker: 29 | - image: hexpm/elixir:<< parameters.tag >> 30 | working_directory: ~/repo 31 | environment: 32 | LC_ALL: C.UTF-8 33 | steps: 34 | - run: 35 | name: Install system dependencies 36 | command: apk add --no-cache build-base linux-headers 37 | - checkout 38 | - run: 39 | name: Install hex and rebar 40 | command: | 41 | mix local.hex --force 42 | mix local.rebar --force 43 | - restore_cache: 44 | keys: 45 | - v1-mix-cache-<< parameters.tag >>-{{ checksum "mix.lock" }} 46 | - run: mix deps.get 47 | - run: mix compile --warnings-as-errors 48 | - run: MIX_ENV=nil_test mix compile --warnings-as-errors 49 | - run: MIX_ENV=test mix compile --warnings-as-errors 50 | - run: mix test 51 | - when: 52 | condition: 53 | matches: { <<: *latest, value: << parameters.tag >> } 54 | steps: 55 | - run: mix format --check-formatted 56 | - run: mix deps.unlock --check-unused 57 | - run: mix docs 58 | - run: mix hex.build 59 | - run: mix credo -a --strict 60 | - run: mix dialyzer 61 | - save_cache: 62 | key: v1-mix-cache-<< parameters.tag >>-{{ checksum "mix.lock" }} 63 | paths: 64 | - _build 65 | - deps 66 | 67 | workflows: 68 | checks: 69 | jobs: 70 | - check-license 71 | - build-test: 72 | name: << matrix.tag >> 73 | matrix: 74 | parameters: 75 | tag: *tags 76 | -------------------------------------------------------------------------------- /.credo.exs: -------------------------------------------------------------------------------- 1 | # .credo.exs 2 | %{ 3 | configs: [ 4 | %{ 5 | name: "default", 6 | files: %{ 7 | included: ["lib/"], 8 | excluded: ["lib/gpio/gpio_nif.ex"] 9 | }, 10 | strict: true, 11 | checks: [ 12 | {Credo.Check.Refactor.MapInto, false}, 13 | {Credo.Check.Warning.LazyLogging, false}, 14 | {Credo.Check.Readability.LargeNumbers, only_greater_than: 86400}, 15 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs, parens: true}, 16 | {Credo.Check.Readability.Specs, tags: []}, 17 | {Credo.Check.Readability.StrictModuleLayout, tags: []} 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter,.credo}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: mix 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | circuits_gpio-*.tar 24 | 25 | # Ignore C object files and executables 26 | /priv 27 | *.o 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v2.1.2 - 2024-09-08 4 | 5 | * Bug fixes 6 | * Fix compilation when Erlang has been installed to a directory with spaces 7 | 8 | ## v2.1.1 - 2024-08-19 9 | 10 | * Changes 11 | * Show if GPIO number remapping is happening for AM335x platforms via 12 | `backend_info/0`. 13 | * Drop support for Elixir 1.11 and 1.12. 14 | 15 | ## v2.1.0 - 2024-04-08 16 | 17 | * Changes 18 | * Show backend options via `backend_info/0` so that it's possible to see 19 | whether you're using CDev test mode or not. 20 | 21 | ```elixir 22 | iex> Circuits.GPIO.backend_info() 23 | %{name: {Circuits.GPIO.CDev, [test: true]}, pins_open: 0} 24 | ``` 25 | 26 | ## v2.0.2 - 2024-01-17 27 | 28 | * Bug fixes 29 | * Remove lazy NIF loading. There's an unexplained segfault in a small example 30 | program that uses the same strategy. Even though it wasn't reproducible 31 | here, it's not worth the risk. Thanks to @pojiro for investigating. 32 | 33 | * Changes 34 | * Add example Livebook. Thanks to @mnishiguchi. 35 | 36 | ## v2.0.1 - 2024-01-13 37 | 38 | * Bug fixes 39 | * Fix race condition when loading NIF. If two processes caused the NIF to be 40 | loaded at the same time, then it was possible for one to return an error. 41 | * Remove tracking of the number of open pins from the cdev backend to not need 42 | to synchronize access to the count. This feature really was only used for 43 | the unit tests. 44 | 45 | ## v2.0.0 - 2024-01-11 46 | 47 | This is a major update to Circuits.GPIO that modernizes the API, restricts usage 48 | to Nerves and Linux, and updates the Linux/Nerves backend to the Linux GPIO cdev 49 | interface. 50 | 51 | It is mostly backwards compatible with Circuits.GPIO v1. Please see `PORTING.md` 52 | for upgrade instructions. 53 | 54 | * New features 55 | * Support alternative backends for different operating systems or for 56 | simulated hardware. The Linux cdev backend can be compiled out. 57 | 58 | * `Circuits.GPIO.open/3` is much more flexible in how GPIOs are identified. 59 | Specifying GPIOs by number still works, but it's now possible to specify 60 | GPIOs by string labels and by tuples that contain the GPIO controller name 61 | and index. See `t:gpio_spec/0` and the `README.md` for details. 62 | 63 | * List out available GPIOs with `Circuits.GPIO.enumerate/0`. Other helper 64 | functions are available for getting more information about each GPIO too. 65 | 66 | * Specify pull modes in general rather than only Raspberry Pis on Linux and 67 | Nerves 68 | 69 | * Easily do one-off reads and writes with `Circuits.GPIO.read_one/2` and 70 | `Circuits.GPIO.write_one/3` 71 | 72 | * Improved performance on Nerves and Linux; kernel-applied timestamping of 73 | GPIO input events 74 | 75 | * Add `Circuits.GPIO.Diagnostics` to automate runtime testing 76 | 77 | * Changes 78 | * More consistent error returns. Unexpected errors return `{:errno, value}` 79 | tuples to help correlate errors to low level docs 80 | * Deferred loading of the NIF to simplify debugging of GPIO backends. 81 | Segfaults crash on first use of `Circuits.GPIO` rather than on load. 82 | 83 | ## v1.1.0 - 2022-12-31 84 | 85 | * Changes 86 | * Remove Erlang convenience functions since no one used them 87 | * Require Elixir 1.10 or later. Previous versions probably work, but won't be 88 | supported. This opens up the possibility of using Elixir 1.10+ features in 89 | future releases. 90 | 91 | ## v1.0.1 - 2022-7-26 92 | 93 | * Bug fixes 94 | * On at least one device, the pin direction reported by Linux does not match 95 | the actual pin direction. This release forces the pin direction for inputs. 96 | It cannot do this for outputs, since setting a pin to output has a side 97 | effect on the pin state. This technically is a bug in a Linux driver port, 98 | but it was harmless to workaround for inputs so that's what's done. Thanks 99 | to @pojiro for investigating and fixing this issue. 100 | 101 | ## v1.0.0 - 2021-10-20 102 | 103 | This release only changes the version number. No code has changed. 104 | 105 | ## v0.4.8 106 | 107 | This release only has doc and build output cleanup. No code has changed. 108 | 109 | ## v0.4.7 110 | 111 | * Bug fixes 112 | * Fix hang when unloading the NIF. This bug caused `:init.stop` to never 113 | return and anything else that would try to unload this module. 114 | * Fix C compiler warnings with OTP 24 115 | 116 | The minimum Elixir version has been changed from 1.4 to 1.6. Elixir 1.4 might 117 | still work, but it's no longer being verified on CI. 118 | 119 | ## v0.4.6 120 | 121 | * Bug fixes 122 | * Fix quoting issue that was causing failures on Yocto. Thanks to Zander 123 | Erasmus for this. 124 | 125 | ## v0.4.5 126 | 127 | * Bug fixes 128 | * Opening a GPIO to read its state won't clear the interrupt status of another 129 | listener. Registering for interrupts a second time will still clear out the 130 | first registree, though. That's a limitation of the interface. However, for 131 | debugging, it can be nice to look at a GPIO without affecting the program 132 | and this change allows for that. 133 | 134 | ## v0.4.4 135 | 136 | * Bug fixes 137 | * Add -fPIC to compilation flags to fix build with `nerves_system_x86_64` and 138 | other environments using the Musl C toolchains 139 | 140 | ## v0.4.3 141 | 142 | * Bug fixes 143 | * Fix GPIO glitch suppression when interrupts were enabled. Glitch suppression 144 | filters out transitions on a GPIO line that are too fast for Linux and the 145 | NIF to see both the rising and falling edges. Turning it off synthesizes 146 | events. You can identify synthesized events since they have the same 147 | timestamp to the nanosecond. See `Circuits.GPIO.set_interrupts/3`. 148 | 149 | * Improvement 150 | * It's possible to enable the "stub" on Linux by setting 151 | `CIRCUITS_MIX_ENV=test`. This can be useful for unit testing code that uses 152 | Circuits.GPIO. Thanks to Enrico Rivarola for adding this! 153 | 154 | ## v0.4.2 155 | 156 | * Bug fixes 157 | * Fix pullup/pulldown support for the Raspberry Pi 4 158 | 159 | ## v0.4.1 160 | 161 | * Bug fixes 162 | * Fix a race condition on Raspbian where Circuits.GPIO would try to open the 163 | GPIO sysfs file before udev had a chance to fix its permissions. 164 | * Fix RPi platform detection on Raspbian so that pull-ups/pull-downs work 165 | without passing any flags. 166 | 167 | ## v0.4.0 168 | 169 | The GPIO interrupt notification messages have been changed for consistency with 170 | other circuits projects. The initial element of the tuple is now 171 | `:circuits_gpio`, so messages will look like: 172 | 173 | `{:circuits_gpio, 19, 83268239, 1}` 174 | 175 | Please update your project if you call `set_interrupts/2`. 176 | 177 | No more backwards incompatible changes are expected until after 1.0. 178 | 179 | ## v0.3.1 180 | 181 | * Bug fixes 182 | * Build C source under the `_build` directory so that changing targets 183 | properly rebuilds the C code as well as the Elixir. 184 | 185 | ## v0.3.0 186 | 187 | * New features 188 | * Support `pull_mode` initialization in `open/3`. 189 | 190 | ## v0.2.0 191 | 192 | * New features 193 | * Add support for opening GPIOs to an initial value or to not change the 194 | value. This removes a glitch if you want the GPIO to start out high (the 195 | default was low) or if you want the GPIO to keep its current value. 196 | 197 | ## v0.1.0 198 | 199 | Initial release to hex. 200 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 10 | 11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 | 13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 | 15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 | 17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 | 19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 20 | 21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 22 | 23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 24 | 25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 26 | 27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 28 | 29 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 | 31 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 32 | 33 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 34 | 35 | (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and 36 | 37 | (b) You must cause any modified files to carry prominent notices stating that You changed the files; and 38 | 39 | (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | 41 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 42 | 43 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS 56 | 57 | APPENDIX: How to apply the Apache License to your work. 58 | 59 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 60 | 61 | Copyright [yyyy] [name of copyright owner] 62 | 63 | Licensed under the Apache License, Version 2.0 (the "License"); 64 | you may not use this file except in compliance with the License. 65 | You may obtain a copy of the License at 66 | 67 | http://www.apache.org/licenses/LICENSE-2.0 68 | 69 | Unless required by applicable law or agreed to in writing, software 70 | distributed under the License is distributed on an "AS IS" BASIS, 71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 72 | See the License for the specific language governing permissions and 73 | limitations under the License. 74 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International 2 | 3 | Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. 4 | 5 | Using Creative Commons Public Licenses 6 | 7 | Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. 8 | 9 | Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. 10 | 11 | Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. 12 | 13 | Creative Commons Attribution 4.0 International Public License 14 | 15 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 16 | 17 | Section 1 – Definitions. 18 | 19 | a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 20 | 21 | b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 22 | 23 | c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 24 | 25 | d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 26 | 27 | e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 28 | 29 | f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 30 | 31 | g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 32 | 33 | h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. 34 | 35 | i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 36 | 37 | j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 38 | 39 | k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 40 | 41 | Section 2 – Scope. 42 | 43 | a. License grant. 44 | 45 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 46 | 47 | A. reproduce and Share the Licensed Material, in whole or in part; and 48 | 49 | B. produce, reproduce, and Share Adapted Material. 50 | 51 | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 52 | 53 | 3. Term. The term of this Public License is specified in Section 6(a). 54 | 55 | 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 56 | 57 | 5. Downstream recipients. 58 | 59 | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 60 | 61 | B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 62 | 63 | 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 64 | 65 | b. Other rights. 66 | 67 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 68 | 69 | 2. Patent and trademark rights are not licensed under this Public License. 70 | 71 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. 72 | 73 | Section 3 – License Conditions. 74 | 75 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 76 | 77 | a. Attribution. 78 | 79 | 1. If You Share the Licensed Material (including in modified form), You must: 80 | 81 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 82 | 83 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 84 | 85 | ii. a copyright notice; 86 | 87 | iii. a notice that refers to this Public License; 88 | 89 | iv. a notice that refers to the disclaimer of warranties; 90 | 91 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 92 | 93 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 94 | 95 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 96 | 97 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 98 | 99 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 100 | 101 | 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. 102 | 103 | Section 4 – Sui Generis Database Rights. 104 | 105 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 106 | 107 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; 108 | 109 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 110 | 111 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 112 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 113 | 114 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 115 | 116 | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 117 | 118 | b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 119 | 120 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 121 | 122 | Section 6 – Term and Termination. 123 | 124 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 125 | 126 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 127 | 128 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 129 | 130 | 2. upon express reinstatement by the Licensor. 131 | 132 | c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 133 | 134 | d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 135 | 136 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 137 | 138 | Section 7 – Other Terms and Conditions. 139 | 140 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 141 | 142 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 143 | 144 | Section 8 – Interpretation. 145 | 146 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 147 | 148 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 149 | 150 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 151 | 152 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 153 | 154 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 155 | 156 | Creative Commons may be contacted at creativecommons.org. 157 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/GPL-2.0-only.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. 12 | 13 | When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. 14 | 15 | To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. 16 | 17 | For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. 18 | 19 | We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. 20 | 21 | Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. 22 | 23 | Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. 24 | 25 | The precise terms and conditions for copying, distribution and modification follow. 26 | 27 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 28 | 29 | 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". 30 | 31 | Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 32 | 33 | 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. 34 | 35 | You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 36 | 37 | 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: 38 | 39 | a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. 40 | 41 | b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. 42 | 43 | c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) 44 | 45 | These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. 46 | 47 | Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. 48 | 49 | In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 50 | 51 | 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: 52 | 53 | a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 54 | 55 | b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, 56 | 57 | c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) 58 | 59 | The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. 60 | 61 | If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 62 | 63 | 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 64 | 65 | 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 66 | 67 | 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 68 | 69 | 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. 70 | 71 | If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. 72 | 73 | It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. 74 | 75 | This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 76 | 77 | 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 78 | 79 | 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. 80 | 81 | Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 82 | 83 | 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. 84 | 85 | NO WARRANTY 86 | 87 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 88 | 89 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 90 | 91 | END OF TERMS AND CONDITIONS 92 | 93 | How to Apply These Terms to Your New Programs 94 | 95 | If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. 96 | 97 | To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 98 | 99 | one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author 100 | 101 | This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 102 | 103 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 104 | 105 | You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail. 106 | 107 | If the program is interactive, make it output a short notice like this when it starts in an interactive mode: 108 | 109 | Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. 110 | 111 | The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. 112 | 113 | You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: 114 | 115 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. 116 | 117 | signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice 118 | -------------------------------------------------------------------------------- /LICENSES/Linux-syscall-note.txt: -------------------------------------------------------------------------------- 1 | NOTE! This copyright does *not* cover user programs that use kernel 2 | services by normal system calls - this is merely considered normal use 3 | of the kernel, and does *not* fall under the heading of "derived work". 4 | Also note that the GPL below is copyrighted by the Free Software 5 | Foundation, but the instance of code that it refers to (the Linux 6 | kernel) is copyrighted by me and others who actually wrote it. 7 | 8 | Also note that the only valid version of the GPL as far as the kernel 9 | is concerned is _this_ particular version of the license (ie v2, not 10 | v2.2 or v3.x or whatever), unless explicitly otherwise stated. 11 | 12 | Linus Torvalds 13 | -------------------------------------------------------------------------------- /LICENSES/SHL-0.51.txt: -------------------------------------------------------------------------------- 1 | SOLDERPAD HARDWARE LICENSE version 0.51 2 | 3 | This license is based closely on the Apache License Version 2.0, but is not approved or endorsed by the Apache Foundation. A copy of the non-modified Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0. 4 | 5 | As this license is not currently OSI or FSF approved, the Licensor permits any Work licensed under this License, at the option of the Licensee, to be treated as licensed under the Apache License Version 2.0 (which is so approved). 6 | 7 | This License is licensed under the terms of this License and in particular clause 7 below (Disclaimer of Warranties) applies in relation to its use. 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 14 | 15 | "Licensor" shall mean the Rights owner or entity authorized by the Rights owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 18 | 19 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 20 | 21 | "Rights" means copyright and any similar right including design right (whether registered or unregistered), semiconductor topography (mask) rights and database rights (but excluding Patents and Trademarks). 22 | 23 | "Source" form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files. 24 | 25 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, artwork and semiconductor topographies (mask works). 26 | 27 | "Work" shall mean the work of authorship, whether in Source form or other Object form, made available under the License, as indicated by a Rights notice that is included in or attached to the work (an example is provided in the Appendix below). 28 | 29 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) or physically connect to or interoperate with the interfaces of, the Work and Derivative Works thereof. 30 | 31 | "Contribution" shall mean any design or work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the Rights owner or by an individual or Legal Entity authorized to submit on behalf of the Rights owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the Rights owner as "Not a Contribution." 32 | 33 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 34 | 35 | 2. Grant of License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist. 36 | 37 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 38 | 39 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 40 | 41 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 42 | 43 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 44 | 45 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 46 | 47 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 48 | 49 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 50 | 51 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 52 | 53 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 54 | 55 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 56 | 57 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 58 | 59 | END OF TERMS AND CONDITIONS 60 | 61 | APPENDIX: How to apply this license to your work 62 | 63 | To apply this license to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 64 | 65 | Copyright [yyyy] [name of copyright owner] Copyright and related rights are licensed under the Solderpad Hardware License, Version 0.51 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law or agreed to in writing, software, hardware and materials distributed under this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 66 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2014 Frank Hunleth 2 | # SPDX-FileCopyrightText: 2016 Connor Rigby 3 | # SPDX-FileCopyrightText: 2018 Mark Sebald 4 | # SPDX-FileCopyrightText: 2018 Matt Ludwigs 5 | # SPDX-FileCopyrightText: 2019 Enrico Rivarola 6 | # 7 | # SPDX-License-Identifier: Apache-2.0 8 | 9 | # Makefile for building the NIF 10 | # 11 | # Makefile targets: 12 | # 13 | # all/install build and install the NIF 14 | # clean clean build products and intermediates 15 | # 16 | # Variables to override: 17 | # 18 | # MIX_APP_PATH path to the build directory 19 | # CIRCUITS_GPIO_BACKEND Backend to build - `"cdev"`, `"test"`, or `"disabled"` will build a NIF 20 | # 21 | # CC C compiler 22 | # CROSSCOMPILE crosscompiler prefix, if any 23 | # CFLAGS compiler flags for compiling all C files 24 | # ERL_CFLAGS additional compiler flags for files using Erlang header files 25 | # ERL_EI_INCLUDE_DIR include path to ei.h (Required for crosscompile) 26 | # ERL_EI_LIBDIR path to libei.a (Required for crosscompile) 27 | # LDFLAGS linker flags for linking all binaries 28 | # ERL_LDFLAGS additional linker flags for projects referencing Erlang libraries 29 | 30 | PREFIX = $(MIX_APP_PATH)/priv 31 | BUILD = $(MIX_APP_PATH)/obj 32 | 33 | NIF = $(PREFIX)/gpio_nif.so 34 | 35 | CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -pedantic 36 | 37 | $(info "**** CIRCUITS_GPIO_BACKEND set to [$(CIRCUITS_GPIO_BACKEND)] ****") 38 | 39 | # Check that we're on a supported build platform 40 | ifeq ($(CROSSCOMPILE),) 41 | # Not crosscompiling, so check that we're on Linux for whether to compile the NIF. 42 | ifeq ($(shell uname -s),Linux) 43 | CFLAGS += -fPIC 44 | LDFLAGS += -fPIC -shared 45 | else 46 | LDFLAGS += -undefined dynamic_lookup -dynamiclib 47 | ifeq ($(CIRCUITS_GPIO_BACKEND),cdev) 48 | $(error Circuits.GPIO Linux cdev backend is not supported on non-Linux platforms. Review circuits_gpio backend configuration or report an issue if improperly detected.) 49 | endif 50 | endif 51 | else 52 | # Crosscompiled build 53 | LDFLAGS += -fPIC -shared 54 | CFLAGS += -fPIC 55 | endif 56 | 57 | ifeq ($(CIRCUITS_GPIO_BACKEND),cdev) 58 | # Enable real GPIO calls. This is the default and works with Nerves 59 | HAL_SRC = c_src/hal_cdev_gpio.c c_src/hal_cdev_gpio_interrupts.c 60 | # Hide cdev ioctl warnings 61 | CFLAGS += -Wno-overflow 62 | else 63 | ifeq ($(CIRCUITS_GPIO_BACKEND),test) 64 | # Stub out ioctls and send back test data 65 | HAL_SRC = c_src/hal_stub.c 66 | else 67 | # Don't build NIF 68 | NIF = 69 | endif 70 | endif 71 | 72 | # Set Erlang-specific compile and linker flags 73 | ERL_CFLAGS ?= -I"$(ERL_EI_INCLUDE_DIR)" 74 | ERL_LDFLAGS ?= -L"$(ERL_EI_LIBDIR)" -lei 75 | 76 | HAL_SRC += c_src/nif_utils.c 77 | SRC = $(HAL_SRC) c_src/gpio_nif.c 78 | HEADERS =$(wildcard c_src/*.h) 79 | OBJ = $(SRC:c_src/%.c=$(BUILD)/%.o) 80 | 81 | calling_from_make: 82 | mix compile 83 | 84 | all: install 85 | 86 | install: $(PREFIX) $(BUILD) $(NIF) 87 | 88 | $(OBJ): $(HEADERS) Makefile 89 | 90 | $(BUILD)/%.o: c_src/%.c 91 | @echo " CC $(notdir $@)" 92 | $(CC) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $< 93 | 94 | $(NIF): $(OBJ) 95 | @echo " LD $(notdir $@)" 96 | $(CC) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^ 97 | 98 | $(PREFIX) $(BUILD): 99 | mkdir -p $@ 100 | 101 | clean: 102 | $(RM) $(NIF) $(OBJ) 103 | 104 | .PHONY: all clean calling_from_make install 105 | 106 | # Don't echo commands unless the caller exports "V=1" 107 | ${V}.SILENT: 108 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | CircuitsGPIO is open-source software licensed under the Apache License, 2 | Version 2.0. 3 | 4 | Copyright holders include Frank Hunleth, Connor Rigby, Justin Schneck, 5 | Giovanni Visciano, Tim Mecklem, Jon Carstens, Mark Sebald, Matt Ludwigs, Enrico 6 | Rivarola and Michael Roach. 7 | 8 | Authoritative REUSE-compliant copyright and license metadata available at 9 | https://hex.pm/packages/circuits_gpio. 10 | -------------------------------------------------------------------------------- /PORTING.md: -------------------------------------------------------------------------------- 1 | # Porting 2 | 3 | ## Upgrading Circuits.GPIO 1.0 projects to 2.0 4 | 5 | Circuits.GPIO 2.0 supports alternative GPIO hardware and the ability to mock or 6 | emulate devices via backends. The Linux cdev backend is the default and usage is 7 | similar to Circuits.GPIO 1.0. Most projects won't need any changes other than to 8 | update the dependency in `mix.exs`. If upgrading a library, The following 9 | dependency specification is recommended to allow both `circuits_gpio` versions: 10 | 11 | ```elixir 12 | {:circuits_gpio, "~> 2.0 or ~> 1.0"} 13 | ``` 14 | 15 | The following breaking changes were made: 16 | 17 | 1. `Circuits.GPIO.open/3` opens an exclusive reference to the GPIO. It's no 18 | longer possible to have multiple processes open the same GPIO. This sometimes 19 | comes up when GPIOs are not closed when they're done being used. The garbage 20 | collector will eventually close the GPIO, so you may see intermittent failed 21 | opens. The solution is either to open once and pass the handle around or to 22 | explicitly close handles after usage. 23 | 2. `Circuits.GPIO.open/3` accepts more general pin specifications called 24 | `gpio_spec`s. This allows you to specify GPIO controllers and refer to pins 25 | by labels. Please see `t:Circuits.GPIO.gpio_spec/0` since referring to pins 26 | by number is brittle and has broken in the past. 27 | 3. `Circuits.GPIO.open/3` no longer can preserve the previous output value on 28 | a pin automatically. I.e., `initial_value: :not_set` is no longer supported. 29 | 4. Reading the values of output GPIOs is not supported. This does have a chance 30 | of working depending on the backend, but it's no longer a requirement of the 31 | API since some backends may not be readable. The workaround is to cache what 32 | you wrote. 33 | 5. `Circuits.GPIO.set_interrupts/3` does not send an initial notification. 34 | Notifications are ONLY sent on GPIO transitions now. 35 | 6. The `stub` implementation still exists and is useful for testing the cdev NIF 36 | interface. It's possible to have alternative GPIO backends now so more 37 | complicated testing backends can be created. If you have simple needs, it's 38 | still available when compiled with `MIX_ENV=test` and when the backend is 39 | specified to be `{Circuits.GPIO.CDev, test: true}`. See the `README.md` for 40 | more information. 41 | 7. `Circuits.GPIO.pin/1` is no longer available. 42 | 43 | You should hopefully find that the semantics of API are more explicit now or at 44 | least the function documentation is more clear. This was necessary to support 45 | more backends without requiring backend authors to implement features that are 46 | trickier than you'd expect. 47 | 48 | If you find that you have to make any other changes, please let us know via an 49 | issue or PR so that other users can benefit. 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Circuits - GPIO 2 | 3 | [![Hex version](https://img.shields.io/hexpm/v/circuits_gpio.svg "Hex version")](https://hex.pm/packages/circuits_gpio) 4 | [![API docs](https://img.shields.io/hexpm/v/circuits_gpio.svg?label=hexdocs "API docs")](https://hexdocs.pm/circuits_gpio/Circuits.GPIO.html) 5 | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/elixir-circuits/circuits_gpio/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/elixir-circuits/circuits_gpio/tree/main) 6 | [![REUSE status](https://api.reuse.software/badge/github.com/elixir-circuits/circuits_gpio)](https://api.reuse.software/info/github.com/elixir-circuits/circuits_gpio) 7 | 8 | `Circuits.GPIO` lets you use GPIOs in Elixir. 9 | 10 | ## Getting started on Nerves and Linux 11 | 12 | If you're natively compiling `circuits_gpio` using Nerves or using a Linux-based 13 | SBC like a Raspberry Pi, everything should work like any other Elixir library. 14 | Normally, you would include `circuits_gpio` as a dependency in your `mix.exs` 15 | like this: 16 | 17 | ```elixir 18 | def deps do 19 | [{:circuits_gpio, "~> 2.1"}] 20 | end 21 | ``` 22 | 23 | One common error on RaspberryPi OS is that the Erlang headers are missing 24 | (`erl_nif.h`), you may need to install erlang with `apt-get install erlang-dev` 25 | or build Erlang from source per instructions [here](http://elinux.org/Erlang). 26 | 27 | ## Examples 28 | 29 | While `Circuits.GPIO` can support non-Nerves and non-Linux systems, the examples 30 | below were made using Nerves. Operation on other devices mostly differs on how 31 | to refer to GPIOs. 32 | 33 | The following examples were tested on a Raspberry Pi that was connected to an 34 | [Erlang Embedded Demo Board](http://solderpad.com/omerk/erlhwdemo/). There's 35 | nothing special about either the demo board or the Raspberry Pi. 36 | 37 | ## Nerves + Livebook 38 | 39 | [Nerves Livebook project](https://github.com/nerves-livebook/nerves_livebook) 40 | allows you to try out the Nerves project on real hardware without needing 41 | to build a project from scratch. 42 | 43 | Within minutes, you'll have a Raspberry Pi or Beaglebone running Nerves. You'll 44 | be able to run code in [Livebook](https://livebook.dev/) and work through 45 | Nerves tutorials from the comfort of your browser. 46 | 47 | [![Run in Nerves Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Felixir-circuits%2Fcircuits_gpio%2Fblob%2Fmain%2Fnotebooks%2Fbasics.livemd) 48 | 49 | ## GPIO 50 | 51 | A [General Purpose 52 | Input/Output](https://en.wikipedia.org/wiki/General-purpose_input/output) (GPIO) 53 | is just a wire that you can use as an input or an output. It can only be one of 54 | two values, 0 or 1. A 1 corresponds to a logic high voltage like 3.3 V and a 0 55 | corresponds to 0 V. The actual voltage depends on the hardware. 56 | 57 | Here's an example of turning an LED on or off: 58 | 59 | ![GPIO LED schematic](assets/images/schematic-gpio-led.png) 60 | 61 | To turn on the LED that's connected to the net (or wire) labeled `GPIO18`, you 62 | need to open it first. The first parameter to `Circuits.GPIO.open/2` is called a 63 | GPIO spec and identifies the GPIO. The Raspberry Pis are nice and provide string 64 | names for GPIOs. Other boards are not as nice so you always have to check. The 65 | string name for this GPIO is `"GPIO18"` (use `"PIN12"` on a Raspberry Pi 5). 66 | 67 | ```elixir 68 | iex> {:ok, gpio} = Circuits.GPIO.open("GPIO12", :output) 69 | {:ok, %Circuits.GPIO.CDev{...}} 70 | 71 | iex> Circuits.GPIO.write(gpio, 1) 72 | :ok 73 | 74 | iex> Circuits.GPIO.close(gpio) 75 | :ok 76 | ``` 77 | 78 | The call to `Circuits.GPIO.close/1` is not necessary, since the garbage 79 | collector will free up unreferenced GPIOs. It's a good practice, though, 80 | since backends can enforce exclusivity and prevent future opens from 81 | working until the GC occurs. 82 | 83 | Input works similarly. Here's an example of a button with a pull down resistor 84 | connected. 85 | 86 | ![GPIO Button schematic](assets/images/schematic-gpio-button.png) 87 | 88 | If you're not familiar with pull up or pull down resistors, they're resistors 89 | whose purpose is to drive a wire high or low when the button isn't pressed. In 90 | this case, it drives the wire low. Many processors have ways of configuring 91 | internal resistors to accomplish the same effect without needing to add an 92 | external resistor. If you're using a Raspberry Pi, you can use [the built-in 93 | pull-up/pull-down resistors](#internal-pull-uppull-down). 94 | 95 | The code looks like this in `Circuits.GPIO`: 96 | 97 | ```elixir 98 | iex> {:ok, gpio} = Circuits.GPIO.open("GPIO17", :input) 99 | {:ok, %Circuits.GPIO.CDev{...}} 100 | 101 | iex> Circuits.GPIO.read(gpio) 102 | 0 103 | 104 | # Push the button down 105 | 106 | iex> Circuits.GPIO.read(gpio) 107 | 1 108 | ``` 109 | 110 | If you'd like to get a message when the button is pressed or released, call the 111 | `set_interrupts` function. You can trigger on the `:rising` edge, `:falling` 112 | edge or `:both`. 113 | 114 | ```elixir 115 | iex> Circuits.GPIO.set_interrupts(gpio, :both) 116 | :ok 117 | 118 | iex> flush 119 | {:circuits_gpio, "GPIO17", 1233456, 1} 120 | {:circuits_gpio, "GPIO17", 1234567, 0} 121 | :ok 122 | ``` 123 | 124 | Note that after calling `set_interrupts`, the calling process will receive an 125 | initial message with the state of the pin. This prevents the race condition 126 | between getting the initial state of the pin and turning on interrupts. Without 127 | it, you could get the state of the pin, it could change states, and then you 128 | could start waiting on it for interrupts. If that happened, you would be out of 129 | sync. 130 | 131 | ### Internal pull-up/pull-down 132 | 133 | To connect or disconnect an internal [pull-up or pull-down resistor](https://github.com/raspberrypilearning/physical-computing-guide/blob/master/pull_up_down.md) to a GPIO 134 | pin, call the `set_pull_mode` function. 135 | 136 | ```elixir 137 | iex> Circuits.GPIO.set_pull_mode(gpio, pull_mode) 138 | :ok 139 | ``` 140 | 141 | Valid `pull_mode` values are `:none` `:pullup`, or `:pulldown` 142 | 143 | Note that `set_pull_mode` is platform dependent, and currently only works for 144 | Raspberry Pi hardware. Calls to `set_pull_mode` on other platforms will have no 145 | effect. The internal pull-up resistor value is between 50K and 65K, and the 146 | pull-down is between 50K and 60K. It is not possible to read back the current 147 | Pull-up/down settings, and GPIO pull-up pull-down resistor connections are 148 | maintained, even when the CPU is powered down. 149 | 150 | ## GPIO Specs 151 | 152 | `Circuits.GPIO` v2.0 supports a new form of specifying how to open a GPIO called 153 | a `t:gpio_spec/0`. These specs are very flexible and allow for GPIOs to be 154 | opened by number, a string label, or a tuple that includes both the GPIO 155 | controller hardware name and a line offset. 156 | 157 | The contents of a `gpio_spec` depend on the backend. When running on Nerves or 158 | a Linux machine, `Circuits.GPIO` uses the Linux gpio-cdev backend. This backend 159 | prefers the use of GPIO controller/line offset tuples and labels. For backwards 160 | compatibility, it somewhat supports use of the *older* pin numbering scheme. 161 | 162 | The GPIO controller part of the tuple is usually some variation on `"gpiochip0"` 163 | that depends on what controllers are available under `/dev`. The line offset is 164 | a the line offset of the GPIO on that controller. 165 | 166 | ```elixir 167 | iex> {:ok, gpio} = Circuits.GPIO.open({"gpiochip0", 1}, :input) 168 | {:ok, %Circuits.GPIO.CDev{...}} 169 | ``` 170 | 171 | When the Linux device tree is configured with GPIO labels, you can use those instead: 172 | 173 | ```elixir 174 | iex> {:ok, gpio} = Circuits.GPIO.open("special-name-for-pin-1") 175 | {:ok, %Circuits.GPIO.CDev{...}} 176 | ``` 177 | 178 | If you're deploying to multiple types of devices and you can set labels in the 179 | device tree, labels make it really easy for code using `Circuits.GPIO` to just 180 | use the right GPIO. 181 | 182 | Labels are not guaranteed to be unique, so if your device defines one twice, 183 | `Circuits.GPIO` will use the first GPIO it finds that has the specified label. 184 | 185 | See [Enumeration](#enumeration) for listing out all available `gpio_specs` for 186 | your device. 187 | 188 | ## Enumeration 189 | 190 | `Circuits.GPIO` v2.0 supports a new function, `enumerate/0`, which lists every 191 | known GPIO pin. 192 | 193 | For Nerves and Linux users, the `gpio-cdev` subsystem maintains the official 194 | list. See the [Official DeviceTree documentation for 195 | GPIOs](https://elixir.bootlin.com/linux/v6.6.6/source/Documentation/devicetree/bindings/gpio/gpio.txt) 196 | for more information on how to configure the fields of this struct for your own 197 | system. 198 | 199 | Here's an example: 200 | 201 | ```elixir 202 | iex> Circuits.GPIO.enumerate() 203 | [ 204 | %{ 205 | location: {"gpiochip0", 0}, 206 | label: "ID_SDA", 207 | controller: "pinctrl-bcm2835" 208 | }, 209 | %{ 210 | location: {"gpiochip0", 1}, 211 | label: "ID_SCL", 212 | controller: "pinctrl-bcm2835" 213 | }, 214 | %{ 215 | location: {"gpiochip0", 2}, 216 | label: "SDA1", 217 | controller: "pinctrl-bcm2835" 218 | }, 219 | ... 220 | ] 221 | ``` 222 | 223 | The `:location` can always be passed as the first parameter to 224 | `Circuits.GPIO.open/3`. You may find the `:label` field more descriptive to use, 225 | though. 226 | 227 | ## Convenience functions 228 | 229 | Having to `open` *then* `read` and `write` can be a cumbersome for one-off GPIO 230 | access in code and when working at the IEx prompt. Circuits v2.0 has a pair of 231 | new functions to help: 232 | 233 | ```elixir 234 | iex> alias Circuits.GPIO 235 | iex> GPIO.write_one("special-name-for-pin-1", 1) 236 | :ok 237 | iex> GPIO.read_one("special-name-for-pin-2") 238 | 1 239 | ``` 240 | 241 | These functions get passed a `t:gpio_spec/0` just like `open/3` and internally 242 | open the GPIO and read or write it. Importantly, they `close` the GPIO when done 243 | to avoid reserving the GPIO any longer than necessary. 244 | 245 | Please note that this is not a performant way of reading or writing the same 246 | GPIO more than once. Opening a GPIO takes much longer than reading or writing an 247 | already opened one, so if these are used in tight loops, the open overhead will 248 | dominate (>99% of the time taken in a trivial benchmark.) 249 | 250 | ## Testing 251 | 252 | The `Circuits.GPIO.CDev` backend supports a test mode on platforms without CDev 253 | GPIO support and when `MIX_ENV=test`. In previous versions of `Circuits.GPIO`, 254 | this was called the "stub". 255 | 256 | There are a couple ways of using it, but you might have it by default especially 257 | if you're on MacOS. To see, run the following and look for `test: true`: 258 | 259 | ```elixir 260 | iex> Circuits.GPIO.backend_info() 261 | %{name: {Circuits.GPIO.CDev, test: true}, pins_open: 0} 262 | ``` 263 | 264 | If you're not running with `test: true`, you can force it by setting the default 265 | backend: 266 | 267 | ```elixir 268 | config :circuits_gpio, default_backend: {Circuits.GPIO.CDev, test: true} 269 | ``` 270 | 271 | Test mode has 64 GPIOs. Each pair of GPIOs is connected. For example, GPIO 0 is 272 | connected to GPIO 1. If you open GPIO 0 as an output and GPIO 1 as an input, you 273 | can write to GPIO 0 and see the result on GPIO 1. Here's an example: 274 | 275 | ```elixir 276 | iex> {:ok, gpio0} = Circuits.GPIO.open({"gpiochip0", 0}, :output) 277 | {:ok, %Circuits.GPIO.CDev{...}} 278 | iex> {:ok, gpio1} = Circuits.GPIO.open({"gpiochip0", 1}, :input) 279 | {:ok, %Circuits.GPIO.CDev{...}} 280 | iex> Circuits.GPIO.read(gpio1) 281 | 0 282 | iex> Circuits.GPIO.write(gpio0, 1) 283 | :ok 284 | iex> Circuits.GPIO.read(gpio1) 285 | 1 286 | ``` 287 | 288 | Test mode is fairly limited, but it does support interrupts. 289 | 290 | ## Migration from v1.x 291 | 292 | v2.x is the current version of `Circuits.GPIO`. 293 | 294 | `Circuits.GPIO` v2.0 is an almost backwards compatible update to `Circuits.GPIO` 295 | v1.x. Here's what's new: 296 | 297 | * Linux or Nerves are no longer required. In fact, the NIF supporting them won't 298 | be compiled if you don't want it. 299 | * GPIOs can be enumerated to see what's available (See `Circuits.GPIO.enumerate/0`) 300 | * Linux and Nerves now use the Linux GPIO cdev subsystem rather than sysfs 301 | * GPIO pull mode setting for all platforms that support it rather than only Raspberry Pi 302 | * Develop using simulated GPIOs to work with LEDs and buttons with 303 | [CircuitsSim](https://github.com/elixir-circuits/circuits_sim) 304 | 305 | If you've used `Circuits.GPIO` v1.x, nearly all of your code will be the 306 | same.`Circuits.GPIO` offers a substantial improvement by more descriptive GPIO 307 | specs for identifying GPIOs. You can still refer to GPIOs by number. However, 308 | you can also refer to GPIOs by labels and by which GPIO controller handles them. 309 | The new `enumerate/0` can help with this. 310 | 311 | If you are using a previous version and wish to update, review the [porting 312 | guide](PORTING.md). Also see [circuits_gpio v1.x maintenance 313 | branch](https://github.com/elixir-circuits/circuits_gpio/tree/maint-v1.x). 314 | 315 | ## FAQ 316 | 317 | ### Where can I get help? 318 | 319 | Most issues people have are on how to communicate with hardware for the first 320 | time. Since `Circuits.GPIO` is a thin wrapper on the Linux CDev GPIO interface, 321 | you may find help by searching for similar issues when using Python or C. 322 | 323 | For help specifically with `Circuits.GPIO`, you may also find help on the nerves 324 | channel on the [elixir-lang Slack](https://elixir-lang.slack.com/). Many 325 | [Nerves](http://nerves-project.org) users also use `Circuits.GPIO`. 326 | 327 | ### I tried turning on and off a GPIO as fast as I could. Why was it slow? 328 | 329 | Please don't do that - there are so many better ways of accomplishing whatever 330 | you're trying to do: 331 | 332 | 1. If you're trying to drive a servo or dim an LED, look into PWM. Many 333 | platforms have PWM hardware and you won't tax your CPU at all. If your 334 | platform is missing a PWM, several chips are available that take I2C commands 335 | to drive a PWM output. 336 | 2. If you need to implement a wire level protocol to talk to a device, look for 337 | a Linux kernel driver. It may just be a matter of loading the right kernel 338 | module. 339 | 3. If you want a blinking LED to indicate status, `gpio` really should 340 | be fast enough to do that, but check out Linux's LED class interface. Linux 341 | can flash LEDs, trigger off events and more. See [nerves_leds](https://github.com/nerves-project/nerves_leds). 342 | 343 | If you're still intent on optimizing GPIO access, you may be interested in 344 | [gpio_twiddler](https://github.com/fhunleth/gpio_twiddler). Note that the 345 | twiddler doc is dated. Circuits.GPIO v2 should be faster than v1. 346 | 347 | ### Can I develop code that uses GPIO on my laptop? 348 | 349 | The intended way to support this is to have a custom `Circuits.GPIO.Backend` 350 | that runs on your laptop. The 351 | [CircuitsSim](https://github.com/elixir-circuits/circuits_sim) is an example of 352 | a project that provides simulated LEDs and buttons. 353 | 354 | ## License 355 | 356 | All original source code in this project is licensed under Apache-2.0. 357 | 358 | Additionally, this project follows the [REUSE recommendations](https://reuse.software) 359 | and labels so that licensing and copyright are clear at the file level. 360 | 361 | Exceptions to Apache-2.0 licensing are: 362 | 363 | * Configuration and data files are licensed under CC0-1.0 364 | * Documentation files are CC-BY-4.0 365 | * Erlang Embedded board images are Solderpad Hardware License v0.51. 366 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release checklist 2 | 3 | 1. Update `CHANGELOG.md` with a bulletpoint list of new features and bug fixes 4 | 2. Update version numbers in `mix.exs` and `README.md` 5 | 3. Tag 6 | 4. Push last commit(s) *and* tags to GitHub 7 | 5. Wait for the Travis builds to complete successfully 8 | 6. Copy the latest CHANGELOG.md entry to the GitHub releases description 9 | 7. Run `mix hex.publish` 10 | 8. Update version numbers in `CHANGELOG.md` and `mix.exs` for `-dev` work 11 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[annotations]] 4 | path = [ 5 | ".circleci/config.yml", 6 | ".credo.exs", 7 | ".formatter.exs", 8 | ".github/dependabot.yml", 9 | ".gitignore", 10 | "CHANGELOG.md", 11 | "NOTICE", 12 | "REUSE.toml", 13 | "mix.exs", 14 | "mix.lock" 15 | ] 16 | precedence = "aggregate" 17 | SPDX-FileCopyrightText = "None" 18 | SPDX-License-Identifier = "CC0-1.0" 19 | 20 | [[annotations]] 21 | path = [ 22 | "PORTING.md", 23 | "README.md", 24 | "RELEASE.md" 25 | ] 26 | precedence = "aggregate" 27 | SPDX-FileCopyrightText = "2014 Frank Hunleth" 28 | SPDX-License-Identifier = "CC-BY-4.0" 29 | 30 | [[annotations]] 31 | path = ["assets/images/schematic-gpio-led.png", "assets/images/schematic-gpio-button.png"] 32 | precedence = "aggregate" 33 | SPDX-FileCopyrightText = "2013 Erlang Solutions" 34 | SPDX-License-Identifier = "SHL-0.51" 35 | 36 | [[annotations]] 37 | path = ["notebooks/basics.livemd"] 38 | precedence = "aggregate" 39 | SPDX-FileCopyrightText = "2024 Masatoshi Nishiguchi" 40 | SPDX-License-Identifier = "Apache-2.0" 41 | -------------------------------------------------------------------------------- /assets/images/schematic-gpio-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elixir-circuits/circuits_gpio/f29a1dce83bee44372bfd4fda23f48b8294cb5de/assets/images/schematic-gpio-button.png -------------------------------------------------------------------------------- /assets/images/schematic-gpio-led.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elixir-circuits/circuits_gpio/f29a1dce83bee44372bfd4fda23f48b8294cb5de/assets/images/schematic-gpio-led.png -------------------------------------------------------------------------------- /c_src/gpio_nif.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Frank Hunleth 2 | // SPDX-FileCopyrightText: 2018 Mark Sebald 3 | // SPDX-FileCopyrightText: 2018 Matt Ludwigs 4 | // SPDX-FileCopyrightText: 2023 Connor Rigby 5 | // 6 | // SPDX-License-Identifier: Apache-2.0 7 | 8 | #include "gpio_nif.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | ERL_NIF_TERM atom_ok; 20 | ERL_NIF_TERM atom_error; 21 | ERL_NIF_TERM atom_name; 22 | ERL_NIF_TERM atom_label; 23 | ERL_NIF_TERM atom_location; 24 | ERL_NIF_TERM atom_controller; 25 | ERL_NIF_TERM atom_circuits_gpio; 26 | ERL_NIF_TERM atom_consumer; 27 | 28 | #ifdef DEBUG 29 | FILE *log_location = NULL; 30 | #endif 31 | 32 | static void release_gpio_pin(struct gpio_priv *priv, struct gpio_pin *pin) 33 | { 34 | if (pin->env) { 35 | enif_free_env(pin->env); 36 | pin->env = 0; 37 | } 38 | if (pin->fd >= 0) { 39 | hal_close_gpio(pin); 40 | pin->fd = -1; 41 | } 42 | } 43 | 44 | static void gpio_pin_dtor(ErlNifEnv *env, void *obj) 45 | { 46 | struct gpio_priv *priv = enif_priv_data(env); 47 | struct gpio_pin *pin = (struct gpio_pin*) obj; 48 | 49 | debug("gpio_pin_dtor called on pin={%s,%d}", pin->gpiochip, pin->offset); 50 | 51 | release_gpio_pin(priv, pin); 52 | } 53 | 54 | static void gpio_pin_stop(ErlNifEnv *env, void *obj, int fd, int is_direct_call) 55 | { 56 | (void) env; 57 | (void) obj; 58 | (void) fd; 59 | (void) is_direct_call; 60 | //struct gpio_priv *priv = enif_priv_data(env); 61 | #ifdef DEBUG 62 | struct gpio_pin *pin = (struct gpio_pin*) obj; 63 | debug("gpio_pin_stop called %s, pin={%s,%d}", (is_direct_call ? "DIRECT" : "LATER"), pin->gpiochip, pin->offset); 64 | #endif 65 | } 66 | 67 | static void gpio_pin_down(ErlNifEnv *env, void *obj, ErlNifPid *pid, ErlNifMonitor *monitor) 68 | { 69 | (void) env; 70 | (void) obj; 71 | (void) pid; 72 | (void) monitor; 73 | #ifdef DEBUG 74 | struct gpio_pin *pin = (struct gpio_pin*) obj; 75 | debug("gpio_pin_down called on pin={%s,%d}", pin->gpiochip, pin->offset); 76 | #endif 77 | } 78 | 79 | #if (ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 16) 80 | // OTP-24 and later 81 | static ErlNifResourceTypeInit gpio_pin_init = {gpio_pin_dtor, gpio_pin_stop, gpio_pin_down, 3, NULL}; 82 | #else 83 | // Old way 84 | static ErlNifResourceTypeInit gpio_pin_init = {gpio_pin_dtor, gpio_pin_stop, gpio_pin_down}; 85 | #endif 86 | 87 | int send_gpio_message(ErlNifEnv *env, 88 | ERL_NIF_TERM gpio_spec, 89 | ErlNifPid *pid, 90 | int64_t timestamp, 91 | int value) 92 | { 93 | ERL_NIF_TERM msg = enif_make_tuple4(env, 94 | atom_circuits_gpio, 95 | gpio_spec, 96 | enif_make_int64(env, timestamp), 97 | enif_make_int(env, value)); 98 | 99 | return enif_send(env, pid, NULL, msg); 100 | } 101 | 102 | static int load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM info) 103 | { 104 | (void) info; 105 | #ifdef DEBUG 106 | #ifdef LOG_PATH 107 | log_location = fopen(LOG_PATH, "w"); 108 | #else 109 | log_location = stderr; 110 | #endif 111 | #endif 112 | atom_ok = enif_make_atom(env, "ok"); 113 | atom_error = enif_make_atom(env, "error"); 114 | atom_name = enif_make_atom(env, "name"); 115 | atom_label = enif_make_atom(env, "label"); 116 | atom_location = enif_make_atom(env, "location"); 117 | atom_controller = enif_make_atom(env, "controller"); 118 | atom_circuits_gpio = enif_make_atom(env, "circuits_gpio"); 119 | atom_consumer = enif_make_atom(env, "consumer"); 120 | 121 | size_t extra_size = hal_priv_size(); 122 | struct gpio_priv *priv = enif_alloc(sizeof(struct gpio_priv) + extra_size); 123 | if (!priv) { 124 | error("Can't allocate gpio_priv"); 125 | return 1; 126 | } 127 | 128 | priv->gpio_pin_rt = enif_open_resource_type_x(env, "gpio_pin", &gpio_pin_init, ERL_NIF_RT_CREATE, NULL); 129 | 130 | if (hal_load(&priv->hal_priv) < 0) { 131 | error("Can't initialize HAL"); 132 | return 1; 133 | } 134 | 135 | *priv_data = (void *) priv; 136 | return 0; 137 | } 138 | 139 | static void unload(ErlNifEnv *env, void *priv_data) 140 | { 141 | (void) env; 142 | 143 | struct gpio_priv *priv = priv_data; 144 | debug("unload"); 145 | 146 | hal_unload(&priv->hal_priv); 147 | } 148 | 149 | static ERL_NIF_TERM read_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 150 | { 151 | struct gpio_priv *priv = enif_priv_data(env); 152 | struct gpio_pin *pin; 153 | 154 | if (argc != 1 || !enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin)) 155 | return enif_make_badarg(env); 156 | 157 | int value = hal_read_gpio(pin); 158 | if (value < 0) 159 | return enif_raise_exception(env, enif_make_atom(env, strerror(errno))); 160 | 161 | return enif_make_int(env, value); 162 | } 163 | 164 | static ERL_NIF_TERM write_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 165 | { 166 | struct gpio_priv *priv = enif_priv_data(env); 167 | struct gpio_pin *pin; 168 | int value; 169 | if (argc != 2 || 170 | !enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin) || 171 | !enif_get_int(env, argv[1], &value)) 172 | return enif_make_badarg(env); 173 | 174 | if (!pin->config.is_output) 175 | return enif_raise_exception(env, enif_make_atom(env, "pin_not_input")); 176 | 177 | // Make sure value is 0 or 1 178 | value = !!value; 179 | 180 | if (hal_write_gpio(pin, value, env) < 0) 181 | return enif_raise_exception(env, enif_make_atom(env, strerror(errno))); 182 | 183 | return atom_ok; 184 | } 185 | 186 | static int get_trigger(ErlNifEnv *env, ERL_NIF_TERM term, enum trigger_mode *mode) 187 | { 188 | char buffer[16]; 189 | if (!enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1)) 190 | return false; 191 | 192 | if (strcmp("none", buffer) == 0) *mode = TRIGGER_NONE; 193 | else if (strcmp("rising", buffer) == 0) *mode = TRIGGER_RISING; 194 | else if (strcmp("falling", buffer) == 0) *mode = TRIGGER_FALLING; 195 | else if (strcmp("both", buffer) == 0) *mode = TRIGGER_BOTH; 196 | else return false; 197 | 198 | return true; 199 | } 200 | 201 | static int get_direction(ErlNifEnv *env, ERL_NIF_TERM term, bool *is_output) 202 | { 203 | char buffer[8]; 204 | if (!enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1)) 205 | return false; 206 | 207 | if (strcmp("input", buffer) == 0) *is_output = false; 208 | else if (strcmp("output", buffer) == 0) *is_output = true; 209 | else return false; 210 | 211 | return true; 212 | } 213 | 214 | static int get_resolved_location(ErlNifEnv *env, ERL_NIF_TERM term, char *gpiochip_path, int *offset) 215 | { 216 | int arity; 217 | const ERL_NIF_TERM *tuple; 218 | ErlNifBinary gpiochip_binary; 219 | 220 | if (!enif_get_tuple(env, term, &arity, &tuple) || 221 | arity != 2 || 222 | !enif_inspect_binary(env, tuple[0], &gpiochip_binary) || 223 | gpiochip_binary.size + 1 > MAX_GPIOCHIP_PATH_LEN || 224 | !enif_get_int(env, tuple[1], offset)) 225 | return false; 226 | 227 | memcpy(gpiochip_path, gpiochip_binary.data, gpiochip_binary.size); 228 | gpiochip_path[gpiochip_binary.size] = '\0'; 229 | return true; 230 | } 231 | 232 | static int get_value(ErlNifEnv *env, ERL_NIF_TERM term, int *value) 233 | { 234 | int v; 235 | if (enif_get_int(env, term, &v)) { 236 | // Force v to be 0 or 1 237 | *value = !!v; 238 | } else { 239 | // Interpret anything else as 0 for backwards compatibility 240 | // with Circuit.GPIO v1's ":not_set". 0 is cdev's default. 241 | *value = 0; 242 | } 243 | return true; 244 | } 245 | 246 | static int get_pull_mode(ErlNifEnv *env, ERL_NIF_TERM term, enum pull_mode *pull) 247 | { 248 | char buffer[16]; 249 | if (!enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1)) 250 | return false; 251 | 252 | if (strcmp("not_set", buffer) == 0) *pull = PULL_NOT_SET; 253 | else if (strcmp("none", buffer) == 0) *pull = PULL_NONE; 254 | else if (strcmp("pullup", buffer) == 0) *pull = PULL_UP; 255 | else if (strcmp("pulldown", buffer) == 0) *pull = PULL_DOWN; 256 | else return false; 257 | 258 | return true; 259 | } 260 | 261 | static ERL_NIF_TERM set_interrupts(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 262 | { 263 | struct gpio_priv *priv = enif_priv_data(env); 264 | struct gpio_pin *pin; 265 | 266 | if (argc != 4 || 267 | !enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin)) 268 | return enif_make_badarg(env); 269 | 270 | struct gpio_config old_config = pin->config; 271 | if (!get_trigger(env, argv[1], &pin->config.trigger) || 272 | !enif_get_boolean(env, argv[2], &pin->config.suppress_glitches) || 273 | !enif_get_local_pid(env, argv[3], &pin->config.pid)) { 274 | pin->config = old_config; 275 | return enif_make_badarg(env); 276 | } 277 | 278 | int rc = hal_apply_interrupts(pin, env); 279 | if (rc < 0) { 280 | pin->config = old_config; 281 | return make_errno_error(env, rc); 282 | } 283 | 284 | return atom_ok; 285 | } 286 | 287 | static ERL_NIF_TERM set_direction(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 288 | { 289 | struct gpio_priv *priv = enif_priv_data(env); 290 | struct gpio_pin *pin; 291 | 292 | if (argc != 2 || 293 | !enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin)) 294 | return enif_make_badarg(env); 295 | 296 | struct gpio_config old_config = pin->config; 297 | if (!get_direction(env, argv[1], &pin->config.is_output)) 298 | return enif_make_badarg(env); 299 | 300 | int rc = hal_apply_direction(pin); 301 | if (rc < 0) { 302 | pin->config = old_config; 303 | return make_errno_error(env, rc); 304 | } 305 | 306 | return atom_ok; 307 | } 308 | 309 | static ERL_NIF_TERM set_pull_mode(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 310 | { 311 | struct gpio_priv *priv = enif_priv_data(env); 312 | struct gpio_pin *pin; 313 | 314 | if (argc != 2 || 315 | !enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin)) 316 | return enif_make_badarg(env); 317 | 318 | struct gpio_config old_config = pin->config; 319 | if (!get_pull_mode(env, argv[1], &pin->config.pull)) 320 | return enif_make_badarg(env); 321 | 322 | int rc = hal_apply_pull_mode(pin); 323 | if (rc < 0) { 324 | pin->config = old_config; 325 | return make_errno_error(env, rc); 326 | } 327 | 328 | return atom_ok; 329 | } 330 | 331 | static ERL_NIF_TERM get_status(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 332 | { 333 | struct gpio_priv *priv = enif_priv_data(env); 334 | char gpiochip_path[MAX_GPIOCHIP_PATH_LEN]; 335 | int offset; 336 | 337 | if (argc != 1 || !get_resolved_location(env, argv[0], gpiochip_path, &offset)) 338 | return enif_make_badarg(env); 339 | 340 | ERL_NIF_TERM result; 341 | int rc = hal_get_status(priv->hal_priv, env, gpiochip_path, offset, &result); 342 | if (rc >= 0) 343 | return make_ok_tuple(env, result); 344 | else 345 | return make_errno_error(env, rc); 346 | } 347 | 348 | static ERL_NIF_TERM open_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 349 | { 350 | struct gpio_priv *priv = enif_priv_data(env); 351 | bool is_output; 352 | int offset; 353 | int initial_value; 354 | enum pull_mode pull; 355 | char gpiochip_path[MAX_GPIOCHIP_PATH_LEN]; 356 | 357 | if (argc != 5 || 358 | !get_resolved_location(env, argv[1], gpiochip_path, &offset) || 359 | !get_direction(env, argv[2], &is_output) || 360 | !get_value(env, argv[3], &initial_value) || 361 | !get_pull_mode(env, argv[4], &pull)) 362 | return enif_make_badarg(env); 363 | 364 | debug("open {%s, %d}", gpiochip_path, offset); 365 | 366 | struct gpio_pin *pin = enif_alloc_resource(priv->gpio_pin_rt, sizeof(struct gpio_pin)); 367 | pin->fd = -1; 368 | memcpy(pin->gpiochip, gpiochip_path, MAX_GPIOCHIP_PATH_LEN); 369 | pin->offset = offset; 370 | pin->env = enif_alloc_env(); 371 | pin->gpio_spec = enif_make_copy(pin->env, argv[0]); 372 | pin->hal_priv = priv->hal_priv; 373 | pin->config.is_output = is_output; 374 | pin->config.trigger = TRIGGER_NONE; 375 | pin->config.pull = pull; 376 | pin->config.suppress_glitches = false; 377 | pin->config.initial_value = initial_value; 378 | 379 | int rc = hal_open_gpio(pin, env); 380 | if (rc < 0) { 381 | enif_release_resource(pin); 382 | return make_errno_error(env, rc); 383 | } 384 | 385 | // Transfer ownership of the resource to Erlang so that it can be garbage collected. 386 | ERL_NIF_TERM pin_resource = enif_make_resource(env, pin); 387 | enif_release_resource(pin); 388 | 389 | return make_ok_tuple(env, pin_resource); 390 | } 391 | 392 | static ERL_NIF_TERM close_gpio(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 393 | { 394 | struct gpio_priv *priv = enif_priv_data(env); 395 | struct gpio_pin *pin; 396 | if (argc != 1 || 397 | !enif_get_resource(env, argv[0], priv->gpio_pin_rt, (void**) &pin)) 398 | return enif_make_badarg(env); 399 | 400 | release_gpio_pin(priv, pin); 401 | 402 | return atom_ok; 403 | } 404 | 405 | static ERL_NIF_TERM backend_info(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 406 | { 407 | (void) argc; 408 | (void) argv; 409 | 410 | struct gpio_priv *priv = enif_priv_data(env); 411 | ERL_NIF_TERM info = enif_make_new_map(env); 412 | 413 | return hal_info(env, priv->hal_priv, info); 414 | } 415 | 416 | static ERL_NIF_TERM gpio_enumerate(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 417 | { 418 | (void) argc; 419 | (void) argv; 420 | 421 | struct gpio_priv *priv = enif_priv_data(env); 422 | 423 | return hal_enumerate(env, priv->hal_priv); 424 | } 425 | 426 | static ErlNifFunc nif_funcs[] = { 427 | {"open", 5, open_gpio, ERL_NIF_DIRTY_JOB_IO_BOUND}, 428 | {"close", 1, close_gpio, 0}, 429 | {"read", 1, read_gpio, 0}, 430 | {"write", 2, write_gpio, 0}, 431 | {"set_interrupts", 4, set_interrupts, 0}, 432 | {"set_direction", 2, set_direction, 0}, 433 | {"set_pull_mode", 2, set_pull_mode, 0}, 434 | {"status", 1, get_status, 0}, 435 | {"backend_info", 0, backend_info, 0}, 436 | {"enumerate", 0, gpio_enumerate, 0} 437 | }; 438 | 439 | ERL_NIF_INIT(Elixir.Circuits.GPIO.Nif, nif_funcs, load, NULL, NULL, unload) 440 | -------------------------------------------------------------------------------- /c_src/gpio_nif.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Frank Hunleth 2 | // SPDX-FileCopyrightText: 2018 Mark Sebald 3 | // SPDX-FileCopyrightText: 2023 Connor Rigby 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | 7 | #ifndef GPIO_NIF_H 8 | #define GPIO_NIF_H 9 | 10 | #include "erl_nif.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | //#define DEBUG 17 | 18 | #ifdef DEBUG 19 | extern FILE *log_location; 20 | #define LOG_PATH "/tmp/circuits_gpio.log" 21 | #define debug(...) do { enif_fprintf(log_location, __VA_ARGS__); enif_fprintf(log_location, "\r\n"); fflush(log_location); } while(0) 22 | #define error(...) do { debug(__VA_ARGS__); } while (0) 23 | #define start_timing() ErlNifTime __start = enif_monotonic_time(ERL_NIF_USEC) 24 | #define elapsed_microseconds() (enif_monotonic_time(ERL_NIF_USEC) - __start) 25 | #else 26 | #define debug(...) 27 | #define error(...) do { enif_fprintf(stderr, __VA_ARGS__); enif_fprintf(stderr, "\n"); } while(0) 28 | #define start_timing() 29 | #define elapsed_microseconds() 0 30 | #endif 31 | 32 | #define MAX_GPIOCHIP_PATH_LEN 32 33 | #define MAX_GPIO_LISTENERS 32 34 | 35 | enum trigger_mode { 36 | TRIGGER_NONE = 0, 37 | TRIGGER_RISING, 38 | TRIGGER_FALLING, 39 | TRIGGER_BOTH 40 | }; 41 | 42 | enum pull_mode { 43 | PULL_NOT_SET, 44 | PULL_NONE, 45 | PULL_UP, 46 | PULL_DOWN 47 | }; 48 | 49 | struct gpio_priv { 50 | ErlNifResourceType *gpio_pin_rt; 51 | 52 | uint32_t hal_priv[1]; 53 | }; 54 | 55 | struct gpio_config { 56 | bool is_output; 57 | enum trigger_mode trigger; 58 | enum pull_mode pull; 59 | bool suppress_glitches; 60 | int initial_value; 61 | ErlNifPid pid; 62 | }; 63 | 64 | struct gpio_pin { 65 | char gpiochip[MAX_GPIOCHIP_PATH_LEN]; 66 | int offset; 67 | int fd; 68 | void *hal_priv; 69 | struct gpio_config config; 70 | 71 | // NIF environment for holding on to the gpio_spec term 72 | ErlNifEnv *env; 73 | ERL_NIF_TERM gpio_spec; 74 | }; 75 | 76 | // Atoms 77 | extern ERL_NIF_TERM atom_ok; 78 | extern ERL_NIF_TERM atom_error; 79 | extern ERL_NIF_TERM atom_name; 80 | extern ERL_NIF_TERM atom_label; 81 | extern ERL_NIF_TERM atom_location; 82 | extern ERL_NIF_TERM atom_controller; 83 | extern ERL_NIF_TERM atom_circuits_gpio; 84 | extern ERL_NIF_TERM atom_consumer; 85 | 86 | // HAL 87 | 88 | /** 89 | * Return information about the HAL. 90 | * 91 | * This should return a map with the name of the HAL and any info that 92 | * would help debug issues with it. 93 | */ 94 | ERL_NIF_TERM hal_info(ErlNifEnv *env, void *hal_priv, ERL_NIF_TERM info); 95 | 96 | /** 97 | * Enumerate all GPIO pins 98 | * 99 | * Returns a list of Circuits.GPIO.identifiers maps 100 | */ 101 | ERL_NIF_TERM hal_enumerate(ErlNifEnv *env, void *hal_priv); 102 | 103 | /** 104 | * Return the additional number of bytes of private data to allocate 105 | * for the HAL. 106 | */ 107 | size_t hal_priv_size(void); 108 | 109 | /** 110 | * Initialize the HAL 111 | * 112 | * @param hal_priv where to store state 113 | * @return 0 on success 114 | */ 115 | int hal_load(void *hal_priv); 116 | 117 | /** 118 | * Release all resources held by the HAL 119 | * 120 | * @param hal_priv private state 121 | */ 122 | void hal_unload(void *hal_priv); 123 | 124 | /** 125 | * Open up and initialize a GPIO. 126 | * 127 | * @param pin information about the GPIO 128 | * @param env a NIF environment in case a message is sent 129 | * @return 0 on success, -errno on failure 130 | */ 131 | int hal_open_gpio(struct gpio_pin *pin, 132 | ErlNifEnv *env); 133 | 134 | /** 135 | * Free up resources for the specified GPIO 136 | * 137 | * @param pin GPIO pin information 138 | */ 139 | void hal_close_gpio(struct gpio_pin *pin); 140 | 141 | /** 142 | * Read the current value of a GPIO 143 | * 144 | * @param pin which one 145 | * @return 0 if low; 1 if high 146 | */ 147 | int hal_read_gpio(struct gpio_pin *pin); 148 | 149 | /** 150 | * Change the value of a GPIO 151 | * 152 | * @param pin which one 153 | * @param value 0 or 1 154 | * @param env ErlNifEnv if this causes an event to be sent 155 | * @return 0 on success 156 | */ 157 | int hal_write_gpio(struct gpio_pin *pin, int value, ErlNifEnv *env); 158 | 159 | /** 160 | * Apply GPIO direction settings 161 | * 162 | * This should set the GPIO to an input or an output. If setting 163 | * as an output, it should check the initial_value. If the 164 | * initial_value is < 0 then the GPIO should retain its value 165 | * if already an output. If set to 0 or 1, the GPIO should be 166 | * initialized to that value. 167 | * 168 | * @param pin which one 169 | * @return 0 on success 170 | */ 171 | int hal_apply_direction(struct gpio_pin *pin); 172 | 173 | /** 174 | * Apply GPIO interrupt settings 175 | * 176 | * @param pin the pin and notification trigger info 177 | * @return 0 on success 178 | */ 179 | int hal_apply_interrupts(struct gpio_pin *pin, ErlNifEnv *env); 180 | 181 | /** 182 | * Apply GPIO pull mode settings 183 | * 184 | * @param pin which one 185 | * @return 0 on success 186 | */ 187 | int hal_apply_pull_mode(struct gpio_pin *pin); 188 | 189 | /** 190 | * Return a map that has runtime information about a GPIO 191 | * 192 | * @param env a NIF environment for making the map 193 | * @param gpiochip which controller 194 | * @param offset the offset on the controller 195 | * @param result where to store the result when successful 196 | * @return 0 on success, -errno on failure 197 | */ 198 | int hal_get_status(void *hal_priv, ErlNifEnv *env, const char *gpiochip, int offset, ERL_NIF_TERM *result); 199 | 200 | // nif_utils.c 201 | ERL_NIF_TERM make_ok_tuple(ErlNifEnv *env, ERL_NIF_TERM value); 202 | ERL_NIF_TERM make_errno_error(ErlNifEnv *env, int errno_value); 203 | ERL_NIF_TERM make_string_binary(ErlNifEnv *env, const char *str); 204 | int enif_get_boolean(ErlNifEnv *env, ERL_NIF_TERM term, bool *v); 205 | 206 | int send_gpio_message(ErlNifEnv *env, 207 | ERL_NIF_TERM gpio_spec, 208 | ErlNifPid *pid, 209 | int64_t timestamp, 210 | int value); 211 | 212 | #endif // GPIO_NIF_H 213 | -------------------------------------------------------------------------------- /c_src/hal_cdev_gpio.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Connor Rigby 2 | // SPDX-FileCopyrightText: 2023 Frank Hunleth 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | #include "gpio_nif.h" 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include "linux/gpio.h" 18 | 19 | #include "hal_cdev_gpio.h" 20 | 21 | #define CONSUMER "circuits_gpio" 22 | 23 | /* Some time between Linux 5.10 and Linux 5.15, GPIO numbering changed on 24 | * AM335x devices (Beaglebone, etc.). These devices have 4 banks of 32 GPIOs. 25 | * They used to be alphabetically sorted for file names which mirrored the 26 | * order they showed up in the I/O address map. Now they show up with the bank 27 | * at address 0x44c00000 coming after all of the 0x48000000 banks. 28 | * 29 | * To get the original mapping, gpiochips 0-3 need to be rotated. 30 | * 31 | * The real fix is to embrace cdev and stop using GPIO numbers, but that 32 | * requires changing a lot of code, so work around it. 33 | */ 34 | static int gpiochip_order_r[] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; 35 | 36 | static void check_bbb_linux_5_15_gpio_change() 37 | { 38 | // Check for the gpiochip ordering that has the 0x44c00000 controller 39 | // ordered AFTER the 0x48000000. 40 | // 41 | // These are ordered so that the for loop fails as soon as possible on 42 | // non-AM335x platforms. Since few devices get up to gpiochip3, the 43 | // readlink(2) call should fail and there shouldn't even be a string 44 | // compare. 45 | static const char *symlink_value[] = { 46 | "/sys/bus/gpio/devices/gpiochip3", 47 | "../../../devices/platform/ocp/44c00000.interconnect/44c00000.interconnect:segment@200000/44e07000.target-module/44e07000.gpio/gpiochip3", 48 | "/sys/bus/gpio/devices/gpiochip0", 49 | "../../../devices/platform/ocp/48000000.interconnect/48000000.interconnect:segment@0/4804c000.target-module/4804c000.gpio/gpiochip0", 50 | "/sys/bus/gpio/devices/gpiochip1", 51 | "../../../devices/platform/ocp/48000000.interconnect/48000000.interconnect:segment@100000/481ac000.target-module/481ac000.gpio/gpiochip1", 52 | "/sys/bus/gpio/devices/gpiochip2", 53 | "../../../devices/platform/ocp/48000000.interconnect/48000000.interconnect:segment@100000/481ae000.target-module/481ae000.gpio/gpiochip2" 54 | }; 55 | 56 | char path[192]; 57 | int i; 58 | 59 | for (i = 0; i < 8; i += 2) { 60 | ssize_t path_len = readlink(symlink_value[i], path, sizeof(path) - 1); 61 | if (path_len < 0) 62 | return; 63 | 64 | path[path_len] = '\0'; 65 | if (strcmp(symlink_value[i + 1], path) != 0) 66 | return; 67 | } 68 | 69 | // This is a BBB with the new mapping. Rotate the scan order to compensate: 70 | gpiochip_order_r[15] = 3; 71 | gpiochip_order_r[14] = 0; 72 | gpiochip_order_r[13] = 1; 73 | gpiochip_order_r[12] = 2; 74 | } 75 | 76 | static int get_value_v2(int fd) 77 | { 78 | struct gpio_v2_line_values vals; 79 | memset(&vals, 0, sizeof(vals)); 80 | vals.mask = 1; 81 | 82 | if (ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals) < 0) { 83 | return -errno; 84 | } 85 | 86 | return vals.bits & 0x1; 87 | } 88 | 89 | static int set_value_v2(int fd, int value) 90 | { 91 | struct gpio_v2_line_values vals; 92 | vals.bits = value; 93 | vals.mask = 1; 94 | 95 | if (ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &vals) < 0) 96 | return -errno; 97 | 98 | return 0; 99 | } 100 | 101 | static uint64_t config_to_flags(const struct gpio_pin *pin) 102 | { 103 | uint64_t flags = pin->config.is_output ? GPIO_V2_LINE_FLAG_OUTPUT : GPIO_V2_LINE_FLAG_INPUT; 104 | 105 | switch (pin->config.pull) { 106 | case PULL_UP: 107 | flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; 108 | break; 109 | case PULL_DOWN: 110 | flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; 111 | break; 112 | case PULL_NONE: 113 | flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED; 114 | break; 115 | default: 116 | break; 117 | } 118 | 119 | switch (pin->config.trigger) { 120 | case TRIGGER_RISING: 121 | flags |= GPIO_V2_LINE_FLAG_EDGE_RISING; 122 | break; 123 | case TRIGGER_FALLING: 124 | flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING; 125 | break; 126 | case TRIGGER_BOTH: 127 | flags |= GPIO_V2_LINE_FLAG_EDGE_RISING | GPIO_V2_LINE_FLAG_EDGE_FALLING; 128 | break; 129 | case TRIGGER_NONE: 130 | default: 131 | break; 132 | } 133 | 134 | return flags; 135 | } 136 | 137 | static int set_config_v2(int fd, uint64_t flags) 138 | { 139 | struct gpio_v2_line_config config; 140 | memset(&config, 0, sizeof(config)); 141 | 142 | config.flags = flags; 143 | if (ioctl(fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &config) < 0) { 144 | return -errno; 145 | } 146 | 147 | return 0; 148 | } 149 | 150 | static int request_line_v2(int fd, unsigned int offset, uint64_t flags, int val) 151 | { 152 | struct gpio_v2_line_request req; 153 | memset(&req, 0, sizeof(req)); 154 | 155 | req.num_lines = 1; 156 | req.offsets[0] = offset; 157 | req.config.flags = flags; 158 | strcpy(req.consumer, CONSUMER); 159 | if (flags & GPIO_V2_LINE_FLAG_OUTPUT) { 160 | if (val >= 0) { 161 | debug("Initializing %d's value to %d on open", offset, val); 162 | req.config.num_attrs = 1; 163 | req.config.attrs[0].mask = 1; 164 | req.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES; 165 | req.config.attrs[0].attr.values = (unsigned int) val; 166 | } else { 167 | debug("Not initializing %d's value on open", offset); 168 | } 169 | } 170 | 171 | if (ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req) < 0) { 172 | return -errno; 173 | } 174 | return req.fd; 175 | } 176 | 177 | size_t hal_priv_size() 178 | { 179 | return sizeof(struct hal_cdev_gpio_priv); 180 | } 181 | 182 | ERL_NIF_TERM hal_info(ErlNifEnv *env, void *hal_priv, ERL_NIF_TERM info) 183 | { 184 | enif_make_map_put(env, info, atom_name, enif_make_atom(env, "Elixir.Circuits.GPIO.CDev"), &info); 185 | enif_make_map_put(env, info, 186 | enif_make_atom(env, "gpio_number_remapping"), 187 | gpiochip_order_r[15] == 3 ? enif_make_atom(env, "am335x") : enif_make_atom(env, "none"), 188 | &info); 189 | (void) hal_priv; 190 | return info; 191 | } 192 | 193 | int hal_load(void *hal_priv) 194 | { 195 | struct hal_cdev_gpio_priv *priv = hal_priv; 196 | memset(priv, 0, sizeof(struct hal_cdev_gpio_priv)); 197 | check_bbb_linux_5_15_gpio_change(); 198 | 199 | if (pipe(priv->pipe_fds) < 0) { 200 | error("pipe failed"); 201 | return 1; 202 | } 203 | 204 | if (enif_thread_create("gpio_poller", &priv->poller_tid, gpio_poller_thread, &priv->pipe_fds[0], NULL) != 0) { 205 | error("enif_thread_create failed"); 206 | return 1; 207 | } 208 | return 0; 209 | } 210 | 211 | void hal_unload(void *hal_priv) 212 | { 213 | debug("hal_unload"); 214 | struct hal_cdev_gpio_priv *priv = hal_priv; 215 | 216 | // Close everything related to the listening thread so that it exits 217 | close(priv->pipe_fds[0]); 218 | close(priv->pipe_fds[1]); 219 | // If the listener thread hasn't exited already, it should do so soon. 220 | enif_thread_join(priv->poller_tid, NULL); 221 | } 222 | 223 | int hal_open_gpio(struct gpio_pin *pin, 224 | ErlNifEnv *env) 225 | { 226 | int gpiochip_fd = open(pin->gpiochip, O_RDWR|O_CLOEXEC); 227 | if (gpiochip_fd < 0) 228 | return -errno; 229 | 230 | uint64_t flags = config_to_flags(pin); 231 | int value = pin->config.is_output ? pin->config.initial_value : -1; 232 | 233 | pin->fd = request_line_v2(gpiochip_fd, pin->offset, flags, value); 234 | close(gpiochip_fd); 235 | debug("requesting pin %s:%d -> %d", pin->gpiochip, pin->offset, pin->fd); 236 | if (pin->fd < 0) 237 | return pin->fd; 238 | 239 | // Only call hal_apply_interrupts if there's a trigger 240 | if (pin->config.trigger != TRIGGER_NONE) { 241 | int rc = hal_apply_interrupts(pin, env); 242 | if (rc < 0) { 243 | close(pin->fd); 244 | return rc; 245 | } 246 | } 247 | 248 | return 0; 249 | } 250 | 251 | void hal_close_gpio(struct gpio_pin *pin) 252 | { 253 | debug("hal_close_gpio %s:%d", pin->gpiochip, pin->offset); 254 | if (pin->fd >= 0) { 255 | // Turn off interrupts if they're on. 256 | if (pin->config.trigger != TRIGGER_NONE) { 257 | pin->config.trigger = TRIGGER_NONE; 258 | update_polling_thread(pin); 259 | } 260 | close(pin->fd); 261 | } 262 | } 263 | 264 | int hal_read_gpio(struct gpio_pin *pin) 265 | { 266 | debug("hal_read_gpio %s:%d", pin->gpiochip, pin->offset); 267 | return get_value_v2(pin->fd); 268 | } 269 | 270 | int hal_write_gpio(struct gpio_pin *pin, int value, ErlNifEnv *env) 271 | { 272 | (void) env; 273 | debug("hal_write_gpio %s:%d -> %d", pin->gpiochip, pin->offset, value); 274 | return set_value_v2(pin->fd, value); 275 | } 276 | 277 | static int refresh_config(const struct gpio_pin *pin) 278 | { 279 | uint64_t flags = config_to_flags(pin); 280 | return set_config_v2(pin->fd, flags); 281 | } 282 | 283 | int hal_apply_interrupts(struct gpio_pin *pin, ErlNifEnv *env) 284 | { 285 | (void) env; 286 | debug("hal_apply_interrupts %s:%d", pin->gpiochip, pin->offset); 287 | // Update the configuration and start or stop polling 288 | if (refresh_config(pin) < 0 || 289 | update_polling_thread(pin) < 0) 290 | return -1; 291 | 292 | return 0; 293 | } 294 | 295 | int hal_apply_direction(struct gpio_pin *pin) 296 | { 297 | debug("hal_apply_direction %s:%d", pin->gpiochip, pin->offset); 298 | return refresh_config(pin); 299 | } 300 | 301 | int hal_apply_pull_mode(struct gpio_pin *pin) 302 | { 303 | debug("hal_apply_pull_mode %s:%d", pin->gpiochip, pin->offset); 304 | return refresh_config(pin); 305 | } 306 | 307 | ERL_NIF_TERM hal_enumerate(ErlNifEnv *env, void *hal_priv) 308 | { 309 | int i; 310 | 311 | // This code scans GPIOs in reverse order so that the resulting list that 312 | // is built is in order. Order matters because it looks nice and pin number 313 | // compatibility with Circuits.GPIO v1 depends on it. 314 | ERL_NIF_TERM gpio_list = enif_make_list(env, 0); 315 | for (i = 0; i < 16; i++) { 316 | char path[32]; 317 | sprintf(path, "/dev/gpiochip%d", gpiochip_order_r[i]); 318 | 319 | int fd = open(path, O_RDONLY|O_CLOEXEC); 320 | if (fd < 0) 321 | continue; 322 | 323 | struct gpiochip_info info; 324 | memset(&info, 0, sizeof(struct gpiochip_info)); 325 | if (ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info) < 0) 326 | continue; 327 | 328 | ERL_NIF_TERM chip_label = make_string_binary(env, info.label); 329 | ERL_NIF_TERM chip_name = make_string_binary(env, info.name); 330 | 331 | int j; 332 | for (j = info.lines - 1; j >= 0; j--) { 333 | struct gpio_v2_line_info line; 334 | memset(&line, 0, sizeof(struct gpio_v2_line_info)); 335 | line.offset = j; 336 | if (ioctl(fd, GPIO_V2_GET_LINEINFO_IOCTL, &line) >= 0) { 337 | ERL_NIF_TERM line_map = enif_make_new_map(env); 338 | ERL_NIF_TERM line_offset = enif_make_int(env, j); 339 | ERL_NIF_TERM line_label = make_string_binary(env, line.name); 340 | 341 | enif_make_map_put(env, line_map, atom_controller, chip_label, &line_map); 342 | enif_make_map_put(env, line_map, atom_label, line_label, &line_map); 343 | enif_make_map_put(env, line_map, atom_location, enif_make_tuple2(env, chip_name, line_offset), &line_map); 344 | 345 | gpio_list = enif_make_list_cell(env, line_map, gpio_list); 346 | } 347 | } 348 | close(fd); 349 | } 350 | return gpio_list; 351 | } 352 | 353 | int hal_get_status(void *hal_priv, ErlNifEnv *env, const char *gpiochip, int offset, ERL_NIF_TERM *result) 354 | { 355 | int gpiochip_fd = open(gpiochip, O_RDWR|O_CLOEXEC); 356 | if (gpiochip_fd < 0) 357 | return -errno; 358 | 359 | struct gpio_v2_line_info line; 360 | memset(&line, 0, sizeof(struct gpio_v2_line_info)); 361 | line.offset = offset; 362 | if (ioctl(gpiochip_fd, GPIO_V2_GET_LINEINFO_IOCTL, &line) < 0) { 363 | int errno_value = errno; 364 | close(gpiochip_fd); 365 | return -errno_value; 366 | } 367 | close(gpiochip_fd); 368 | 369 | int is_output = line.flags & GPIO_V2_LINE_FLAG_OUTPUT; 370 | const char *pull_mode_str; 371 | if (line.flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP) 372 | pull_mode_str = "pullup"; 373 | else if (line.flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN) 374 | pull_mode_str = "pulldown"; 375 | else 376 | pull_mode_str = "none"; 377 | 378 | ERL_NIF_TERM map = enif_make_new_map(env); 379 | ERL_NIF_TERM consumer = make_string_binary(env, line.consumer); 380 | enif_make_map_put(env, map, atom_consumer, consumer, &map); 381 | enif_make_map_put(env, map, enif_make_atom(env, "direction"), enif_make_atom(env, is_output ? "output" : "input"), &map); 382 | enif_make_map_put(env, map, enif_make_atom(env, "pull_mode"), enif_make_atom(env, pull_mode_str), &map); 383 | 384 | *result = map; 385 | return 0; 386 | } 387 | -------------------------------------------------------------------------------- /c_src/hal_cdev_gpio.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Frank Hunleth 2 | // SPDX-FileCopyrightText: 2023 Connor Rigby 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | #ifndef HAL_CDEV_GPIO_H 7 | #define HAL_CDEV_GPIO_H 8 | 9 | #include 10 | #include "erl_nif.h" 11 | 12 | struct hal_cdev_gpio_priv { 13 | ErlNifTid poller_tid; 14 | int pipe_fds[2]; 15 | }; 16 | 17 | struct gpio_pin; 18 | 19 | void *gpio_poller_thread(void *arg); 20 | int update_polling_thread(struct gpio_pin *pin); 21 | 22 | #endif // HAL_CDEV_GPIO_H 23 | -------------------------------------------------------------------------------- /c_src/hal_cdev_gpio_interrupts.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Frank Hunleth 2 | // SPDX-FileCopyrightText: 2019 Matt Ludwigs 3 | // SPDX-FileCopyrightText: 2023 Connor Rigby 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | 7 | #include "gpio_nif.h" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include "linux/gpio.h" 21 | 22 | #include "hal_cdev_gpio.h" 23 | 24 | #ifndef CLOCK_MONOTONIC 25 | #define CLOCK_MONOTONIC 1 26 | #endif 27 | 28 | struct gpio_monitor_info { 29 | enum trigger_mode trigger; 30 | int fd; 31 | int offset; 32 | ErlNifPid pid; 33 | ERL_NIF_TERM gpio_spec; 34 | }; 35 | 36 | static void init_listeners(struct gpio_monitor_info *infos) 37 | { 38 | memset(infos, 0, MAX_GPIO_LISTENERS * sizeof(struct gpio_monitor_info)); 39 | } 40 | 41 | static void compact_listeners(struct gpio_monitor_info *infos, int count) 42 | { 43 | int j = -1; 44 | for (int i = 0; i < count - 1; i++) { 45 | if (infos[i].trigger == TRIGGER_NONE) { 46 | if (j >= 0) { 47 | memcpy(&infos[j], &infos[i], sizeof(struct gpio_monitor_info)); 48 | memset(&infos[i], 0, sizeof(struct gpio_monitor_info)); 49 | j++; 50 | } 51 | } else { 52 | if (j < 0) 53 | j = i; 54 | } 55 | } 56 | } 57 | 58 | static int handle_gpio_update(ErlNifEnv *env, 59 | struct gpio_monitor_info *info, 60 | uint64_t timestamp, 61 | int event_id) 62 | { 63 | debug("handle_gpio_update %d", info->offset); 64 | int value = event_id == GPIO_V2_LINE_EVENT_RISING_EDGE ? 1 : 0; 65 | 66 | return send_gpio_message(env, info->gpio_spec, &info->pid, timestamp, value); 67 | } 68 | 69 | static int process_gpio_events(ErlNifEnv *env, 70 | struct gpio_monitor_info *info) 71 | { 72 | struct gpio_v2_line_event events[16]; 73 | ssize_t amount_read = read(info->fd, events, sizeof(events)); 74 | if (amount_read < 0) { 75 | error("Unexpected return from reading gpio events: %d, errno=%d", amount_read, errno); 76 | return -1; 77 | } 78 | 79 | int num_events = amount_read / sizeof(struct gpio_v2_line_event); 80 | for (int i = 0; i < num_events; i++) { 81 | if (!handle_gpio_update(env, 82 | info, 83 | events[i].timestamp_ns, 84 | events[i].id)) { 85 | error("send for gpio %d failed, so not listening to it any more", info->offset); 86 | return -1; 87 | } 88 | } 89 | return 0; 90 | } 91 | 92 | static void add_listener(ErlNifEnv *env, struct gpio_monitor_info *infos, const struct gpio_monitor_info *to_add) 93 | { 94 | for (int i = 0; i < MAX_GPIO_LISTENERS; i++) { 95 | if (infos[i].trigger == TRIGGER_NONE || infos[i].fd == to_add->fd) { 96 | memcpy(&infos[i], to_add, sizeof(struct gpio_monitor_info)); 97 | return; 98 | } 99 | } 100 | error("Too many gpio listeners. Max is %d", MAX_GPIO_LISTENERS); 101 | } 102 | 103 | static void remove_listener(struct gpio_monitor_info *infos, int fd) 104 | { 105 | for (int i = 0; i < MAX_GPIO_LISTENERS; i++) { 106 | if (infos[i].trigger == TRIGGER_NONE) 107 | return; 108 | 109 | if (infos[i].fd == fd) { 110 | infos[i].trigger = TRIGGER_NONE; 111 | compact_listeners(infos, MAX_GPIO_LISTENERS); 112 | return; 113 | } 114 | } 115 | } 116 | 117 | void *gpio_poller_thread(void *arg) 118 | { 119 | struct gpio_monitor_info monitor_info[MAX_GPIO_LISTENERS]; 120 | struct pollfd fdset[MAX_GPIO_LISTENERS + 1]; 121 | int *pipefd = arg; 122 | debug("gpio_poller_thread started"); 123 | 124 | ErlNifEnv *env = enif_alloc_env(); 125 | 126 | init_listeners(monitor_info); 127 | for (;;) { 128 | struct pollfd *fds = &fdset[0]; 129 | nfds_t count = 0; 130 | 131 | struct gpio_monitor_info *info = &monitor_info[0]; 132 | while (info->trigger != TRIGGER_NONE) { 133 | debug("adding fd %d to poll list", info->fd); 134 | fds->fd = info->fd; 135 | fds->events = POLLIN; 136 | fds->revents = 0; 137 | fds++; 138 | info++; 139 | count++; 140 | } 141 | 142 | fds->fd = *pipefd; 143 | fds->events = POLLIN; 144 | fds->revents = 0; 145 | count++; 146 | 147 | debug("poll waiting on %d handles", count); 148 | int rc = poll(fdset, count, -1); 149 | if (rc < 0) { 150 | // Retry if EINTR 151 | if (errno == EINTR) 152 | continue; 153 | 154 | error("poll failed. errno=%d", errno); 155 | break; 156 | } 157 | debug("poll returned rc=%d", rc); 158 | 159 | short revents = fdset[count - 1].revents; 160 | if (revents & (POLLERR | POLLNVAL)) { 161 | // Socket closed so quit thread. This happens on NIF unload. 162 | break; 163 | } 164 | if (revents & (POLLIN | POLLHUP)) { 165 | struct gpio_monitor_info message; 166 | ssize_t amount_read = read(*pipefd, &message, sizeof(message)); 167 | if (amount_read != sizeof(message)) { 168 | error("Unexpected return from read: %d, errno=%d", amount_read, errno); 169 | break; 170 | } 171 | 172 | if (message.trigger != TRIGGER_NONE) 173 | add_listener(env, monitor_info, &message); 174 | else 175 | remove_listener(monitor_info, message.fd); 176 | } 177 | 178 | bool cleanup = false; 179 | for (nfds_t i = 0; i < count - 1; i++) { 180 | if (fdset[i].revents) { 181 | if (fdset[i].revents) { 182 | debug("interrupt on %d", monitor_info[i].offset); 183 | if (process_gpio_events(env, &monitor_info[i]) < 0) { 184 | monitor_info[i].trigger = TRIGGER_NONE; 185 | cleanup = true; 186 | } 187 | } else { 188 | error("error listening on gpio %d", monitor_info[i].offset); 189 | monitor_info[i].trigger = TRIGGER_NONE; 190 | cleanup = true; 191 | } 192 | } 193 | } 194 | 195 | if (cleanup) { 196 | // Compact the listener list 197 | compact_listeners(monitor_info, count); 198 | } 199 | } 200 | 201 | enif_free_env(env); 202 | debug("gpio_poller_thread ended"); 203 | return NULL; 204 | } 205 | 206 | int update_polling_thread(struct gpio_pin *pin) 207 | { 208 | struct hal_cdev_gpio_priv *priv = (struct hal_cdev_gpio_priv *) pin->hal_priv; 209 | 210 | struct gpio_monitor_info message; 211 | message.trigger = pin->config.trigger; 212 | message.fd = pin->fd; 213 | message.offset = pin->offset; 214 | message.gpio_spec = pin->gpio_spec; 215 | message.pid = pin->config.pid; 216 | if (write(priv->pipe_fds[1], &message, sizeof(message)) != sizeof(message)) { 217 | error("Error writing polling thread!"); 218 | return -1; 219 | } 220 | return 0; 221 | } 222 | -------------------------------------------------------------------------------- /c_src/hal_stub.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Frank Hunleth 2 | // SPDX-FileCopyrightText: 2019 Matt Ludwigs 3 | // SPDX-FileCopyrightText: 2023 Connor Rigby 4 | // 5 | // SPDX-License-Identifier: Apache-2.0 6 | 7 | #include "gpio_nif.h" 8 | #include 9 | #include 10 | #include 11 | 12 | #define NUM_GPIOS 64 13 | 14 | /** 15 | * The stub hardware abstraction layer is suitable for some unit testing. 16 | * 17 | * gpiochip0 -> 32 GPIOs. GPIO 0 is connected to GPIO 1, 2 to 3, and so on. 18 | * gpiochip1 -> 32 GPIOs. GPIO 0 is connected to GPIO 1, 2 to 3, and so on. 19 | */ 20 | 21 | struct stub_priv { 22 | atomic_int pins_open; 23 | int in_use[NUM_GPIOS]; // 0=no; 1=yes 24 | int value[NUM_GPIOS]; // -1, 0, 1 -> -1=hiZ 25 | struct gpio_pin *gpio_pins[NUM_GPIOS]; 26 | ErlNifPid pid[NUM_GPIOS]; 27 | enum trigger_mode mode[NUM_GPIOS]; 28 | }; 29 | 30 | ERL_NIF_TERM hal_info(ErlNifEnv *env, void *hal_priv, ERL_NIF_TERM info) 31 | { 32 | struct stub_priv *stub_priv = (struct stub_priv *) hal_priv; 33 | int pins_open = atomic_load(&stub_priv->pins_open); 34 | 35 | // %{name: {Circuits.GPIO.Cdev, test: true}, pins_open: 123}} 36 | enif_make_map_put(env, info, atom_name, 37 | enif_make_tuple2(env, 38 | enif_make_atom(env, "Elixir.Circuits.GPIO.CDev"), 39 | enif_make_list1(env, enif_make_tuple2(env, enif_make_atom(env, "test"), enif_make_atom(env, "true")))), 40 | &info); 41 | enif_make_map_put(env, info, enif_make_atom(env, "pins_open"), enif_make_int(env, pins_open), &info); 42 | 43 | return info; 44 | } 45 | 46 | size_t hal_priv_size(void) 47 | { 48 | return sizeof(struct stub_priv); 49 | } 50 | 51 | int hal_load(void *hal_priv) 52 | { 53 | struct stub_priv *stub_priv = (struct stub_priv *) hal_priv; 54 | 55 | memset(stub_priv, 0, sizeof(struct stub_priv)); 56 | stub_priv->pins_open = 0; 57 | 58 | return 0; 59 | } 60 | 61 | void hal_unload(void *hal_priv) 62 | { 63 | (void) hal_priv; 64 | } 65 | 66 | int hal_open_gpio(struct gpio_pin *pin, 67 | ErlNifEnv *env) 68 | { 69 | struct stub_priv *stub_priv = pin->hal_priv; 70 | int pin_base; 71 | 72 | if (strcmp(pin->gpiochip, "gpiochip0") == 0 || 73 | strcmp(pin->gpiochip, "/dev/gpiochip0") == 0) { 74 | pin_base = 0; 75 | } else if (strcmp(pin->gpiochip, "gpiochip1") == 0 || 76 | strcmp(pin->gpiochip, "/dev/gpiochip1") == 0) { 77 | pin_base = 32; 78 | } else { 79 | return -ENOENT; 80 | } 81 | 82 | if (pin->offset < 0 || pin->offset >= 32) 83 | return -ENOENT; 84 | 85 | pin->fd = pin_base + pin->offset; 86 | stub_priv->gpio_pins[pin->fd] = pin; 87 | 88 | if (pin->config.is_output) { 89 | if (pin->config.initial_value >= 0) { 90 | hal_write_gpio(pin, pin->config.initial_value, env); 91 | } else if (stub_priv->value[pin->fd] == -1) { 92 | // Default the pin to zero when hi impedance even 93 | // when no initial value. 94 | hal_write_gpio(pin, 0, env); 95 | } 96 | } else { 97 | stub_priv->value[pin->fd] = -1; 98 | } 99 | stub_priv->in_use[pin->fd]++; 100 | atomic_fetch_add(&stub_priv->pins_open, 1); 101 | 102 | return 0; 103 | } 104 | 105 | void hal_close_gpio(struct gpio_pin *pin) 106 | { 107 | if (pin->fd >= 0 && pin->fd < NUM_GPIOS) { 108 | struct stub_priv *stub_priv = pin->hal_priv; 109 | stub_priv->mode[pin->fd] = TRIGGER_NONE; 110 | stub_priv->gpio_pins[pin->fd] = NULL; 111 | stub_priv->in_use[pin->fd]--; 112 | atomic_fetch_sub(&stub_priv->pins_open, 1); 113 | 114 | pin->fd = -1; 115 | } 116 | } 117 | 118 | int hal_read_gpio(struct gpio_pin *pin) 119 | { 120 | struct stub_priv *stub_priv = pin->hal_priv; 121 | int our_pin = pin->fd; 122 | int other_pin = our_pin ^ 1; 123 | 124 | if (stub_priv->value[our_pin] != -1) 125 | return stub_priv->value[our_pin]; 126 | 127 | if (stub_priv->value[other_pin] != -1) 128 | return stub_priv->value[other_pin]; 129 | 130 | if (pin->config.pull == PULL_UP) 131 | return 1; 132 | 133 | if (pin->config.pull == PULL_DOWN) 134 | return 0; 135 | 136 | // Both the pin and the pin it's connected to are high impedance and pull mode 137 | // isn't set. This should be random, but that might be more confusing so return 0. 138 | return 0; 139 | } 140 | 141 | static void maybe_send_notification(ErlNifEnv *env, struct gpio_pin *pin, int value) 142 | { 143 | if (!pin) 144 | return; 145 | 146 | struct stub_priv *stub_priv = pin->hal_priv; 147 | 148 | int send_it = 0; 149 | switch (stub_priv->mode[pin->fd]) { 150 | case TRIGGER_BOTH: 151 | send_it = 1; 152 | break; 153 | case TRIGGER_FALLING: 154 | send_it = (value == 0); 155 | break; 156 | case TRIGGER_RISING: 157 | send_it = (value != 0); 158 | break; 159 | case TRIGGER_NONE: 160 | send_it = 0; 161 | break; 162 | } 163 | 164 | if (send_it) { 165 | ErlNifTime now = enif_monotonic_time(ERL_NIF_NSEC); 166 | send_gpio_message(env, pin->gpio_spec, &stub_priv->pid[pin->fd], now, value); 167 | } 168 | } 169 | 170 | int hal_write_gpio(struct gpio_pin *pin, int value, ErlNifEnv *env) 171 | { 172 | struct stub_priv *stub_priv = pin->hal_priv; 173 | int our_pin = pin->fd; 174 | int other_pin = our_pin ^ 1; 175 | if (stub_priv->value[our_pin] != value) { 176 | stub_priv->value[our_pin] = value; 177 | maybe_send_notification(env, stub_priv->gpio_pins[our_pin], value); 178 | 179 | // Only notify other pin if it's not outputting a value. 180 | if (stub_priv->value[other_pin] == -1) 181 | maybe_send_notification(env, stub_priv->gpio_pins[other_pin], value); 182 | } 183 | return 0; 184 | } 185 | 186 | int hal_apply_interrupts(struct gpio_pin *pin, ErlNifEnv *env) 187 | { 188 | struct stub_priv *stub_priv = pin->hal_priv; 189 | 190 | stub_priv->mode[pin->fd] = pin->config.trigger; 191 | stub_priv->pid[pin->fd] = pin->config.pid; 192 | stub_priv->gpio_pins[pin->fd] = pin; 193 | 194 | return 0; 195 | } 196 | 197 | int hal_apply_direction(struct gpio_pin *pin) 198 | { 199 | struct stub_priv *stub_priv = pin->hal_priv; 200 | 201 | if (pin->config.is_output) { 202 | if (stub_priv->value[pin->fd] == -1) { 203 | stub_priv->value[pin->fd] = 0; 204 | } 205 | } else { 206 | stub_priv->value[pin->fd] = -1; 207 | } 208 | 209 | return 0; 210 | } 211 | 212 | int hal_apply_pull_mode(struct gpio_pin *pin) 213 | { 214 | (void) pin; 215 | return 0; 216 | } 217 | 218 | ERL_NIF_TERM hal_enumerate(ErlNifEnv *env, void *hal_priv) 219 | { 220 | ERL_NIF_TERM gpio_list = enif_make_list(env, 0); 221 | 222 | ERL_NIF_TERM chip_name0 = make_string_binary(env, "gpiochip0"); 223 | ERL_NIF_TERM chip_name1 = make_string_binary(env, "gpiochip1"); 224 | ERL_NIF_TERM chip_label0 = make_string_binary(env, "stub0"); 225 | ERL_NIF_TERM chip_label1 = make_string_binary(env, "stub1"); 226 | 227 | int j; 228 | for (j = NUM_GPIOS - 1; j >= 0; j--) { 229 | char line_name[32]; 230 | sprintf(line_name, "pair_%d_%d", j / 2, j % 2); 231 | 232 | ERL_NIF_TERM chip_name = (j >= 32) ? chip_name1 : chip_name0; 233 | ERL_NIF_TERM chip_label = (j >= 32) ? chip_label1 : chip_label0; 234 | ERL_NIF_TERM line_map = enif_make_new_map(env); 235 | ERL_NIF_TERM line_label = make_string_binary(env, line_name); 236 | ERL_NIF_TERM line_offset = enif_make_int(env, j % 32); 237 | 238 | enif_make_map_put(env, line_map, atom_controller, chip_label, &line_map); 239 | enif_make_map_put(env, line_map, atom_label, line_label, &line_map); 240 | enif_make_map_put(env, line_map, atom_location, enif_make_tuple2(env, chip_name, line_offset), &line_map); 241 | 242 | gpio_list = enif_make_list_cell(env, line_map, gpio_list); 243 | } 244 | 245 | return gpio_list; 246 | } 247 | 248 | int hal_get_status(void *hal_priv, ErlNifEnv *env, const char *gpiochip, int offset, ERL_NIF_TERM *result) 249 | { 250 | struct stub_priv *stub_priv = hal_priv; 251 | int pin_base; 252 | 253 | if (strcmp(gpiochip, "gpiochip0") == 0 || 254 | strcmp(gpiochip, "/dev/gpiochip0") == 0) { 255 | pin_base = 0; 256 | } else if (strcmp(gpiochip, "gpiochip1") == 0 || 257 | strcmp(gpiochip, "/dev/gpiochip1") == 0) { 258 | pin_base = 32; 259 | } else { 260 | return -ENOENT; 261 | } 262 | 263 | if (offset < 0 || offset >= 32) 264 | return -ENOENT; 265 | int pin_index = pin_base + offset; 266 | 267 | ERL_NIF_TERM map = enif_make_new_map(env); 268 | 269 | int in_use = stub_priv->in_use[pin_index]; 270 | ERL_NIF_TERM consumer = make_string_binary(env, in_use > 0 ? "stub" : ""); 271 | 272 | struct gpio_pin *pin = stub_priv->gpio_pins[pin_index]; 273 | const char *pull_mode_str; 274 | int is_output; 275 | if (pin) { 276 | switch (pin->config.pull) { 277 | case PULL_DOWN: 278 | pull_mode_str = "pulldown"; 279 | break; 280 | case PULL_UP: 281 | pull_mode_str = "pullup"; 282 | break; 283 | default: 284 | pull_mode_str = "none"; 285 | break; 286 | } 287 | is_output = pin->config.is_output; 288 | } else { 289 | is_output = 0; 290 | pull_mode_str = "none"; 291 | } 292 | 293 | enif_make_map_put(env, map, atom_consumer, consumer, &map); 294 | enif_make_map_put(env, map, enif_make_atom(env, "direction"), enif_make_atom(env, is_output ? "output" : "input"), &map); 295 | enif_make_map_put(env, map, enif_make_atom(env, "pull_mode"), enif_make_atom(env, pull_mode_str), &map); 296 | 297 | *result = map; 298 | return 0; 299 | } 300 | -------------------------------------------------------------------------------- /c_src/nif_utils.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Frank Hunleth 2 | // SPDX-FileCopyrightText: 2024 Connor Rigby 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | #include "gpio_nif.h" 7 | 8 | #include 9 | #include 10 | 11 | ERL_NIF_TERM make_ok_tuple(ErlNifEnv *env, ERL_NIF_TERM value) 12 | { 13 | return enif_make_tuple2(env, atom_ok, value); 14 | } 15 | 16 | ERL_NIF_TERM make_errno_error(ErlNifEnv *env, int errno_value) 17 | { 18 | // Handle return codes from functions that return -errno 19 | if (errno_value < 0) 20 | errno_value = -errno_value; 21 | 22 | ERL_NIF_TERM reason; 23 | switch (errno_value) { 24 | case ENOENT: 25 | reason = enif_make_atom(env, "not_found"); 26 | break; 27 | 28 | case EBUSY: 29 | reason = enif_make_atom(env, "already_open"); 30 | break; 31 | 32 | case EOPNOTSUPP: 33 | reason = enif_make_atom(env, "not_supported"); 34 | break; 35 | 36 | default: 37 | // These errors aren't that helpful, so if they happen, please report 38 | // or update this code to provide a better reason. 39 | reason = enif_make_tuple2(env, enif_make_atom(env, "errno"), enif_make_int(env, errno_value)); 40 | break; 41 | } 42 | 43 | return enif_make_tuple2(env, atom_error, reason); 44 | } 45 | 46 | ERL_NIF_TERM make_string_binary(ErlNifEnv *env, const char *str) 47 | { 48 | ERL_NIF_TERM term; 49 | size_t len = strlen(str); 50 | unsigned char *data = enif_make_new_binary(env, len, &term); 51 | memcpy(data, str, len); 52 | return term; 53 | } 54 | 55 | int enif_get_boolean(ErlNifEnv *env, ERL_NIF_TERM term, bool *v) 56 | { 57 | char buffer[16]; 58 | if (enif_get_atom(env, term, buffer, sizeof(buffer), ERL_NIF_LATIN1) <= 0) 59 | return false; 60 | 61 | if (strcmp("false", buffer) == 0) 62 | *v = false; 63 | else 64 | *v = true; 65 | 66 | return true; 67 | } 68 | -------------------------------------------------------------------------------- /lib/gpio.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2014 Frank Hunleth 2 | # SPDX-FileCopyrightText: 2016 Justin Schneck 3 | # SPDX-FileCopyrightText: 2017 Giovanni Visciano 4 | # SPDX-FileCopyrightText: 2017 Tim Mecklem 5 | # SPDX-FileCopyrightText: 2018 Jon Carstens 6 | # SPDX-FileCopyrightText: 2018 Mark Sebald 7 | # SPDX-FileCopyrightText: 2018 Matt Ludwigs 8 | # SPDX-FileCopyrightText: 2019 Michael Roach 9 | # SPDX-FileCopyrightText: 2023 Connor Rigby 10 | # 11 | # SPDX-License-Identifier: Apache-2.0 12 | 13 | defmodule Circuits.GPIO do 14 | @moduledoc """ 15 | Control GPIOs from Elixir 16 | 17 | See the [Readme](README.md) for a tutorial and the [porting guide](PORTING.md) 18 | if updating from Circuits.GPIO v1.x. 19 | 20 | Simple example: 21 | 22 | ```elixir 23 | # GPIO 2 is connected to GPIO 3 24 | iex> {:ok, my_output_gpio} = Circuits.GPIO.open({"gpiochip0", 2}, :output) 25 | iex> {:ok, my_input_gpio} = Circuits.GPIO.open({"gpiochip0", 3}, :input) 26 | iex> Circuits.GPIO.write(my_output_gpio, 1) 27 | :ok 28 | iex> Circuits.GPIO.read(my_input_gpio) 29 | 1 30 | iex> Circuits.GPIO.close(my_output_gpio) 31 | iex> Circuits.GPIO.close(my_input_gpio) 32 | ``` 33 | """ 34 | alias Circuits.GPIO.Handle 35 | 36 | require Logger 37 | 38 | @typedoc """ 39 | Backends specify an implementation of a Circuits.GPIO.Backend behaviour 40 | 41 | The second parameter of the Backend 2-tuple is a list of options. These are 42 | passed to the behaviour function call implementations. 43 | """ 44 | @type backend() :: {module(), keyword()} 45 | 46 | @typedoc """ 47 | GPIO controller 48 | 49 | GPIO controllers manage one or more GPIO lines. They're referred to by 50 | strings. For example, controllers are named `"gpiochip0"`, etc. for the 51 | Linux cdev backend. Other backends may have similar conventions or use 52 | the empty string if there's only one controller. 53 | """ 54 | @type controller() :: String.t() 55 | 56 | @typedoc """ 57 | GPIO line offset on a controller 58 | 59 | GPIOs are numbered based on how they're connected to a controller. The 60 | details are controller specific, but usually the first one is `0`, then `1`, 61 | etc. 62 | """ 63 | @type line_offset() :: non_neg_integer() 64 | 65 | @typedoc """ 66 | A GPIO controller label or GPIO label 67 | 68 | Labels provide aliases for GPIO lines and controllers. They're 69 | system-specific. On Linux, labels are provided in device tree files. 70 | """ 71 | @type label() :: String.t() 72 | 73 | @typedoc """ 74 | An identifier for a GPIO 75 | 76 | Call `Circuits.GPIO.enumerate/0` to see what GPIOs are available on your 77 | device. Several ways exist to refer to GPIOs due to variations in devices and 78 | programmer preference. Most Raspberry Pi models have labels like `"GPIO26"`. 79 | The Raspberry Pi 5 has labels based on physical location (e.g., `"PIN37"` for 80 | GPIO 26.) 81 | 82 | Options: 83 | 84 | 1. `index` - Many examples exist where GPIOs are referred to by a GPIO 85 | number. There are issues with this strategy since GPIO indices can change. 86 | It is so common that it's still supported. Prefer other ways when you're 87 | able to change code. 88 | 2. `{controller_name, line_offset}` - Specify a line on a specific GPIO 89 | controller. E.g., `{"gpiochip0", 10}` 90 | 3. `label` - Specify a GPIO label. The first controller that has a 91 | matching GPIO is used. This lets you move the mapping of GPIOs to 92 | peripheral connections to a device tree file or other central place. E.g., 93 | `"LED_ENABLE"` 94 | 4. `{controller_name, label}` - Specify both GPIO controller and GPIO labels. 95 | E.g., `{"gpiochip4", "PIO4"}` 96 | """ 97 | @type gpio_spec() :: 98 | non_neg_integer() | {controller(), line_offset()} | label() | {controller(), label()} 99 | 100 | @typedoc "The GPIO direction (input or output)" 101 | @type direction() :: :input | :output 102 | 103 | @typedoc "GPIO logic value (low = 0 or high = 1)" 104 | @type value() :: 0 | 1 105 | 106 | @typedoc "Trigger edge for pin change notifications" 107 | @type trigger() :: :rising | :falling | :both | :none 108 | 109 | @typedoc "Pull mode for platforms that support controllable pullups and pulldowns" 110 | @type pull_mode() :: :not_set | :none | :pullup | :pulldown 111 | 112 | @typedoc """ 113 | Ways of referring to a GPIO 114 | 115 | It's possible to refer to a GPIOs in many ways and this map contains 116 | information for doing that. See `enumerate/1` and `identifiers/1` for 117 | querying `Circuits.GPIO` for these maps. 118 | 119 | The information in this map is backend specific. At a minimum, all backends 120 | provide the `:location` field which is an unambiguous `t:gpio_spec/0` for use 121 | with `open/3`. 122 | 123 | When provided, the `:label` field is a string name for the GPIO that should 124 | be unique to the system but this isn't guaranteed. A common convention is to 125 | label GPIOs by their pin names in documentation or net names in schematics. 126 | The Linux cdev backend uses labels from the device tree file. 127 | 128 | Fields: 129 | 130 | * `:location` - this is the canonical gpio_spec for a GPIO. 131 | * `:label` - an optional label for the GPIO that may indicate what the GPIO is connected to 132 | or be more helpful that the `:location`. It may be passed to `GPIO.open/3`. 133 | * `:controller` - the name or an alias for the GPIO controller. Empty string if unused 134 | """ 135 | @type identifiers() :: %{ 136 | location: {controller(), non_neg_integer()}, 137 | controller: controller(), 138 | label: label() 139 | } 140 | 141 | @typedoc """ 142 | Dynamic GPIO configuration and status 143 | 144 | Fields: 145 | 146 | * `:consumer` - if this GPIO is in use, this optional string gives a hint as to who is 147 | using it. 148 | * `:direction` - whether this GPIO is an input or output 149 | * `:pull_mode` - if this GPIO is an input, then this is the pull mode 150 | """ 151 | @type status() :: %{ 152 | consumer: String.t(), 153 | direction: direction(), 154 | pull_mode: pull_mode() 155 | } 156 | 157 | @typedoc """ 158 | Options for `open/3` 159 | 160 | * `:initial_value` - the initial value of an output GPIO 161 | * `:pull_mode` - the initial pull mode for an input GPIO 162 | * `:force_enumeration` - Linux cdev-specific option to force a scan of 163 | available GPIOs rather than using the cache. This is only for test purposes 164 | since the GPIO cache should refresh as needed. 165 | """ 166 | @type open_options() :: [ 167 | initial_value: value(), 168 | pull_mode: pull_mode(), 169 | force_enumeration: boolean() 170 | ] 171 | 172 | @typedoc """ 173 | Options for `set_interrupt/2` 174 | """ 175 | @type interrupt_options() :: [suppress_glitches: boolean(), receiver: pid() | atom()] 176 | 177 | @doc """ 178 | Guard version of `gpio_spec?/1` 179 | 180 | Add `require Circuits.GPIO` to your source file to use this guard. 181 | """ 182 | defguard is_gpio_spec(x) 183 | when (is_tuple(x) and is_binary(elem(x, 0)) and 184 | (is_binary(elem(x, 1)) or is_integer(elem(x, 1)))) or is_binary(x) or 185 | is_integer(x) 186 | 187 | @doc """ 188 | Return if a term looks like a `gpio_spec` 189 | 190 | This function only verifies that the term has the right shape to be a 191 | `t:gpio_spec/0`. Whether or not it refers to a usable GPIO is checked by 192 | `Circuits.GPIO.open/3`. 193 | """ 194 | @spec gpio_spec?(any) :: boolean() 195 | def gpio_spec?(x), do: is_gpio_spec(x) 196 | 197 | @doc """ 198 | Return identifying information about a GPIO 199 | 200 | See `t:gpio_spec/0` for the ways of referring to GPIOs. If the GPIO is found, 201 | this function returns information about the GPIO. 202 | """ 203 | @spec identifiers(gpio_spec()) :: {:ok, identifiers()} | {:error, atom()} 204 | def identifiers(gpio_spec) do 205 | {backend, backend_defaults} = default_backend() 206 | 207 | backend.identifiers(gpio_spec, backend_defaults) 208 | end 209 | 210 | @doc """ 211 | Return dynamic configuration and status information about a GPIO 212 | 213 | See `t:gpio_spec/0` for the ways of referring to GPIOs. If the GPIO is found, 214 | this function returns information about the GPIO. 215 | """ 216 | @spec status(gpio_spec()) :: {:ok, status()} | {:error, atom()} 217 | def status(gpio_spec) do 218 | {backend, backend_defaults} = default_backend() 219 | 220 | backend.status(gpio_spec, backend_defaults) 221 | end 222 | 223 | @doc """ 224 | Open a GPIO 225 | 226 | See `t:gpio_spec/0` for the ways of referring to GPIOs. Set `direction` to 227 | either `:input` or `:output`. If opening as an output, then be sure to set 228 | the `:initial_value` option to minimize the time the GPIO is in the default 229 | state. 230 | 231 | If you're having trouble, see `enumerate/0` for available GPIOs. If you 232 | suspect a hardware or driver issue, see `Circuits.GPIO.Diagnostics`. 233 | 234 | Options: 235 | 236 | * :initial_value - Set to `0` or `1`. Only used for outputs. Defaults to `0`. 237 | * :pull_mode - Set to `:not_set`, `:pullup`, `:pulldown`, or `:none` for an 238 | input pin. `:not_set` is the default. 239 | 240 | Returns `{:ok, handle}` on success. 241 | """ 242 | @spec open(gpio_spec() | identifiers(), direction(), open_options()) :: 243 | {:ok, Handle.t()} | {:error, atom()} 244 | def open(gpio_spec_or_line_info, direction, options \\ []) 245 | 246 | def open(%{location: gpio_spec}, direction, options) do 247 | open(gpio_spec, direction, options) 248 | end 249 | 250 | def open(gpio_spec, direction, options) do 251 | check_gpio_spec!(gpio_spec) 252 | check_direction!(direction) 253 | check_options!(options) 254 | 255 | {backend, backend_defaults} = default_backend() 256 | 257 | all_options = 258 | backend_defaults 259 | |> Keyword.merge(options) 260 | |> Keyword.put_new(:initial_value, 0) 261 | |> Keyword.put_new(:pull_mode, :not_set) 262 | 263 | backend.open(gpio_spec, direction, all_options) 264 | end 265 | 266 | defp check_gpio_spec!(gpio_spec) do 267 | if not gpio_spec?(gpio_spec) do 268 | raise ArgumentError, "Invalid GPIO spec: #{inspect(gpio_spec)}" 269 | end 270 | end 271 | 272 | defp check_direction!(direction) do 273 | if direction not in [:input, :output] do 274 | raise ArgumentError, 275 | "Invalid direction: #{inspect(direction)}. Options are :input or :output" 276 | end 277 | end 278 | 279 | defp check_options!([]), do: :ok 280 | 281 | defp check_options!([{:initial_value, value} | rest]) do 282 | case value do 283 | 0 -> :ok 284 | 1 -> :ok 285 | :not_set -> Logger.warning("Circuits.GPIO no longer supports :not_set for :initial_value") 286 | _ -> raise ArgumentError, ":initial_value should be :not_set, 0, or 1" 287 | end 288 | 289 | check_options!(rest) 290 | end 291 | 292 | defp check_options!([{:pull_mode, value} | rest]) do 293 | unless value in [:not_set, :pullup, :pulldown, :none], 294 | do: raise(ArgumentError, ":pull_mode should be :not_set, :pullup, :pulldown, or :none") 295 | 296 | check_options!(rest) 297 | end 298 | 299 | defp check_options!([_unknown_option | rest]) do 300 | # Ignore unknown options - the backend might use them 301 | check_options!(rest) 302 | end 303 | 304 | @doc """ 305 | Release the resources associated with a GPIO 306 | 307 | This is optional. The garbage collector will free GPIO resources that aren't 308 | in use, but this will free them sooner. 309 | """ 310 | @spec close(Handle.t()) :: :ok 311 | defdelegate close(handle), to: Handle 312 | 313 | @doc """ 314 | Read a GPIO's value 315 | 316 | The value returned for GPIO's that are configured as outputs is undefined. 317 | Backends may choose not to support this. 318 | """ 319 | @spec read(Handle.t()) :: value() 320 | defdelegate read(handle), to: Handle 321 | 322 | @doc """ 323 | One line GPIO read 324 | 325 | This is a convenience function that opens, reads, and closes a GPIO. It's 326 | intended to simplify one-off reads in code and for IEx prompt use. 327 | 328 | Prefer using handles in other situations. 329 | """ 330 | @spec read_one(gpio_spec(), open_options()) :: value() | {:error, atom()} 331 | def read_one(gpio_spec, options \\ []) do 332 | with {:ok, handle} <- open(gpio_spec, :input, options), 333 | value <- read(handle) do 334 | :ok = close(handle) 335 | value 336 | end 337 | end 338 | 339 | @doc """ 340 | Set the value of a GPIO 341 | 342 | The GPIO must be configured as an output. 343 | """ 344 | @spec write(Handle.t(), value()) :: :ok 345 | defdelegate write(handle, value), to: Handle 346 | 347 | @doc """ 348 | One line GPIO write 349 | 350 | This is a convenience function that opens, writes, and closes a GPIO. It's 351 | intended to simplify one-off writes in code and for IEx prompt use. 352 | 353 | Prefer using handles in other situations. 354 | """ 355 | @spec write_one(gpio_spec(), value(), open_options()) :: :ok | {:error, atom()} 356 | def write_one(gpio_spec, value, options \\ []) do 357 | with {:ok, handle} <- open(gpio_spec, :output, options), 358 | :ok <- write(handle, value) do 359 | :ok = close(handle) 360 | end 361 | end 362 | 363 | @doc """ 364 | Enable or disable GPIO value change notifications 365 | 366 | Notifications are sent based on the trigger: 367 | 368 | * `:none` - No notifications are sent 369 | * `:rising` - Send a notification when the pin changes from 0 to 1 370 | * `:falling` - Send a notification when the pin changes from 1 to 0 371 | * `:both` - Send a notification on all changes 372 | 373 | Available Options: 374 | * `:suppress_glitches` - Not supported in Circuits.GPIO v2 375 | * `:receiver` - Process which should receive the notifications. 376 | Defaults to the calling process (`self()`) 377 | 378 | Notification messages look like: 379 | 380 | ``` 381 | {:circuits_gpio, gpio_spec, timestamp, value} 382 | ``` 383 | 384 | Where `gpio_spec` is the `t:gpio_spec/0` passed to `open/3`, `timestamp` is an OS 385 | monotonic timestamp in nanoseconds, and `value` is the new value. 386 | 387 | Timestamps are not necessarily the same as from `System.monotonic_time/0`. 388 | For example, with the cdev backend, they're applied by the Linux kernel or 389 | can be come from a hardware timer. Erlang's monotonic time is adjusted so 390 | it's not the same as OS monotonic time. The result is that these timestamps 391 | can be compared with each other, but not with anything else. 392 | 393 | NOTE: You will need to store the `Circuits.GPIO` reference somewhere (like 394 | your `GenServer`'s state) so that it doesn't get garbage collected. Event 395 | messages stop when it gets collected. If you only get one message and you are 396 | expecting more, this is likely the case. 397 | """ 398 | @spec set_interrupts(Handle.t(), trigger(), interrupt_options()) :: :ok | {:error, atom()} 399 | defdelegate set_interrupts(handle, trigger, options \\ []), to: Handle 400 | 401 | @doc """ 402 | Change the direction of the pin 403 | """ 404 | @spec set_direction(Handle.t(), direction()) :: :ok | {:error, atom()} 405 | defdelegate set_direction(handle, direction), to: Handle 406 | 407 | @doc """ 408 | Enable or disable an internal pull-up or pull-down resistor 409 | """ 410 | @spec set_pull_mode(Handle.t(), pull_mode()) :: :ok | {:error, atom()} 411 | defdelegate set_pull_mode(gpio, pull_mode), to: Handle 412 | 413 | @doc """ 414 | Return info about the low level GPIO interface 415 | 416 | This may be helpful when debugging issues. 417 | """ 418 | @spec backend_info(backend() | nil) :: map() 419 | def backend_info(backend \\ nil) 420 | 421 | def backend_info(nil), do: backend_info(default_backend()) 422 | def backend_info({backend, _options}), do: backend.backend_info() 423 | 424 | @doc """ 425 | Return a list of accessible GPIOs 426 | 427 | Each GPIO is described in a `t:identifiers/0` map. Some fields in the map like 428 | `:location` and `:label` may be passed to `open/3` to use the GPIO. The map 429 | itself can also be passed to `open/3` and the function will figure out how to 430 | access the GPIO. 431 | """ 432 | @spec enumerate(backend() | nil) :: [identifiers()] 433 | def enumerate(backend \\ nil) 434 | def enumerate(nil), do: enumerate(default_backend()) 435 | def enumerate({backend, options}), do: backend.enumerate(options) 436 | 437 | defp default_backend() do 438 | case Application.get_env(:circuits_gpio, :default_backend) do 439 | nil -> {Circuits.GPIO.NilBackend, []} 440 | m when is_atom(m) -> {m, []} 441 | {m, o} = value when is_atom(m) and is_list(o) -> value 442 | end 443 | end 444 | end 445 | -------------------------------------------------------------------------------- /lib/gpio/backend.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Frank Hunleth 2 | # SPDX-FileCopyrightText: 2024 Connor Rigby 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | defmodule Circuits.GPIO.Backend do 7 | @moduledoc """ 8 | Backends provide the connection to the real or virtual GPIO controllers 9 | """ 10 | alias Circuits.GPIO 11 | alias Circuits.GPIO.Handle 12 | 13 | @doc """ 14 | Return a list of GPIOs 15 | 16 | See `t:GPIO.identifiers/0` for the information that is returned. The `options` contain 17 | backend-specific options to help with enumeration. 18 | """ 19 | @callback enumerate(options :: GPIO.open_options()) :: [GPIO.identifiers()] 20 | 21 | @doc """ 22 | Return identifying information about a GPIO 23 | 24 | See `t:gpio_spec/0` for the ways of referring to GPIOs. The `options` contain 25 | backend-specific options to help enumerating GPIOs. 26 | 27 | If the GPIO is found, this function returns identifying information about the GPIO. 28 | """ 29 | @callback identifiers( 30 | gpio_spec :: GPIO.gpio_spec(), 31 | options :: GPIO.open_options() 32 | ) :: {:ok, GPIO.identifiers()} | {:error, atom()} 33 | 34 | @doc """ 35 | Return a GPIO's current status 36 | 37 | This function returns how a GPIO is configured. The GPIO doesn't need to be 38 | opened. It's different from `gpio_identifiers/2` since it returns dynamic information 39 | whereas `gpio_identifiers/2` only returns information about how to refer to a GPIO 40 | and where it exists in the system. 41 | 42 | See `t:gpio_spec/0` for the ways of referring to GPIOs. The `options` contain 43 | backend-specific options to help enumerating GPIOs. 44 | 45 | If the GPIO is found, this function returns its status. 46 | """ 47 | @callback status( 48 | gpio_spec :: GPIO.gpio_spec(), 49 | options :: GPIO.open_options() 50 | ) :: {:ok, GPIO.status()} | {:error, atom()} 51 | 52 | @doc """ 53 | Open a GPIO 54 | 55 | See `t:gpio_spec/0` for the ways of referring to GPIOs. Set `direction` to 56 | either `:input` or `:output`. If opening as an output, then be sure to set 57 | the `:initial_value` option to minimize the time the GPIO is in the default 58 | state. 59 | 60 | Options: 61 | 62 | * :initial_value - Set to `0` or `1`. Only used for outputs. Defaults to `0`. 63 | * :pull_mode - Set to `:not_set`, `:pullup`, `:pulldown`, or `:none` for an 64 | input pin. `:not_set` is the default. 65 | 66 | Returns `{:ok, handle}` on success. 67 | """ 68 | @callback open( 69 | gpio_spec :: GPIO.gpio_spec(), 70 | direction :: GPIO.direction(), 71 | options :: GPIO.open_options() 72 | ) :: 73 | {:ok, Handle.t()} | {:error, atom()} 74 | 75 | @doc """ 76 | Return information about this backend 77 | """ 78 | @callback backend_info() :: map() 79 | end 80 | -------------------------------------------------------------------------------- /lib/gpio/cdev.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Connor Rigby 2 | # SPDX-FileCopyrightText: 2023 Frank Hunleth 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | defmodule Circuits.GPIO.CDev do 7 | @moduledoc """ 8 | Circuits.GPIO backend that uses the Linux CDev for controlling GPIOs 9 | 10 | This is the default on Linux and Nerves. Nothing needs to be done to 11 | use it on those platforms. If you need to be explicit, here's the 12 | configuration to force it: 13 | 14 | ```elixir 15 | config :circuits_gpio, default_backend: Circuits.GPIO.CDev 16 | ``` 17 | 18 | It takes one option, `:test`, that can be set to `true` to compile 19 | the stub implementation that can be useful for testing. 20 | 21 | ```elixir 22 | config :circuits_gpio, default_backend: {Circuits.GPIO.CDev, test: true} 23 | ``` 24 | """ 25 | @behaviour Circuits.GPIO.Backend 26 | 27 | alias Circuits.GPIO.Backend 28 | alias Circuits.GPIO.Handle 29 | alias Circuits.GPIO.Nif 30 | 31 | defstruct [:ref] 32 | 33 | @impl Backend 34 | def enumerate(options) do 35 | cached = :persistent_term.get(__MODULE__, []) 36 | 37 | if cached == [] or options[:force_enumeration] do 38 | results = Nif.enumerate() 39 | :persistent_term.put(__MODULE__, results) 40 | results 41 | else 42 | cached 43 | end 44 | end 45 | 46 | defp find_by_index(gpios, index), do: Enum.at(gpios, index) 47 | 48 | defp find_by_tuple(gpios, {controller, label_or_index}) do 49 | Enum.find(gpios, fn 50 | %{location: {^controller, _}, label: ^label_or_index} -> true 51 | %{controller: ^controller, label: ^label_or_index} -> true 52 | %{location: {^controller, ^label_or_index}} -> true 53 | %{controller: ^controller, location: {_, ^label_or_index}} -> true 54 | _ -> false 55 | end) 56 | end 57 | 58 | defp find_by_label(gpios, label) do 59 | Enum.find(gpios, fn 60 | %{label: ^label} -> true 61 | _ -> false 62 | end) 63 | end 64 | 65 | defp retry_find(options, find_fun) do 66 | info = 67 | find_fun.(enumerate(options)) || 68 | find_fun.(enumerate([{:force_enumeration, true} | options])) 69 | 70 | if info, do: {:ok, info}, else: {:error, :not_found} 71 | end 72 | 73 | @impl Backend 74 | def identifiers(number, options) when is_integer(number) and number >= 0 do 75 | retry_find(options, &find_by_index(&1, number)) 76 | end 77 | 78 | def identifiers(line_label, options) when is_binary(line_label) do 79 | retry_find(options, &find_by_label(&1, line_label)) 80 | end 81 | 82 | def identifiers(tuple_spec, options) 83 | when is_tuple(tuple_spec) and tuple_size(tuple_spec) == 2 do 84 | retry_find(options, &find_by_tuple(&1, tuple_spec)) 85 | end 86 | 87 | def identifiers(_gpio_spec, _options) do 88 | {:error, :not_found} 89 | end 90 | 91 | @impl Backend 92 | def status(gpio_spec, options \\ []) do 93 | with {:ok, location} <- find_location(gpio_spec, options) do 94 | resolved_location = resolve_gpiochip(location) 95 | Nif.status(resolved_location) 96 | end 97 | end 98 | 99 | # Handle special case that doesn't require a lookup 100 | defp find_location({"gpiochip" <> _, line} = gpio_spec, _options) when is_integer(line) do 101 | {:ok, gpio_spec} 102 | end 103 | 104 | defp find_location(gpio_spec, options) do 105 | with {:ok, ids} <- identifiers(gpio_spec, options) do 106 | {:ok, ids.location} 107 | end 108 | end 109 | 110 | defp resolve_gpiochip({controller, line}) do 111 | in_slash_dev = Path.expand(controller, "/dev") 112 | 113 | if File.exists?(in_slash_dev), 114 | do: {in_slash_dev, line}, 115 | else: {controller, line} 116 | end 117 | 118 | @impl Backend 119 | def open(gpio_spec, direction, options) do 120 | value = Keyword.fetch!(options, :initial_value) 121 | pull_mode = Keyword.fetch!(options, :pull_mode) 122 | 123 | with {:ok, location} <- find_location(gpio_spec, options), 124 | resolved_location = resolve_gpiochip(location), 125 | {:ok, ref} <- Nif.open(gpio_spec, resolved_location, direction, value, pull_mode) do 126 | {:ok, %__MODULE__{ref: ref}} 127 | end 128 | end 129 | 130 | @impl Backend 131 | def backend_info() do 132 | Nif.backend_info() 133 | end 134 | 135 | defimpl Handle do 136 | @impl Handle 137 | def read(%Circuits.GPIO.CDev{ref: ref}) do 138 | Nif.read(ref) 139 | end 140 | 141 | @impl Handle 142 | def write(%Circuits.GPIO.CDev{ref: ref}, value) do 143 | Nif.write(ref, value) 144 | end 145 | 146 | @impl Handle 147 | def set_direction(%Circuits.GPIO.CDev{ref: ref}, direction) do 148 | Nif.set_direction(ref, direction) 149 | end 150 | 151 | @impl Handle 152 | def set_pull_mode(%Circuits.GPIO.CDev{ref: ref}, pull_mode) do 153 | Nif.set_pull_mode(ref, pull_mode) 154 | end 155 | 156 | @impl Handle 157 | def set_interrupts(%Circuits.GPIO.CDev{ref: ref}, trigger, options) do 158 | suppress_glitches = Keyword.get(options, :suppress_glitches, true) 159 | 160 | receiver = 161 | case Keyword.get(options, :receiver) do 162 | pid when is_pid(pid) -> pid 163 | name when is_atom(name) -> Process.whereis(name) || self() 164 | _ -> self() 165 | end 166 | 167 | Nif.set_interrupts(ref, trigger, suppress_glitches, receiver) 168 | end 169 | 170 | @impl Handle 171 | def close(%Circuits.GPIO.CDev{ref: ref}) do 172 | Nif.close(ref) 173 | end 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /lib/gpio/diagnostics.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Frank Hunleth 2 | # SPDX-FileCopyrightText: 2024 Connor Rigby 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | defmodule Circuits.GPIO.Diagnostics do 7 | @moduledoc """ 8 | Runtime diagnostics 9 | 10 | This module provides simple diagnostics to verify GPIO controller and 11 | implementation differences. Along with the device that you're using, this is 12 | super helpful for diagnosing issues since some GPIO features aren't supposed 13 | to work on some devices. 14 | """ 15 | alias Circuits.GPIO 16 | 17 | @doc """ 18 | Reminder for how to use report/2 19 | """ 20 | @spec report() :: String.t() 21 | def report() do 22 | "Externally connect two GPIOs. Pass the gpio_specs for each to report/2." 23 | end 24 | 25 | @doc """ 26 | Print a summary of the GPIO diagnostics 27 | 28 | Connect the pins referred to by `out_gpio_spec` and `in_gpio_spec` together. 29 | When using the cdev stub implementation, any pair of GPIOs can be used. For 30 | example, run: 31 | 32 | ```elixir 33 | Circuits.GPIO.Diagnostics.report({"gpiochip0", 0}, {"gpiochip0", 1}) 34 | ``` 35 | 36 | This function is intended for IEx prompt usage. See `run/2` for programmatic 37 | use. 38 | """ 39 | @spec report(GPIO.gpio_spec(), GPIO.gpio_spec()) :: boolean 40 | def report(out_gpio_spec, in_gpio_spec) do 41 | {:ok, out_identifiers} = GPIO.identifiers(out_gpio_spec) 42 | {:ok, in_identifiers} = GPIO.identifiers(in_gpio_spec) 43 | results = run(out_gpio_spec, in_gpio_spec) 44 | passed = Enum.all?(results, fn {_, result} -> result == :ok end) 45 | check_connections? = hd(results) != {"Simple writes and reads work", :ok} 46 | speed_results = speed_test(out_gpio_spec) 47 | 48 | [ 49 | """ 50 | Circuits.GPIO Diagnostics #{Application.spec(:circuits_gpio)[:vsn]} 51 | 52 | Output GPIO: #{inspect(out_gpio_spec)} 53 | Input GPIO: #{inspect(in_gpio_spec)} 54 | 55 | Output ids: #{inspect(out_identifiers)} 56 | Input ids: #{inspect(in_identifiers)} 57 | Backend: #{inspect(Circuits.GPIO.backend_info()[:name])} 58 | 59 | """, 60 | Enum.map(results, &pass_text/1), 61 | """ 62 | 63 | write/2: #{round(speed_results.write_cps)} calls/s 64 | read/1: #{round(speed_results.read_cps)} calls/s 65 | write_one/3: #{round(speed_results.write_one_cps)} calls/s 66 | read_one/2: #{round(speed_results.read_one_cps)} calls/s 67 | 68 | """, 69 | if(check_connections?, 70 | do: [ 71 | :red, 72 | "Check that the pins are physically connected and the gpio_specs are correct.\n", 73 | :reset 74 | ], 75 | else: [] 76 | ), 77 | if(passed, do: "All tests passed", else: "Failed") 78 | ] 79 | |> IO.ANSI.format() 80 | |> IO.puts() 81 | 82 | passed 83 | end 84 | 85 | defp pass_text({name, :ok}), do: [name, ": ", :green, "PASSED", :reset, "\n"] 86 | 87 | defp pass_text({name, {:error, reason}}), 88 | do: [name, ": ", :red, "FAILED", :reset, " ", reason, "\n"] 89 | 90 | @doc """ 91 | Run GPIO tests and return a list of the results 92 | """ 93 | @spec run(GPIO.gpio_spec(), GPIO.gpio_spec()) :: list() 94 | def run(out_gpio_spec, in_gpio_spec) do 95 | tests = [ 96 | {"Simple writes and reads work", &check_reading_and_writing/3, []}, 97 | {"Can set 0 on open", &check_setting_initial_value/3, value: 0}, 98 | {"Can set 1 on open", &check_setting_initial_value/3, value: 1}, 99 | {"Input interrupts sent", &check_interrupts/3, []}, 100 | {"Interrupt timing sane", &check_interrupt_timing/3, []}, 101 | {"Internal pullup works", &check_pullup/3, []}, 102 | {"Internal pulldown works", &check_pulldown/3, []} 103 | ] 104 | 105 | tests 106 | |> Enum.map(&check(&1, out_gpio_spec, in_gpio_spec)) 107 | end 108 | 109 | @doc """ 110 | Run GPIO API performance tests 111 | 112 | Disclaimer: There should be a better way than relying on the Circuits.GPIO 113 | write performance on nearly every device. Write performance shouldn't be 114 | terrible, though. 115 | """ 116 | @spec speed_test(GPIO.gpio_spec()) :: %{ 117 | write_cps: float(), 118 | read_cps: float(), 119 | write_one_cps: float(), 120 | read_one_cps: float() 121 | } 122 | def speed_test(gpio_spec) do 123 | times = 1000 124 | one_times = ceil(times / 100) 125 | 126 | {:ok, gpio} = GPIO.open(gpio_spec, :output) 127 | write_cps = time_fun2(times, &write2/1, gpio) 128 | GPIO.close(gpio) 129 | 130 | {:ok, gpio} = GPIO.open(gpio_spec, :input) 131 | read_cps = time_fun2(times, &read2/1, gpio) 132 | GPIO.close(gpio) 133 | 134 | write_one_cps = time_fun2(one_times, &write_one2/1, gpio_spec) 135 | read_one_cps = time_fun2(one_times, &read_one2/1, gpio_spec) 136 | 137 | %{ 138 | write_cps: write_cps, 139 | read_cps: read_cps, 140 | write_one_cps: write_one_cps, 141 | read_one_cps: read_one_cps 142 | } 143 | end 144 | 145 | defp time_fun2(times, fun, arg) do 146 | # Check that it works 147 | _ = fun.(arg) 148 | 149 | # Benchmark it 150 | {micros, :ok} = :timer.tc(fn -> Enum.each(1..times, fn _ -> fun.(arg) end) end) 151 | times / micros * 1_000_000 * 2 152 | end 153 | 154 | defp write2(gpio) do 155 | GPIO.write(gpio, 0) 156 | GPIO.write(gpio, 1) 157 | end 158 | 159 | defp read2(gpio) do 160 | GPIO.read(gpio) 161 | GPIO.read(gpio) 162 | end 163 | 164 | defp write_one2(gpio_spec) do 165 | _ = GPIO.write_one(gpio_spec, 0) 166 | _ = GPIO.write_one(gpio_spec, 1) 167 | end 168 | 169 | defp read_one2(gpio_spec) do 170 | _ = GPIO.read_one(gpio_spec) 171 | _ = GPIO.read_one(gpio_spec) 172 | end 173 | 174 | defmacrop assert(expr) do 175 | line = __CALLER__.line 176 | 177 | quote do 178 | unless unquote(expr) do 179 | raise "#{unquote(line)}: Assertion failed: #{unquote(Macro.to_string(expr))}" 180 | end 181 | end 182 | end 183 | 184 | defmacrop assert_receive(expected, timeout \\ 500) do 185 | quote do 186 | receive do 187 | unquote(expected) = x -> 188 | x 189 | after 190 | unquote(timeout) -> 191 | raise "Expected message not received within #{unquote(timeout)}ms: #{unquote(Macro.to_string(expected))}" 192 | end 193 | end 194 | end 195 | 196 | defmacrop refute_receive(unexpected, timeout \\ 50) do 197 | quote do 198 | receive do 199 | unquote(unexpected) -> 200 | raise "Should not have received message within #{unquote(timeout)}ms: #{unquote(Macro.to_string(unexpected))}" 201 | after 202 | unquote(timeout) -> :ok 203 | end 204 | end 205 | end 206 | 207 | defp check({name, test, options}, out_gpio_spec, in_gpio_spec) do 208 | # Run the tests in a process so that GPIO handles get cleaned up and the process 209 | # mailbox is empty for interrupt checks. 210 | t = Task.async(fn -> safe_check(test, out_gpio_spec, in_gpio_spec, options) end) 211 | 212 | {name, Task.await(t)} 213 | rescue 214 | e -> 215 | {name, {:error, Exception.message(e)}} 216 | end 217 | 218 | defp safe_check(test, out_gpio_spec, in_gpio_spec, options) do 219 | test.(out_gpio_spec, in_gpio_spec, options) 220 | rescue 221 | e -> {:error, Exception.message(e)} 222 | end 223 | 224 | @doc false 225 | @spec check_reading_and_writing(GPIO.gpio_spec(), GPIO.gpio_spec(), keyword()) :: :ok 226 | def check_reading_and_writing(out_gpio_spec, in_gpio_spec, _options) do 227 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :output) 228 | {:ok, in_gpio} = GPIO.open(in_gpio_spec, :input) 229 | 230 | GPIO.write(out_gpio, 0) 231 | assert GPIO.read(in_gpio) == 0 232 | 233 | GPIO.write(out_gpio, 1) 234 | assert GPIO.read(in_gpio) == 1 235 | 236 | GPIO.write(out_gpio, 0) 237 | assert GPIO.read(in_gpio) == 0 238 | 239 | GPIO.close(out_gpio) 240 | GPIO.close(in_gpio) 241 | end 242 | 243 | @doc false 244 | @spec check_setting_initial_value(GPIO.gpio_spec(), GPIO.gpio_spec(), keyword()) :: :ok 245 | def check_setting_initial_value(out_gpio_spec, in_gpio_spec, options) do 246 | value = options[:value] 247 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :output, initial_value: value) 248 | {:ok, in_gpio} = GPIO.open(in_gpio_spec, :input) 249 | 250 | assert GPIO.read(in_gpio) == value 251 | 252 | GPIO.close(out_gpio) 253 | GPIO.close(in_gpio) 254 | end 255 | 256 | @doc false 257 | @spec check_interrupts(GPIO.gpio_spec(), GPIO.gpio_spec(), keyword()) :: :ok 258 | def check_interrupts(out_gpio_spec, in_gpio_spec, _options) do 259 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :output, initial_value: 0) 260 | {:ok, in_gpio} = GPIO.open(in_gpio_spec, :input) 261 | 262 | :ok = GPIO.set_interrupts(in_gpio, :both) 263 | 264 | # Initial notification 265 | refute_receive {:circuits_gpio, _spec, _timestamp, _} 266 | 267 | # Toggle enough times to avoid being tricked by something 268 | # works a few times and then stops. 269 | for _ <- 1..64 do 270 | GPIO.write(out_gpio, 1) 271 | _ = assert_receive {:circuits_gpio, ^in_gpio_spec, _timestamp, 1} 272 | 273 | GPIO.write(out_gpio, 0) 274 | _ = assert_receive {:circuits_gpio, ^in_gpio_spec, _timestamp, 0} 275 | end 276 | 277 | GPIO.close(out_gpio) 278 | GPIO.close(in_gpio) 279 | end 280 | 281 | @doc false 282 | @spec check_interrupt_timing(GPIO.gpio_spec(), GPIO.gpio_spec(), keyword()) :: :ok 283 | def check_interrupt_timing(out_gpio_spec, in_gpio_spec, _options) do 284 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :output, initial_value: 0) 285 | {:ok, in_gpio} = GPIO.open(in_gpio_spec, :input) 286 | 287 | :ok = GPIO.set_interrupts(in_gpio, :both) 288 | 289 | # No notifications until something changes 290 | refute_receive {:circuits_gpio, _, _, _} 291 | 292 | GPIO.write(out_gpio, 1) 293 | {_, _, first_ns, _} = assert_receive {:circuits_gpio, ^in_gpio_spec, _, 1} 294 | 295 | GPIO.write(out_gpio, 0) 296 | {_, _, second_ns, _} = assert_receive {:circuits_gpio, ^in_gpio_spec, _, 0} 297 | 298 | # No notifications after this 299 | refute_receive {:circuits_gpio, _, _, _} 300 | 301 | GPIO.close(out_gpio) 302 | GPIO.close(in_gpio) 303 | 304 | # Check that the timestamps are ordered and not too far apart. 305 | assert first_ns < second_ns 306 | assert second_ns - first_ns < 100_000_000 307 | assert second_ns - first_ns > 100 308 | 309 | :ok 310 | end 311 | 312 | @doc false 313 | @spec check_pullup(GPIO.gpio_spec(), GPIO.gpio_spec(), keyword()) :: :ok 314 | def check_pullup(out_gpio_spec, in_gpio_spec, _options) do 315 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :output, initial_value: 0) 316 | {:ok, in_gpio} = GPIO.open(in_gpio_spec, :input, pull_mode: :pullup) 317 | 318 | # Check non-pullup case 319 | assert GPIO.read(in_gpio) == 0 320 | GPIO.write(out_gpio, 1) 321 | assert GPIO.read(in_gpio) == 1 322 | GPIO.write(out_gpio, 0) 323 | assert GPIO.read(in_gpio) == 0 324 | 325 | # Check pullup by re-opening out_gpio as an input 326 | GPIO.close(out_gpio) 327 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :input) 328 | 329 | assert GPIO.read(in_gpio) == 1 330 | 331 | GPIO.close(out_gpio) 332 | GPIO.close(in_gpio) 333 | end 334 | 335 | @doc false 336 | @spec check_pulldown(GPIO.gpio_spec(), GPIO.gpio_spec(), keyword()) :: :ok 337 | def check_pulldown(out_gpio_spec, in_gpio_spec, _options) do 338 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :output, initial_value: 1) 339 | {:ok, in_gpio} = GPIO.open(in_gpio_spec, :input, pull_mode: :pulldown) 340 | 341 | # Check non-pullup case 342 | assert GPIO.read(in_gpio) == 1 343 | GPIO.write(out_gpio, 0) 344 | assert GPIO.read(in_gpio) == 0 345 | GPIO.write(out_gpio, 1) 346 | assert GPIO.read(in_gpio) == 1 347 | 348 | # Check pulldown by re-opening out_gpio as an input 349 | GPIO.close(out_gpio) 350 | {:ok, out_gpio} = GPIO.open(out_gpio_spec, :input) 351 | 352 | assert GPIO.read(in_gpio) == 0 353 | 354 | GPIO.close(out_gpio) 355 | GPIO.close(in_gpio) 356 | end 357 | end 358 | -------------------------------------------------------------------------------- /lib/gpio/gpio_nif.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2018 Frank Hunleth 2 | # SPDX-FileCopyrightText: 2018 Mark Sebald 3 | # SPDX-FileCopyrightText: 2018 Matt Ludwigs 4 | # SPDX-FileCopyrightText: 2023 Connor Rigby 5 | # 6 | # SPDX-License-Identifier: Apache-2.0 7 | 8 | defmodule Circuits.GPIO.Nif do 9 | @moduledoc false 10 | 11 | @on_load {:load_nif, 0} 12 | @compile {:autoload, false} 13 | 14 | def load_nif() do 15 | :erlang.load_nif(:code.priv_dir(:circuits_gpio) ++ ~c"/gpio_nif", 0) 16 | end 17 | 18 | def open(_gpio_spec, _resolved_gpio_spec, _direction, _initial_value, _pull_mode), 19 | do: :erlang.nif_error(:nif_not_loaded) 20 | 21 | def close(_gpio), do: :erlang.nif_error(:nif_not_loaded) 22 | def read(_gpio), do: :erlang.nif_error(:nif_not_loaded) 23 | def write(_gpio, _value), do: :erlang.nif_error(:nif_not_loaded) 24 | 25 | def set_interrupts(_gpio, _trigger, _suppress_glitches, _process), 26 | do: :erlang.nif_error(:nif_not_loaded) 27 | 28 | def set_direction(_gpio, _direction), do: :erlang.nif_error(:nif_not_loaded) 29 | def set_pull_mode(_gpio, _pull_mode), do: :erlang.nif_error(:nif_not_loaded) 30 | def info(_gpio), do: :erlang.nif_error(:nif_not_loaded) 31 | def status(_resolved_gpio_spec), do: :erlang.nif_error(:nif_not_loaded) 32 | def backend_info(), do: :erlang.nif_error(:nif_not_loaded) 33 | def enumerate(), do: :erlang.nif_error(:nif_not_loaded) 34 | end 35 | -------------------------------------------------------------------------------- /lib/gpio/handle.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Frank Hunleth 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | defprotocol Circuits.GPIO.Handle do 6 | @moduledoc """ 7 | Handle for referring to GPIOs 8 | 9 | Use the functions in `Circuits.GPIO` to read and write to GPIOs. 10 | """ 11 | 12 | alias Circuits.GPIO 13 | 14 | # Return the current GPIO state 15 | @doc false 16 | @spec read(t()) :: GPIO.value() 17 | def read(handle) 18 | 19 | # Set the GPIO state 20 | @doc false 21 | @spec write(t(), GPIO.value()) :: :ok 22 | def write(handle, value) 23 | 24 | # Change the direction of the GPIO 25 | @doc false 26 | @spec set_direction(t(), GPIO.direction()) :: :ok | {:error, atom()} 27 | def set_direction(handle, direction) 28 | 29 | # Change the pull mode of an input GPIO 30 | @doc false 31 | @spec set_pull_mode(t(), GPIO.pull_mode()) :: :ok | {:error, atom()} 32 | def set_pull_mode(handle, mode) 33 | 34 | # Free up resources associated with the handle 35 | # 36 | # Well behaved backends free up their resources with the help of the Erlang 37 | # garbage collector. However, it is good practice for users to call 38 | # `Circuits.GPIO.close/1` (and hence this function) so that limited resources 39 | # are freed before they're needed again. 40 | @doc false 41 | @spec close(t()) :: :ok 42 | def close(handle) 43 | 44 | @doc false 45 | @spec set_interrupts(t(), GPIO.trigger(), GPIO.interrupt_options()) :: :ok | {:error, atom()} 46 | def set_interrupts(handle, trigger, options) 47 | end 48 | -------------------------------------------------------------------------------- /lib/gpio/nil_backend.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Frank Hunleth 2 | # SPDX-FileCopyrightText: 2024 Connor Rigby 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | defmodule Circuits.GPIO.NilBackend do 7 | @moduledoc """ 8 | Circuits.GPIO backend when nothing else is available 9 | """ 10 | @behaviour Circuits.GPIO.Backend 11 | 12 | alias Circuits.GPIO.Backend 13 | 14 | @impl Backend 15 | def enumerate(_options) do 16 | [] 17 | end 18 | 19 | @impl Backend 20 | def identifiers(_gpio_spec, _options) do 21 | {:error, :not_found} 22 | end 23 | 24 | @impl Backend 25 | def status(_gpio_spec, _options) do 26 | {:error, :not_found} 27 | end 28 | 29 | @impl Backend 30 | def open(_gpio_spec, _direction, _options) do 31 | {:error, :unimplemented} 32 | end 33 | 34 | @impl Backend 35 | def backend_info() do 36 | %{name: __MODULE__} 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Circuits.GPIO.MixProject do 2 | use Mix.Project 3 | 4 | @version "2.1.2" 5 | @description "Use GPIOs in Elixir" 6 | @source_url "https://github.com/elixir-circuits/circuits_gpio" 7 | 8 | def project do 9 | [ 10 | app: :circuits_gpio, 11 | version: @version, 12 | elixir: "~> 1.13", 13 | description: @description, 14 | package: package(), 15 | source_url: @source_url, 16 | compilers: [:elixir_make | Mix.compilers()], 17 | make_targets: ["all"], 18 | make_clean: ["clean"], 19 | make_env: make_env(), 20 | docs: docs(), 21 | aliases: [format: [&format_c/1, "format"]], 22 | start_permanent: Mix.env() == :prod, 23 | dialyzer: dialyzer(), 24 | deps: deps(), 25 | preferred_cli_env: %{ 26 | docs: :docs, 27 | "hex.publish": :docs, 28 | "hex.build": :docs 29 | } 30 | ] 31 | end 32 | 33 | def application do 34 | # IMPORTANT: This provides a default at runtime and at compile-time when 35 | # circuits_gpio is pulled in as a dependency. 36 | [env: [default_backend: default_backend()], extra_applications: [:logger]] 37 | end 38 | 39 | defp package do 40 | %{ 41 | files: [ 42 | "CHANGELOG.md", 43 | "c_src/*.[ch]", 44 | "c_src/linux/gpio.h", 45 | "lib", 46 | "LICENSES/*", 47 | "Makefile", 48 | "mix.exs", 49 | "NOTICE", 50 | "PORTING.md", 51 | "README.md", 52 | "REUSE.toml" 53 | ], 54 | licenses: ["Apache-2.0"], 55 | links: %{ 56 | "GitHub" => @source_url, 57 | "REUSE Compliance" => 58 | "https://api.reuse.software/info/github.com/elixir-circuits/circuits_gpio" 59 | } 60 | } 61 | end 62 | 63 | defp deps() do 64 | [ 65 | {:ex_doc, "~> 0.22", only: :docs, runtime: false}, 66 | {:credo, "~> 1.6", only: :dev, runtime: false}, 67 | {:dialyxir, "~> 1.2", only: :dev, runtime: false}, 68 | {:elixir_make, "~> 0.6", runtime: false} 69 | ] 70 | end 71 | 72 | defp dialyzer() do 73 | [ 74 | flags: [:missing_return, :extra_return, :unmatched_returns, :error_handling, :underspecs], 75 | list_unused_filters: true, 76 | plt_file: {:no_warn, "_build/plts/dialyzer.plt"} 77 | ] 78 | end 79 | 80 | defp docs do 81 | [ 82 | assets: %{"assets" => "assets"}, 83 | extras: ["README.md", "PORTING.md", "CHANGELOG.md"], 84 | main: "readme", 85 | skip_code_autolink_to: ["Circuits.GPIO.pin/1"], 86 | source_ref: "v#{@version}", 87 | source_url: @source_url 88 | ] 89 | end 90 | 91 | defp default_backend(), do: default_backend(Mix.env(), Mix.target()) 92 | defp default_backend(:test, _target), do: {Circuits.GPIO.CDev, test: true} 93 | defp default_backend(:docs, _target), do: {Circuits.GPIO.CDev, test: true} 94 | defp default_backend(:nil_test, _target), do: Circuits.GPIO.NilBackend 95 | 96 | defp default_backend(_env, :host) do 97 | case :os.type() do 98 | {:unix, :linux} -> Circuits.GPIO.CDev 99 | _ -> {Circuits.GPIO.CDev, test: true} 100 | end 101 | end 102 | 103 | # MIX_TARGET set to something besides host 104 | defp default_backend(env, _not_host) do 105 | # If CROSSCOMPILE is set, then the Makefile will use the crosscompiler and 106 | # assume a Linux/Nerves build If not, then the NIF will be build for the 107 | # host, so use the default host backend 108 | case System.fetch_env("CROSSCOMPILE") do 109 | {:ok, _} -> Circuits.GPIO.CDev 110 | :error -> default_backend(env, :host) 111 | end 112 | end 113 | 114 | defp make_env() do 115 | # Since user configuration hasn't been loaded into the application 116 | # environment when `project/1` is called, load it here for building 117 | # the NIF. 118 | backend = Application.get_env(:circuits_gpio, :default_backend, default_backend()) 119 | 120 | %{"CIRCUITS_GPIO_BACKEND" => cdev_compile_mode(backend)} 121 | end 122 | 123 | defp cdev_compile_mode({Circuits.GPIO.CDev, options}) do 124 | if Keyword.get(options, :test) do 125 | "test" 126 | else 127 | "cdev" 128 | end 129 | end 130 | 131 | defp cdev_compile_mode(Circuits.GPIO.CDev) do 132 | "cdev" 133 | end 134 | 135 | defp cdev_compile_mode(_other) do 136 | "disabled" 137 | end 138 | 139 | defp format_c([]) do 140 | case System.find_executable("astyle") do 141 | nil -> 142 | Mix.Shell.IO.info("Install astyle to format C code.") 143 | 144 | astyle -> 145 | System.cmd(astyle, ["-n", "c_src/*.c"], into: IO.stream(:stdio, :line)) 146 | end 147 | end 148 | 149 | defp format_c(_args), do: true 150 | end 151 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 3 | "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, 4 | "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, 5 | "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, 6 | "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, 7 | "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, 8 | "ex_doc": {:hex, :ex_doc, "0.37.2", "2a3aa7014094f0e4e286a82aa5194a34dd17057160988b8509b15aa6c292720c", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4dfa56075ce4887e4e8b1dcc121cd5fcb0f02b00391fd367ff5336d98fa49049"}, 9 | "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, 10 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 11 | "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, 12 | "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, 13 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, 14 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, 15 | } 16 | -------------------------------------------------------------------------------- /notebooks/basics.livemd: -------------------------------------------------------------------------------- 1 | # Hello Circuits GPIO v2 2 | 3 | ```elixir 4 | Mix.install([ 5 | {:circuits_gpio, "~> 2.0.1"}, 6 | {:kino, "~> 0.12.2"} 7 | ]) 8 | ``` 9 | 10 | ## Introduction 11 | 12 | `Circuits.GPIO` lets you use GPIOs in Elixir. In this exercise, we will 13 | familiarize ourselves with `Circuits.GPIO` v2.0. For details, see 14 | [documentation](https://hexdocs.pm/circuits_gpio). 15 | 16 | ## Supported systems 17 | 18 | While `Circuits.GPIO` can support non-Nerves and non-Linux systems, the examples 19 | below were made using Nerves. Operation on other devices mostly differs on how 20 | to refer to GPIOs. 21 | 22 | This Livebook only works on Raspberry Pi (1, 2, 3, 4, 400 and Zero families) 23 | without modification. 24 | 25 | The following examples were tested on a Raspberry Pi that was connected to an 26 | [Erlang Embedded Demo Board](http://solderpad.com/omerk/erlhwdemo/). There's 27 | nothing special about either the demo board or the Raspberry Pi. 28 | 29 | ## GPIO 30 | 31 | A [General Purpose 32 | Input/Output](https://en.wikipedia.org/wiki/General-purpose_input/output) (GPIO) 33 | is just a wire that you can use as an input or an output. It can only be one of 34 | two values, 0 or 1. A 1 corresponds to a logic high voltage like 3.3 V and a 0 35 | corresponds to 0 V. The actual voltage depends on the hardware. 36 | 37 | ## GPIO Specs 38 | 39 | `Circuits.GPIO` v2.0 supports a new form of specifying how to open a GPIO called 40 | a `t:gpio_spec/0`. These specs are very flexible and allow for GPIOs to be 41 | opened by number, a string label, or a tuple that includes both the GPIO 42 | controller hardware name and a line offset. 43 | 44 | The contents of a `gpio_spec` depend on the backend. When running on Nerves or 45 | a Linux machine, `Circuits.GPIO` uses the Linux gpio-cdev backend. This backend 46 | prefers the use of GPIO controller/line offset tuples and labels. For backwards 47 | compatibility, it somewhat supports use of the _older_ pin numbering scheme. 48 | 49 | See [Enumeration](#enumeration) for listing out all available `gpio_specs` for 50 | your device. 51 | 52 | ## Enumeration 53 | 54 | `Circuits.GPIO` v2.0 supports a new function, `enumerate/0`, which lists every 55 | known GPIO pin. 56 | 57 | For Nerves and Linux users, the `gpio-cdev` subsystem maintains the official 58 | list. See the [Official DeviceTree documentation for 59 | GPIOs](https://elixir.bootlin.com/linux/v6.6.6/source/Documentation/devicetree/bindings/gpio/gpio.txt) 60 | for more information on how to configure the fields of this struct for your own 61 | system. 62 | 63 | Here's an example: 64 | 65 | ```elixir 66 | Circuits.GPIO.enumerate() |> Kino.DataTable.new() 67 | ``` 68 | 69 | 70 | 71 | ```text 72 | [%{label: "ID_SDA", location: {"gpiochip0", 0}, controller: "pinctrl-bcm2835"}, {label: "ID_SCL", location: {"gpiochip0", 1}, controller: "pinctrl-bcm2835"}, {label: "SDA1", location: {"gpiochip0", 2}, controller: "pinctrl-bcm2835"}, {label: "SCL1", location: {"gpiochip0", 3}, controller: "pinctrl-bcm2835"}, { label: "GPIO_GCLK", location: {"gpiochip0", 4}, controller: "pinctrl-bcm2835" }, {label: "GPIO5", location: {"gpiochip0", 5}, controller: "pinctrl-bcm2835"}, {label: "GPIO6", location: {"gpiochip0", 6}, controller: "pinctrl-bcm2835"}, { label: "SPI_CE1_N", location: {"gpiochip0", 7}, controller: "pinctrl-bcm2835" }, { label: "SPI_CE0_N", location: {"gpiochip0", 8}, controller: "pinctrl-bcm2835" }, { label: "SPI_MISO", location: {"gpiochip0", 9}, controller: "pinctrl-bcm2835" }, { label: "SPI_MOSI", location: {"gpiochip0", 10}, controller: "pinctrl-bcm2835" }, { label: "SPI_SCLK", location: {"gpiochip0", 11}, controller: "pinctrl-bcm2835" }, {label: "GPIO12", location: {"gpiochip0", 12}, controller: "pinctrl-bcm2835"}, {label: "GPIO13", location: {"gpiochip0", 13}, controller: "pinctrl-bcm2835"}, {label: "TXD1", location: {"gpiochip0", 14}, controller: "pinctrl-bcm2835"}, {label: "RXD1", location: {"gpiochip0", 15}, controller: "pinctrl-bcm2835"}, {label: "GPIO16", location: {"gpiochip0", 16}, controller: "pinctrl-bcm2835"}, {label: "GPIO17", location: {"gpiochip0", 17}, controller: "pinctrl-bcm2835"}, {label: "GPIO18", location: {"gpiochip0", 18}, controller: "pinctrl-bcm2835"}, {label: "GPIO19", location: {"gpiochip0", 19}, controller: "pinctrl-bcm2835"}, {label: "GPIO20", location: {"gpiochip0", 20}, controller: "pinctrl-bcm2835"}, {label: "GPIO21", location: {"gpiochip0", 21}, controller: "pinctrl-bcm2835"}, {label: "GPIO22", location: {"gpiochip0", 22}, controller: "pinctrl-bcm2835"}, {label: "GPIO23", location: {"gpiochip0", 23}, controller: "pinctrl-bcm2835"}, {label: "GPIO24", location: {"gpiochip0", 24}, controller: "pinctrl-bcm2835"}, {label: "GPIO25", location: {"gpiochip0", 25}, controller: "pinctrl-bcm2835"}, {label: "GPIO26", location: {"gpiochip0", 26}, controller: "pinctrl-bcm2835"}, {label: "GPIO27", location: {"gpiochip0", 27}, controller: "pinctrl-bcm2835"}, {label: "SDA0", location: {"gpiochip0", 28}, controller: "pinctrl-bcm2835"}, {label: "SCL0", location: {"gpiochip0", 29}, controller: "pinctrl-bcm2835"}, {label: "CTS0", location: {"gpiochip0", 30}, controller: "pinctrl-bcm2835"}, {label: "RTS0", location: {"gpiochip0", 31}, controller: "pinctrl-bcm2835"}, {label: "TXD0", location: {"gpiochip0", 32}, controller: "pinctrl-bcm2835"}, {label: "RXD0", location: {"gpiochip0", 33}, controller: "pinctrl-bcm2835"}, { label: "SD1_CLK", location: {"gpiochip0", 34}, controller: "pinctrl-bcm2835" }, { label: "SD1_CMD", location: {"gpiochip0", 35}, controller: "pinctrl-bcm2835" }, { label: "SD1_DATA0", location: {"gpiochip0", 36}, controller: "pinctrl-bcm2835" }, { label: "SD1_DATA1", location: {"gpiochip0", 37}, controller: "pinctrl-bcm2835" }, { label: "SD1_DATA2", location: {"gpiochip0", 38}, controller: "pinctrl-bcm2835" }, { label: "SD1_DATA3", location: {"gpiochip0", 39}, controller: "pinctrl-bcm2835" }, { label: "CAM_GPIO1", location: {"gpiochip0", 40}, controller: "pinctrl-bcm2835" }, {label: "WL_ON", location: {"gpiochip0", 41}, controller: "pinctrl-bcm2835"}, {label: "NC", location: {"gpiochip0", 42}, controller: "pinctrl-bcm2835"}, { label: "WIFI_CLK", location: {"gpiochip0", 43}, controller: "pinctrl-bcm2835" }, { label: "CAM_GPIO0", location: {"gpiochip0", 44}, controller: "pinctrl-bcm2835" }, {label: "BT_ON", location: {"gpiochip0", 45}, controller: "pinctrl-bcm2835"}, { label: "HDMI_HPD_N", location: {"gpiochip0", 46}, controller: "pinctrl-bcm2835" }, { label: "STATUS_LED_N", location: {"gpiochip0", 47}, controller: "pinctrl-bcm2835" }, { label: "SD_CLK_R", location: {"gpiochip0", 48}, controller: "pinctrl-bcm2835" }, { label: "SD_CMD_R", location: {"gpiochip0", 49}, controller: "pinctrl-bcm2835" }, { label: "SD_DATA0_R", location: {"gpiochip0", 50}, controller: "pinctrl-bcm2835" }, { label: "SD_DATA1_R", location: {"gpiochip0", 51}, controller: "pinctrl-bcm2835" }, { label: "SD_DATA2_R", location: {"gpiochip0", 52}, controller: "pinctrl-bcm2835" }, { label: "SD_DATA3_R", location: {"gpiochip0", 53}, controller: "pinctrl-bcm2835"}] 73 | ``` 74 | 75 | The `:location` can always be passed as the first parameter to 76 | `Circuits.GPIO.open/3`. You may find the `:label` field more descriptive to use, 77 | though. 78 | 79 | The GPIO controller part of the `:location` tuple is usually some variation on 80 | `"gpiochip0"` that depends on what controllers are available under `/dev`. The 81 | line offset is a the line offset of the GPIO on that controller. 82 | 83 | ```elixir 84 | Path.wildcard("/dev/gpiochip*") 85 | ``` 86 | 87 | 88 | 89 | ```text 90 | ["/dev/gpiochip0"] 91 | ``` 92 | 93 | If you're deploying to multiple types of devices and you can set labels in the 94 | device tree, labels make it really easy for code using `Circuits.GPIO` to just 95 | use the right GPIO. 96 | 97 | Labels are not guaranteed to be unique, so if your device defines one twice, 98 | `Circuits.GPIO` will use the first GPIO it finds that has the specified label. 99 | 100 | ```elixir 101 | # label only 102 | {:ok, gpio} = Circuits.GPIO.open("GPIO18", :input) 103 | Circuits.GPIO.close(gpio) 104 | 105 | # location (controller_name and line_offset) 106 | {:ok, gpio} = Circuits.GPIO.open({"gpiochip0", 18}, :input) 107 | Circuits.GPIO.close(gpio) 108 | 109 | # controller_name and label 110 | {:ok, gpio} = Circuits.GPIO.open({"gpiochip0", "GPIO18"}, :input) 111 | Circuits.GPIO.close(gpio) 112 | 113 | # index only (the older pin numbering scheme) 114 | {:ok, gpio} = Circuits.GPIO.open(18, :input) 115 | Circuits.GPIO.close(gpio) 116 | ``` 117 | 118 | 119 | 120 | ``` 121 | :ok 122 | ``` 123 | 124 | When the Linux device tree is configured with GPIO labels, you can use those instead: 125 | 126 | ```elixir 127 | # {:ok, gpio} = Circuits.GPIO.open("special-name-for-pin-1") 128 | ``` 129 | 130 | 131 | 132 | ``` 133 | nil 134 | ``` 135 | 136 | ## Turning an LED on or off 137 | 138 | Here's an example of turning an LED on or off: 139 | 140 | ![GPIO LED schematic](https://github.com/elixir-circuits/circuits_gpio/raw/v2.0.1/assets/images/schematic-gpio-led.png) 141 | 142 | To turn on the LED that's connected to the net (or wire) labeled `GPIO18`, you 143 | need to open it first. The first parameter to `Circuits.GPIO.open/2` is called a 144 | GPIO spec and identifies the GPIO. The Raspberry Pis are nice and provide string 145 | names for GPIOs. Other boards are not as nice so you always have to check. The 146 | string name for this GPIO is `"GPIO18"` (use `"PIN12"` on a Raspberry Pi 5). 147 | 148 | ```elixir 149 | {:ok, gpio} = Circuits.GPIO.open("GPIO12", :output) 150 | 151 | Circuits.GPIO.write(gpio, 1) 152 | 153 | Circuits.GPIO.close(gpio) 154 | ``` 155 | 156 | 157 | 158 | ``` 159 | :ok 160 | ``` 161 | 162 | The call to `Circuits.GPIO.close/1` is not necessary, since the garbage 163 | collector will free up unreferenced GPIOs. It's a good practice, though, 164 | since backends can enforce exclusivity and prevent future opens from 165 | working until the GC occurs. 166 | 167 | Input works similarly. Here's an example of a button with a pull down resistor 168 | connected. 169 | 170 | ![GPIO Button schematic](https://github.com/elixir-circuits/circuits_gpio/raw/v2.0.1/assets/images/schematic-gpio-button.png) 171 | 172 | If you're not familiar with pull up or pull down resistors, they're resistors 173 | whose purpose is to drive a wire high or low when the button isn't pressed. In 174 | this case, it drives the wire low. Many processors have ways of configuring 175 | internal resistors to accomplish the same effect without needing to add an 176 | external resistor. If you're using a Raspberry Pi, you can use [the built-in 177 | pull-up/pull-down resistors](#internal-pull-uppull-down). 178 | 179 | The code looks like this in `Circuits.GPIO`: 180 | 181 | ```elixir 182 | {:ok, gpio} = Circuits.GPIO.open("GPIO17", :input) 183 | 184 | Circuits.GPIO.read(gpio) 185 | ``` 186 | 187 | 188 | 189 | ``` 190 | 0 191 | ``` 192 | Push the button down. 193 | 194 | ```elixir 195 | Circuits.GPIO.read(gpio) 196 | ``` 197 | 198 | 199 | 200 | ``` 201 | 1 202 | ``` 203 | 204 | If you'd like to get a message when the button is pressed or released, call the 205 | `set_interrupts` function. You can trigger on the `:rising` edge, `:falling` 206 | edge or `:both`. 207 | 208 | ```elixir 209 | Circuits.GPIO.set_interrupts(gpio, :both) 210 | 211 | IEx.Helpers.flush() 212 | ``` 213 | 214 | 215 | 216 | ``` 217 | :ok 218 | ``` 219 | 220 | Note that after calling `set_interrupts`, the calling process will receive an 221 | initial message with the state of the pin. This prevents the race condition 222 | between getting the initial state of the pin and turning on interrupts. Without 223 | it, you could get the state of the pin, it could change states, and then you 224 | could start waiting on it for interrupts. If that happened, you would be out of 225 | sync. 226 | 227 | ### Internal pull-up/pull-down 228 | 229 | To connect or disconnect an internal [pull-up or pull-down resistor](https://github.com/raspberrypilearning/physical-computing-guide/blob/master/pull_up_down.md) to a GPIO 230 | pin, call the `set_pull_mode` function. 231 | 232 | ```elixir 233 | Circuits.GPIO.set_pull_mode(gpio, :pullup) 234 | ``` 235 | 236 | 237 | 238 | ``` 239 | :ok 240 | ``` 241 | 242 | Valid `pull_mode` values are `:none` `:pullup`, or `:pulldown` 243 | 244 | Note that `set_pull_mode` is platform dependent, and currently only works for 245 | Raspberry Pi hardware. Calls to `set_pull_mode` on other platforms will have no 246 | effect. The internal pull-up resistor value is between 50K and 65K, and the 247 | pull-down is between 50K and 60K. It is not possible to read back the current 248 | Pull-up/down settings, and GPIO pull-up pull-down resistor connections are 249 | maintained, even when the CPU is powered down. 250 | 251 | ## Convenience functions 252 | 253 | Having to `open` _then_ `read` and `write` can be a cumbersome for one-off GPIO 254 | access in code and when working at the IEx prompt. Circuits v2.0 has a pair of 255 | new functions to help: 256 | 257 | 258 | ```elixir 259 | Circuits.GPIO.write_one("special-name-for-pin-1", 1) 260 | ``` 261 | 262 | 263 | 264 | ``` 265 | :ok 266 | ``` 267 | 268 | ```elixir 269 | Circuits.GPIO.read_one("special-name-for-pin-2") 270 | ``` 271 | 272 | 273 | 274 | ``` 275 | 1 276 | ``` 277 | 278 | These functions get passed a `t:gpio_spec/0` just like `open/3` and internally 279 | open the GPIO and read or write it. Importantly, they `close` the GPIO when done 280 | to avoid reserving the GPIO any longer than necessary. 281 | 282 | Please note that this is not a performant way of reading or writing the same 283 | GPIO more than once. Opening a GPIO takes much longer than reading or writing an 284 | already opened one, so if these are used in tight loops, the open overhead will 285 | dominate (>99% of the time taken in a trivial benchmark.) 286 | -------------------------------------------------------------------------------- /test/gpio/diagnostics_test.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Frank Hunleth 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | defmodule Circuits.GPIO.DiagnosticsTest do 6 | use ExUnit.Case 7 | alias Circuits.GPIO.Diagnostics 8 | 9 | import ExUnit.CaptureIO 10 | 11 | test "report/2" do 12 | output = capture_io(fn -> Diagnostics.report(0, 1) end) 13 | assert output =~ "All tests passed" 14 | end 15 | 16 | test "run/2" do 17 | results = Diagnostics.run(2, 3) 18 | 19 | assert Enum.all?(results, fn {_name, result} -> result == :ok end) 20 | end 21 | 22 | test "speed_test/1" do 23 | results = Diagnostics.speed_test(10) 24 | 25 | # Just check that the result is not completely bogus 26 | assert results.write_cps > 1000 27 | assert results.read_cps > 1000 28 | assert results.write_one_cps > ceil(results.write_cps / 10000) 29 | assert results.read_one_cps > ceil(results.read_cps / 10000) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2014 Frank Hunleth 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | ExUnit.start() 6 | --------------------------------------------------------------------------------