From 6388856523d704195c1eeccdf9c3b3b90f45da86 Mon Sep 17 00:00:00 2001 From: Markus Mayer Date: Fri, 5 Jul 2024 23:32:40 +0200 Subject: [PATCH] Initial commit --- .codespellrc | 3 + .github/dependabot.yml | 7 + .github/workflows/rust.yml | 76 ++++++++++ .gitignore | 4 + CHANGELOG.md | 14 ++ Cargo.toml | 32 ++++ LICENSE-APACHE | 201 +++++++++++++++++++++++++ LICENSE-EUPL | 287 ++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 25 ++++ README.md | 73 ++++++++++ src/characteristics.rs | 22 +++ src/i16x3.rs | 46 ++++++ src/lib.rs | 292 +++++++++++++++++++++++++++++++++++++ src/reading.rs | 89 +++++++++++ src/sensor_data.rs | 51 +++++++ src/wrapper.rs | 3 + src/wrapper/refcell.rs | 59 ++++++++ 17 files changed, 1284 insertions(+) create mode 100644 .codespellrc create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-EUPL create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 src/characteristics.rs create mode 100644 src/i16x3.rs create mode 100644 src/lib.rs create mode 100644 src/reading.rs create mode 100644 src/sensor_data.rs create mode 100644 src/wrapper.rs create mode 100644 src/wrapper/refcell.rs diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..66eacc0 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +ignore-words-list = crate +skip = .git,*.lock diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f591f50 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +--- +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..881839e --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,76 @@ +--- +name: Rust + +on: + workflow_dispatch: + push: + branches: + - main + - master + paths: + - 'Cargo.toml' + - 'src/**' + - '.codespellrc' + - '.github/workflows/rust.yml' + pull_request: + branches: + - main + - master + paths: + - 'Cargo.toml' + - 'src/**' + - '.codespellrc' + - '.github/workflows/rust.yml' + +env: + CARGO_TERM_COLOR: always + +jobs: + codespell: + name: Check for spelling mistakes + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Codespell + uses: codespell-project/actions-codespell@v2 + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check format + run: cargo fmt --check + - name: Clippy + run: cargo clippy --all-features + + docs: + name: Build documentation + needs: + - codespell + - lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build documentation + run: cargo doc --all-features + + build-linux: + name: Build on Linux + needs: + - lint + runs-on: ubuntu-latest + strategy: + matrix: + features: [ [ ], [ "--all-features" ], [ "--no-default-features" ] ] + steps: + - uses: actions/checkout@v4 + - name: Clippy + run: cargo clippy ${{ join(matrix.features, ' ') }} + - name: Build + run: cargo build --verbose ${{ join(matrix.features, ' ') }} + - name: Run doctests + run: cargo test --doc --verbose ${{ join(matrix.features, ' ') }} + - name: Run regular tests + run: cargo test --tests --verbose ${{ join(matrix.features, ' ') }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8d7c8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/*.rs.bk +.#* +/target/ +Cargo.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..90ba25f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [0.1.0-alpha.1] - 2024-05-07 + +[0.1.0-alpha.1]: https://github.com/sunsided/l3gd20/releases/tag/v0.1.0-alpha.1 + +### Added + +- Initial release. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f521fee --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "l3gd20-ng" +version = "0.1.0-alpha.1" +authors = ["Markus Mayer "] +categories = ["embedded", "hardware-support", "no-std"] +description = "A platform agnostic SPI driver to interface with the L3GD2 gyroscope" +keywords = ["embedded-hal-driver", "gyroscope", "MEMS", "IMU", "l3gd20"] +license = "EUPL-1.2 OR MIT OR Apache-2.0" +homepage = "https://github.com/sunsided/l3gd20" +repository = "https://github.com/sunsided/l3gd20" +edition = "2021" +rust-version = "1.67" + +[features] +default = [] +defmt = ["dep:defmt", "l3gd20-registers/defmt"] + +[dependencies] +chip-select = { version = "0.2.0", default-features = false, features = ["hal-0_2"] } +defmt = { version = "0.3.8", optional = true } +embedded-hal = "0.2.7" +l3gd20-registers = "0.1.0" + +[dev-dependencies] +test-format = "0.1.0" + +[patch.crates-io] +# l3gd20-registers = { git = "https://github.com/sunsided/l3gd20-registers" } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "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. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "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. + + "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). + + "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. + + "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." + + "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. + +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. + +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. + +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: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (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 + + (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. + + 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. + +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. + +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. + +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. + +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. + +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. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + 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. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the 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. diff --git a/LICENSE-EUPL b/LICENSE-EUPL new file mode 100644 index 0000000..4153cd3 --- /dev/null +++ b/LICENSE-EUPL @@ -0,0 +1,287 @@ + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined +below) which is provided under the terms of this Licence. Any use of the Work, +other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This Licence + does not define the extent of modification or dependence on the Original Work + required in order to classify a work as a Derivative Work; this extent is + determined by copyright law applicable in the country mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case may + be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of the +Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions on +the Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed under +a Compatible Licence, this Distribution or Communication can be done under the +terms of this Compatible Licence. For the sake of this clause, ‘Compatible +Licence’ refers to the licences listed in the appendix attached to this Licence. +Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible +Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, +the Licensee will provide a machine-readable copy of the Source Code or indicate +a repository where this Source will be easily and freely available for as long +as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or 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 copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such damage. +However, the Licensor will be liable under statutory product liability laws as +far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if +accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or 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 +the fact You have accepted any warranty or additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a +remote location) the distribution channel or media (for example, a website) must +at least provide to the public the information requested by the applicable law +regarding the Licensor, the Licence and the way it may be accessible, concluded, +stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of +this Licence or updated versions of the Appendix, so far this is required and +reasonable, without reducing the scope of the rights granted by the Licence. New +versions of the Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty on + the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive jurisdiction + of the competent court where the Licensor resides or conducts its primary + business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide +the rights granted in Article 2 of this Licence and protect the covered Source +Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new +EUPL version. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..e2685a3 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2024 Markus Mayer + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..02088c3 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# STMicroelectronics L3GD20 SPI driver + +[![Crates.io][crates-image]][crates-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![MSRV][msrv-image] +[![EUPL 1.2 licensed][license-eupl-image]][license-eupl-link] +[![Apache 2.0 licensed][license-apache-image]][license-apache-link] +[![MIT licensed][license-mit-image]][license-mit-link] + +> A platform-agnostic driver to interface with the L3GD20 gyroscope. + +Do note that the sensor is discontinued and that documentation is scarce. Owners of an STM32F3 Discovery +board may still find this crate useful, among others. + +## Implemented so far + +- Reading the gyroscope in SPI blocking mode +- Direct access to registers via [`l3gd20-registers`] structs and [`hardware-registers`] traits + +## License + +Licensed under either of + +- European Union Public Licence, Version 1.2, ([LICENSE-EUPL](LICENSE-EUPL) + or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12) +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the +work by you, as defined in the Apache-2.0 license, shall be triple licensed as above, without any +additional terms or conditions. + +## Code of Conduct + +We abide by the [Contributor Covenant][cc] and ask that you do as well. + +[crates-image]: https://img.shields.io/crates/v/l3gd20-ng + +[crates-link]: https://crates.io/crates/l3gd20-ng + +[docs-image]: https://docs.rs/l3gd20-ng/badge.svg + +[docs-link]: https://docs.rs/l3gd20-ng/ + +[build-image]: https://github.com/sunsided/l3gd20/workflows/Rust/badge.svg + +[build-link]: https://github.com/sunsided/l3gd20/actions + +[msrv-image]: https://img.shields.io/badge/rustc-1.64+-blue.svg + +[license-eupl-image]: https://img.shields.io/badge/license-EUPL_1.2-blue.svg + +[license-apache-image]: https://img.shields.io/badge/license-Apache_2.0-blue.svg + +[license-mit-image]: https://img.shields.io/badge/license-MIT-blue.svg + +[license-apache-link]: https://github.com/sunsided/l3gd20/blob/develop/LICENSE-APACHE + +[license-mit-link]: https://github.com/sunsided/l3gd20/blob/develop/LICENSE-MIT + +[license-eupl-link]: https://github.com/sunsided/l3gd20/blob/develop/LICENSE-EUPL + +[cc]: https://contributor-covenant.org + +[`l3gd20-registers`]: https://crates.io/crates/l3gd20-registers + +[`hardware-registers`]: https://crates.io/crates/hardware-registers diff --git a/src/characteristics.rs b/src/characteristics.rs new file mode 100644 index 0000000..a1cd9fc --- /dev/null +++ b/src/characteristics.rs @@ -0,0 +1,22 @@ +/// Scale and noise characteristics of the sensor. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Characteristics { + /// Full Scale selection in degrees/second. + pub full_scale: u16, + + /// The sensitivity at the current selected full scale. + pub sensitivity: f32, + + /// The zero-rate noise level in degrees/second. + pub zero_rate_noise: f32, + + /// The temperature-related zero-rate noise level change, in degrees/second. + /// * At FS=250 dps, ±0.03 dps/°C + /// * At FS=2000 dps, ±0.04 dps/°C + pub zero_rate_level_temp: f32, + + /// The frequency-dependent rate noise level in degrees/second. + /// The rate noise density is at 0.03 dps/√Hz. + pub rate_noise_density: f32, +} diff --git a/src/i16x3.rs b/src/i16x3.rs new file mode 100644 index 0000000..519b34c --- /dev/null +++ b/src/i16x3.rs @@ -0,0 +1,46 @@ +/// XYZ triple +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct I16x3 { + /// X component + pub x: i16, + /// Y component + pub y: i16, + /// Z component + pub z: i16, +} + +#[cfg(feature = "defmt")] +#[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] +impl defmt::Format for I16x3 { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "({}, {}, {})", self.x, self.y, self.z); + } +} + +impl core::fmt::Debug for I16x3 { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use core::fmt::Write; + f.write_char('(')?; + core::fmt::Debug::fmt(&self.x, f)?; + f.write_str(", ")?; + core::fmt::Debug::fmt(&self.y, f)?; + f.write_str(", ")?; + core::fmt::Debug::fmt(&self.z, f)?; + f.write_char(')') + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn i16x3_debug() { + let value = I16x3 { + x: 10, + y: 20, + z: 30, + }; + test_format::assert_debug_fmt!(value, "(10, 20, 30)"); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..10d453a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,292 @@ +//! # STMicroelectronics L3GD20 SPI driver +//! +//! > A platform-agnostic SPI driver to interface with the L3GD20 gyroscope +//! +//! Do note that the sensor is discontinued and that documentation is scarce. Owners of an STM32F3 Discovery +//! board may still find this crate useful, among others. +//! +//! This driver was built using [`embedded-hal`] and [`hardware-registers`] traits. +//! +//! [`embedded-hal`]: https://crates.io/crates/embedded-hal +//! [`hardware-registers`]: https://crates.io/crates/hardware-registers + +#![deny(missing_docs)] +#![deny(warnings)] +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] + +mod characteristics; +mod i16x3; +mod reading; +mod sensor_data; +pub mod wrapper; + +use chip_select::{ChipSelect, ChipSelectGuarded}; +use embedded_hal::blocking::spi::Transfer; +use l3gd20_registers::prelude::SPIRegister; +use l3gd20_registers::*; + +pub use characteristics::Characteristics; +pub use i16x3::I16x3; +pub use reading::Reading; +pub use sensor_data::SensorData; + +/// SPI Driver for Bosch Sensortec L3GD20 Gyroscope +#[allow(non_snake_case)] +pub struct L3GD20SPI { + cs: CS, + spi: SPI, +} + +impl L3GD20SPI +where + CS: ChipSelect, + SPI: Transfer, +{ + /// Bit flag for a read command. + const READ: u8 = 0b1000_0000; + + /// Bit flag for a write command. + const WRITE: u8 = 0b0000_0000; + + /// Bit flag for a multi-address command; auto-increments addresses after each transfer. + const MULTI: u8 = 0b0100_0000; + + /// Bit flag for a single-address command. + const SINGLE: u8 = 0b0000_0000; + + /// Mask for register addresses. + const REG_ADDR_MASK: u8 = 0b0011_1111; + + /// Initialize the SPI connection. + #[allow(clippy::too_many_arguments)] + pub fn new(spi: SPI, chip_select: CS) -> Result + where + CS: ChipSelectGuarded, + { + let mut device = Self { + cs: chip_select, + spi, + }; + + // Apply standard configuration. + device.reset()?; + Ok(device) + } + + /// Identifies this chip by querying the `WHO_AM_I` register. + pub fn identify(&mut self) -> Result + where + CS: ChipSelectGuarded, + { + let ident = self.read_register::()?; + if ident.ident() == 0b11010100 { + Ok(true) + } else { + #[cfg(feature = "defmt")] + defmt::debug!( + "L3GD20 sensor identification failed; got {:08b}", + ident.ident() + ); + Ok(false) + } + } + + /// Resets the device to reasonable defaults. + pub fn reset(&mut self) -> Result<(), E> + where + CS: ChipSelectGuarded, + { + // Use a bulk write instead. + self.write_register( + ControlRegister1::default() + .with_power_up(true) + .with_x_enable(true) + .with_y_enable(true) + .with_z_enable(true) + .with_output_data_rate(OutputDataRate::Hz95) + .with_bandwidth(Bandwidth::Narrowest), + )?; + self.write_register( + ControlRegister2::default() + .with_hpm(HighpassFilterMode::NormalModeResetFilter) + .with_hpcf(0), + )?; + self.write_register( + ControlRegister3::default() + .with_i1int1(false) + .with_i1boot(false) + .with_int1_low(false) + .with_i2drdy(false) + .with_i2wtm(false) + .with_i2orun(false) + .with_i2empty(false) + .with_open_drain(false), + )?; + self.write_register( + ControlRegister4::default() + .with_block_data_update(false) + .with_big_endian(false) + .with_full_scale(Sensitivity::D250) + .with_spi_serial_3wire(false), + )?; + self.write_register(ControlRegister5::default().with_boot(true))?; // toggle boot + self.write_register( + ControlRegister5::default() + .with_boot(false) + .with_fifo_enable(false) + .with_hpen(false) + .with_int1_sel(0) + .with_out_sel(0), + )?; + + Ok(()) + } + + /// Sets the be powered up and active. + pub fn power_up(&mut self) -> Result<(), E> + where + CS: ChipSelectGuarded, + { + self.modify_register(|reg: ControlRegister1| { + reg.with_power_up(true) + .with_x_enable(true) + .with_y_enable(true) + .with_z_enable(true) + }) + } + + /// Sets the device to sleep mode. + pub fn sleep_mode(&mut self) -> Result<(), E> + where + CS: ChipSelectGuarded, + { + self.modify_register(|reg: ControlRegister1| { + reg.with_power_up(true) + .with_x_enable(false) + .with_y_enable(false) + .with_z_enable(false) + }) + } + + /// Sets the device to be powered down. + pub fn power_down(&mut self) -> Result<(), E> + where + CS: ChipSelectGuarded, + { + self.modify_register(|reg: ControlRegister1| reg.with_power_up(false)) + } + + /// Sets the output data rate. + pub fn set_odr(&mut self, data_rate: OutputDataRate) -> Result<(), E> + where + CS: ChipSelectGuarded, + { + self.modify_register(|reg: ControlRegister1| reg.with_output_data_rate(data_rate)) + } + + /// Sets the output data rate. + pub fn set_bandwidth(&mut self, bandwidth: Bandwidth) -> Result<(), E> + where + CS: ChipSelectGuarded, + { + self.modify_register(|reg: ControlRegister1| reg.with_bandwidth(bandwidth)) + } + + /// Identifies this chip by querying the `WHO_AM_I` register. + pub fn temp_raw(&mut self) -> Result + where + CS: ChipSelectGuarded, + { + let ident = self.read_register::()?; + Ok(ident.temp()) + } + + /// Fetches all data off the sensor. + pub fn raw_data(&mut self) -> Result + where + CS: ChipSelectGuarded, + { + let _guard = self.cs.select_guard(); + + // The registers come in the order Temperature (0x26), Status (0x27), XL, XH, YL, YH, ZL, ZH (0x2D) + let command = Self::read_multi_cmd(*TemperatureRegister::REGISTER_ADDRESS); + let mut buffer = [command, 0, 0, 0, 0, 0, 0, 0, 0]; + self.spi.transfer(&mut buffer)?; + + let temp = TemperatureRegister::from_bits(buffer[1]); + let _status = StatusRegister::from_bits(buffer[2]); // TODO: Use status + let xlo = OutXLow::from_bits(buffer[3]); + let xhi = OutXHigh::from_bits(buffer[4]); + let ylo = OutYLow::from_bits(buffer[5]); + let yhi = OutYHigh::from_bits(buffer[6]); + let zlo = OutZLow::from_bits(buffer[7]); + let zhi = OutZHigh::from_bits(buffer[8]); + + let x = xhi + xlo; + let y = yhi + ylo; + let z = zhi + zlo; + + Ok(SensorData { + temperature: temp.temp(), + x: Reading::new_fresh(x), + y: Reading::new_fresh(y), + z: Reading::new_fresh(z), + }) + } + + /// Creates a read command for a given address. Does not auto-increment the address afterward. + fn read_single_cmd(address: u8) -> u8 { + Self::READ | Self::SINGLE | (address & Self::REG_ADDR_MASK) + } + + /// Creates a read command for a given address. Does not auto-increment the address afterward. + fn read_multi_cmd(address: u8) -> u8 { + Self::READ | Self::MULTI | (address & Self::REG_ADDR_MASK) + } + + /// Creates a write command for a given address. Does not auto-increment the address afterward. + fn write_single_cmd(address: u8) -> u8 { + Self::WRITE | Self::SINGLE | (address & Self::REG_ADDR_MASK) + } + + /// Reads a single register. Assumes the chip is selected. + pub fn read_register(&mut self) -> Result + where + R: Register, + CS: ChipSelectGuarded, + { + let _guard = self.cs.select_guard(); + let command = Self::read_single_cmd(*R::REGISTER_ADDRESS); + let mut buffer = [command, 0]; + self.spi.transfer(&mut buffer)?; + Ok(R::from_bits(buffer[1])) + } + + /// Writes a single register. Assumes the chip is selected. + pub fn write_register(&mut self, register: B) -> Result<(), E> + where + B: core::borrow::Borrow, + R: WritableRegister, + CS: ChipSelectGuarded, + { + let _guard = self.cs.select_guard(); + let byte = register.borrow().to_bits(); + let command = Self::write_single_cmd(*R::REGISTER_ADDRESS); + let mut buffer = [command, byte]; + self.spi.transfer(&mut buffer)?; + Ok(()) + } + + /// Modifies a single register. Assumes the chip is selected. + pub fn modify_register(&mut self, f: F) -> Result<(), E> + where + F: FnOnce(R) -> R, + R: WritableRegister, + CS: ChipSelectGuarded, + { + let register: R = self.read_register()?; + let register = f(register); + self.write_register(register) + } +} diff --git a/src/reading.rs b/src/reading.rs new file mode 100644 index 0000000..56b869f --- /dev/null +++ b/src/reading.rs @@ -0,0 +1,89 @@ +use core::ops::{Deref, DerefMut}; + +/// A sensor reading that captures the notion of recent and outdated information. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Reading { + /// The reading is stale. + Stale(T), + /// The reading is recent. + Fresh(T), + /// New data was written before old data was read. + Overrun(T), +} + +impl Deref for Reading { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + Reading::Stale(x) => x, + Reading::Fresh(x) => x, + Reading::Overrun(x) => x, + } + } +} + +impl DerefMut for Reading { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Reading::Stale(x) => x, + Reading::Fresh(x) => x, + Reading::Overrun(x) => x, + } + } +} + +impl Reading { + /// Creates a stale reading. + #[must_use] + pub fn new_stale(value: T) -> Self { + Self::Stale(value) + } + + /// Creates a fresh reading. + #[must_use] + pub fn new_fresh(value: T) -> Self { + Self::Fresh(value) + } + + /// Creates a fresh reading. + #[must_use] + pub fn new_overrun(value: T) -> Self { + Self::Overrun(value) + } + + /// Consumes self and returns the inner value. + #[must_use] + pub fn into_inner(self) -> T { + match self { + Reading::Stale(x) => x, + Reading::Fresh(x) => x, + Reading::Overrun(x) => x, + } + } + + /// Indicates whether this is a stale reading. + #[must_use] + pub fn stale(&self) -> bool { + matches!(self, Reading::Stale(_)) + } + + /// Indicates whether this is a fresh reading. + #[must_use] + pub fn fresh(&self) -> bool { + matches!(self, Reading::Fresh(_)) + } + + /// Indicates whether this is a fresh or an overrun reading. + #[must_use] + pub fn fresh_or_overrun(&self) -> bool { + self.fresh() || self.overrun() + } + + /// Indicates whether this is an overrun reading. + #[must_use] + pub fn overrun(&self) -> bool { + matches!(self, Reading::Overrun(_)) + } +} diff --git a/src/sensor_data.rs b/src/sensor_data.rs new file mode 100644 index 0000000..ee1f6a7 --- /dev/null +++ b/src/sensor_data.rs @@ -0,0 +1,51 @@ +use crate::{I16x3, Reading}; + +/// Sensor data. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SensorData { + /// The temperature reading + pub temperature: u8, + /// The X-axis reading. + pub x: Reading, + /// The Y-axis reading. + pub y: Reading, + /// The Z-axis reading. + pub z: Reading, +} + +impl SensorData { + /// Indicates whether any reading is stale. + #[must_use] + pub fn stale(&self) -> bool { + self.x.stale() || self.y.stale() || self.z.stale() + } + + /// Indicates whether all readings are fresh. + #[must_use] + pub fn fresh(&self) -> bool { + self.x.fresh() && self.y.stale() || self.z.stale() + } + + /// Indicates whether all readings are fresh or overrun. + #[must_use] + pub fn fresh_or_overrun(&self) -> bool { + self.x.fresh_or_overrun() && self.y.fresh_or_overrun() || self.z.fresh_or_overrun() + } + + /// Indicates whether this is an overrun reading. + #[must_use] + pub fn overrun(&self) -> bool { + self.x.overrun() && self.y.overrun() || self.z.overrun() + } +} + +impl From for I16x3 { + fn from(value: SensorData) -> Self { + Self { + x: *value.x, + y: *value.y, + z: *value.z, + } + } +} diff --git a/src/wrapper.rs b/src/wrapper.rs new file mode 100644 index 0000000..e304532 --- /dev/null +++ b/src/wrapper.rs @@ -0,0 +1,3 @@ +//! Provides wrappers for SPI types. + +pub mod refcell; diff --git a/src/wrapper/refcell.rs b/src/wrapper/refcell.rs new file mode 100644 index 0000000..d7a8c71 --- /dev/null +++ b/src/wrapper/refcell.rs @@ -0,0 +1,59 @@ +//! Provides [`RefCell`] wrappers for SPI types. + +use core::cell::RefCell; +use core::ops::{Deref, DerefMut}; +use embedded_hal::blocking::spi::Transfer; + +/// An SPI instance wrapped in a [`RefCell`]. +pub struct RefCellSPI(RefCell) +where + SPI: Transfer; + +impl RefCellSPI +where + SPI: Transfer, +{ + /// Initializes a new instance of the [`RefCellSPI`] type. + pub const fn new(i2c: RefCell) -> Self { + Self(i2c) + } + + /// Consumes self and returns the inner SPI instance. + #[inline] + pub fn into_inner(self) -> RefCell { + self.0 + } +} + +impl Transfer for RefCellSPI +where + SPI: Transfer, +{ + type Error = E; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.0.borrow_mut().transfer(words) + } +} + +impl Deref for RefCellSPI +where + SPI: Transfer, +{ + type Target = RefCell; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for RefCellSPI +where + SPI: Transfer, +{ + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +}