├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASING.md └── src └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | schedule: 4 | - cron: '30 3 * * 2' 5 | 6 | name: CI 7 | 8 | jobs: 9 | 10 | build_and_test: 11 | name: Build and test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | - name: Run check 20 | uses: actions-rs/cargo@v1 21 | with: 22 | command: check 23 | args: --all-features 24 | - name: Run tests 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: test 28 | args: --all-features 29 | 30 | ensure_no_std: 31 | name: Ensure no_std 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v1 35 | - uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: stable 38 | override: true 39 | - name: Download cargo-nono 40 | run: | 41 | wget https://github.com/hobofan/cargo-nono/releases/download/0.1.8/cargo-nono-0.1.8-x86_64-unknown-linux-gnu.tar.gz \ 42 | && tar xfvz cargo-nono-0.1.8-x86_64-unknown-linux-gnu.tar.gz 43 | - name: Run check 44 | run: ./cargo-nono check 45 | 46 | clippy: 47 | name: Clippy 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v1 51 | - uses: actions-rs/toolchain@v1 52 | with: 53 | toolchain: stable 54 | components: clippy 55 | override: true 56 | - uses: actions-rs/clippy-check@v1 57 | with: 58 | token: ${{ secrets.GITHUB_TOKEN }} 59 | args: --all-features 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | ## 0.2.2 - 2020-11-21 9 | 10 | - [fix] Fixed bug in the stateful debouncer initialization (#7) 11 | 12 | ## 0.2.1 - 2020-11-18 13 | 14 | - [fix] Docs: Fix typo in RTIC example 15 | 16 | ## 0.2.0 - 2020-11-03 17 | 18 | By default, the debouncer will report any change from "bouncing" to "stable 19 | high/low" as an edge. If instead you want to detect only changes from a stable 20 | state to the opposite stable state, use the new stateful debouncer instead. 21 | 22 | Additionally, the debouncer construction function now allows specifying the 23 | initial state. 24 | 25 | - [add] Implement stateful debouncing (#3) 26 | - [add] Allow specifying initial state (#5) 27 | 28 | ## 0.1.3 - 2020-08-20 29 | 30 | - [fix] Docs-only update 31 | 32 | ## 0.1.2 - 2020-04-28 33 | 34 | - [fix] Fix documentation examples 35 | 36 | ## 0.1.1 - 2020-04-28 37 | 38 | - [fix] Fix metadata in Cargo.toml 39 | 40 | ## 0.1.0 - 2020-04-28 41 | 42 | This is the initial release to crates.io. All changes will be documented in 43 | this CHANGELOG. 44 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "debouncr" 3 | version = "0.2.2" 4 | authors = ["Danilo Bargen "] 5 | documentation = "https://docs.rs/debouncr" 6 | description = "A simple no-std input debouncer to detect rising and falling edges with minimal RAM requirements." 7 | readme = "README.md" 8 | repository = "https://github.com/dbrgn/debouncr/" 9 | license = "MIT OR Apache-2.0" 10 | keywords = ["debouncer", "no-std", "embedded-hal"] 11 | edition = "2018" 12 | 13 | [dependencies] 14 | doc-comment = "0.3" 15 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Danilo Bargen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Debouncr 2 | 3 | [![Build status][workflow-badge]][workflow] 4 | [![Crates.io Version][crates-io-badge]][crates-io] 5 | [![Crates.io Downloads][crates-io-download-badge]][crates-io-download] 6 | ![No Std][no-std-badge] 7 | 8 | A simple and efficient `no_std` input debouncer that uses integer bit shifting 9 | to debounce inputs. The algorithm can detect rising and falling edges and only 10 | requires 1 byte of RAM for detecting up to 8 consecutive high/low states or 2 11 | bytes of RAM for detecting up to 16 consecutive high/low states. 12 | 13 | The algorithm is based on the [Ganssle Guide to 14 | Debouncing](http://www.ganssle.com/debouncing-pt2.htm) (section "An 15 | Alternative"). 16 | 17 | Docs: https://docs.rs/debouncr 18 | 19 | ## License 20 | 21 | Licensed under either of 22 | 23 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 24 | http://www.apache.org/licenses/LICENSE-2.0) 25 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 26 | http://opensource.org/licenses/MIT) at your option. 27 | 28 | ### Contributing 29 | 30 | Unless you explicitly state otherwise, any contribution intentionally submitted 31 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 32 | be dual licensed as above, without any additional terms or conditions. 33 | 34 | 35 | 36 | [workflow]: https://github.com/dbrgn/debouncr/actions?query=workflow%3ACI 37 | [workflow-badge]: https://img.shields.io/github/workflow/status/dbrgn/debouncr/CI/master 38 | [crates-io]: https://crates.io/crates/debouncr 39 | [crates-io-badge]: https://img.shields.io/crates/v/debouncr.svg?maxAge=3600 40 | [crates-io-download]: https://crates.io/crates/debouncr 41 | [crates-io-download-badge]: https://img.shields.io/crates/d/debouncr.svg?maxAge=3600 42 | [no-std-badge]: https://img.shields.io/badge/no__std-yes-blue 43 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | Set variables: 4 | 5 | $ export VERSION=X.Y.Z 6 | $ export GPG_KEY=EA456E8BAF0109429583EED83578F667F2F3A5FA 7 | 8 | Update version numbers: 9 | 10 | $ vim -p Cargo.toml 11 | 12 | Update changelog: 13 | 14 | $ vim CHANGELOG.md 15 | 16 | Commit & tag: 17 | 18 | $ git commit -S${GPG_KEY} -m "Release v${VERSION}" 19 | $ git tag -s -u ${GPG_KEY} v${VERSION} -m "Version ${VERSION}" 20 | 21 | Publish: 22 | 23 | $ cargo publish 24 | $ git push && git push --tags 25 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Debouncr 2 | //! 3 | //! A simple and efficient `no_std` input debouncer that uses integer bit 4 | //! shifting to debounce inputs. The basic algorithm can detect rising and 5 | //! falling edges and only requires 1 byte of RAM for detecting up to 6 | //! 8 consecutive high/low states or 2 bytes of RAM for detecting up to 7 | //! 16 consecutive high/low states. 8 | //! 9 | //! While the regular algorithm will detect any change from "bouncing" 10 | //! to "stable high/low" as an edge, there is also a variant that will 11 | //! only detect changes from "stable high" to "stable low" and 12 | //! vice versa as an edge (see section "Stateful Debouncing"). 13 | //! 14 | //! The algorithm is based on the [Ganssle Guide to 15 | //! Debouncing](http://www.ganssle.com/debouncing-pt2.htm) (section "An 16 | //! Alternative"). 17 | //! 18 | //! ## API 19 | //! 20 | //! ### Instantiate 21 | //! 22 | //! First, decide how many consecutive states you want to detect. For example, 23 | //! if you poll the input pin every 5 ms and require 4 consecutive logical-high 24 | //! states to trigger a debounced press event, that event will happen after 20 ms. 25 | //! 26 | //! On initialization, you also need to specify the initial state: `true` for 27 | //! logical-high, `false` for logical-low. 28 | //! 29 | //! ```rust 30 | //! use debouncr::debounce_4; 31 | //! 32 | //! let mut debouncer = debounce_4(false); // Type: Debouncer 33 | //! ``` 34 | //! 35 | //! ### Update 36 | //! 37 | //! In regular intervals, call the `update(pressed)` function to update the 38 | //! internal state. 39 | //! 40 | //! ```rust 41 | //! use debouncr::{debounce_3, Edge}; 42 | //! 43 | //! let mut debouncer = debounce_3(false); 44 | //! # fn poll_button() -> bool { true }; 45 | //! assert_eq!(debouncer.update(poll_button()), None); 46 | //! assert_eq!(debouncer.update(poll_button()), None); 47 | //! assert_eq!(debouncer.update(poll_button()), Some(Edge::Rising)); 48 | //! ```` 49 | //! 50 | //! The `update` function will return a rising/falling edge, or `None` if the 51 | //! input is still bouncing. 52 | //! 53 | //! ### Query Debounced State 54 | //! 55 | //! You can also query the current debounced state. If none of the `n` recent 56 | //! updates were pressed, then the debounced state will be low. If all `n` 57 | //! recent updates were pressed, then the debounced state will be high. 58 | //! 59 | //! ```rust 60 | //! use debouncr::debounce_3; 61 | //! 62 | //! let mut debouncer = debounce_3(false); 63 | //! 64 | //! // Initially low 65 | //! assert!(debouncer.is_low()); 66 | //! assert!(!debouncer.is_high()); 67 | //! 68 | //! // Update, now it's neither high nor low 69 | //! debouncer.update(true); 70 | //! assert!(!debouncer.is_low()); 71 | //! assert!(!debouncer.is_high()); 72 | //! 73 | //! // After two more updates, it's high 74 | //! debouncer.update(true); 75 | //! debouncer.update(true); 76 | //! assert!(debouncer.is_high()); 77 | //! ``` 78 | //! 79 | //! ### Stateful Debouncing 80 | //! 81 | //! By default, the debouncer will report any change from "bouncing" to 82 | //! "stable high/low" as an edge. If instead you want to detect only 83 | //! changes from a stable state to the opposite stable state, use the 84 | //! stateful debouncer instead. It has slightly higher (but still tiny) memory 85 | //! overhead than the regular debouncer, because it also stores the previous 86 | //! state in addition to the debouncing updates. 87 | //! 88 | //! ```rust 89 | //! use debouncr::{debounce_stateful_3, Edge}; 90 | //! 91 | //! let mut debouncer = debounce_stateful_3(false); 92 | //! 93 | //! // Ensure initial low state 94 | //! assert!(debouncer.is_low()); 95 | //! 96 | //! // Temporary bouncing states will not trigger an edge 97 | //! assert_eq!(debouncer.update(true), None); 98 | //! assert_eq!(debouncer.update(false), None); 99 | //! assert_eq!(debouncer.update(false), None); 100 | //! assert_eq!(debouncer.update(false), None); 101 | //! 102 | //! // However, stable opposite states will trigger an edge 103 | //! assert_eq!(debouncer.update(true), None); 104 | //! assert_eq!(debouncer.update(true), None); 105 | //! assert_eq!(debouncer.update(true), Some(Edge::Rising)); 106 | //! ``` 107 | //! 108 | //! ## Example: RTIC 109 | //! 110 | //! If you want to debounce a pin in an [RTIC](https://rtic.rs/) project, 111 | //! register a resource and a timer. 112 | //! 113 | //! ```ignore 114 | //! use debouncr::{Debouncer, debounce_12, Edge, Repeat12}; 115 | //! 116 | //! #[app(..., monotonic = rtic::cyccnt::CYCCNT)] 117 | //! const APP: () = { 118 | //! struct Resources { 119 | //! button: gpioa::PA11>, 120 | //! button_state: Debouncer, 121 | //! } 122 | //! 123 | //! #[init(spawn = [poll_button])] 124 | //! fn init(ctx: init::Context) -> init::LateResources { 125 | //! // ... 126 | //! ctx.spawn.poll_button().unwrap(); 127 | //! init::LateResources { 128 | //! button, 129 | //! button_state: debounce_12(false), 130 | //! } 131 | //! } 132 | //! 133 | //! /// Regularly called task that polls the buttons and debounces them. 134 | //! #[task( 135 | //! resources = [button, button_state], 136 | //! spawn = [button_pressed, button_released], 137 | //! schedule = [poll_button], 138 | //! )] 139 | //! fn poll_button(ctx: poll_button::Context) { 140 | //! // Poll button 141 | //! let pressed: bool = ctx.resources.button.is_low().unwrap(); 142 | //! 143 | //! // Update state 144 | //! let edge = ctx.resources.button_state.update(pressed); 145 | //! 146 | //! // Dispatch event 147 | //! if edge == Some(Edge::Rising) { 148 | //! ctx.spawn.button_pressed().unwrap(); 149 | //! } else if edge == Some(Edge::Falling) { 150 | //! ctx.spawn.button_released().unwrap(); 151 | //! } 152 | //! 153 | //! // Re-schedule the timer interrupt 154 | //! ctx.schedule 155 | //! .poll_button(ctx.scheduled + POLL_PERIOD.cycles()) 156 | //! .unwrap(); 157 | //! } 158 | //! 159 | //! /// The button was pressed. 160 | //! #[task] 161 | //! fn button_pressed(ctx: button_pressed::Context) { 162 | //! // Button was pressed, handle event somehow 163 | //! } 164 | //! 165 | //! /// The button was released. 166 | //! #[task] 167 | //! fn button_released(ctx: button_pressed::Context) { 168 | //! // Button was released, handle event somehow 169 | //! } 170 | //! 171 | //! }; 172 | //! ``` 173 | //! 174 | //! ## Memory Consumption 175 | //! 176 | //! Memory size of a debouncer instance: 177 | //! 178 | //! |Debouncer|Repetitions|Bytes| 179 | //! |--|--|--| 180 | //! |[`Debouncer`]|2..8|1| 181 | //! |[`DebouncerStateful`]|2..8|2| 182 | //! |[`Debouncer`]|9..16|2| 183 | //! |[`DebouncerStateful`]|9..16|4| 184 | //! 185 | //! [`Debouncer`]: struct.Debouncer.html 186 | //! [`DebouncerStateful`]: struct.DebouncerStateful.html 187 | #![cfg_attr(not(test), no_std)] 188 | #![deny(unsafe_code, missing_docs)] 189 | 190 | use doc_comment::doc_comment; 191 | 192 | /// A debouncer. 193 | /// 194 | /// It wraps a `u8` or `u16`, depending on the number of required consecutive 195 | /// logical-high states. 196 | /// 197 | /// To create an instance, use the appropriate `debounce_X` function (where `X` 198 | /// is the number of required consecutive logical-high states). 199 | #[repr(transparent)] 200 | pub struct Debouncer { 201 | state: S, 202 | mask: core::marker::PhantomData, 203 | } 204 | 205 | /// A stateful debouncer. 206 | /// 207 | /// The regular [`Debouncer`](struct.Debouncer.html) will report any change 208 | /// from "bouncing" to "stable high/low" as an edge. That means that if a 209 | /// button is not pressed, bounces twice and then goes back to unpressed, it 210 | /// will report a falling edge even though there was no rising edge. 211 | /// 212 | /// This `DebouncerStateful` on the other hand stores the previous stable state 213 | /// and will only report a falling edge if there was previously a rising edge 214 | /// (and vice versa). 215 | /// 216 | /// The memory cost for this is storing an extra enum value per debouncer. 217 | pub struct DebouncerStateful { 218 | debouncer: Debouncer, 219 | last_edge: Edge, 220 | } 221 | 222 | /// Rising or falling edge. 223 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 224 | pub enum Edge { 225 | /// A rising edge 226 | Rising, 227 | /// A falling edge 228 | Falling, 229 | } 230 | 231 | macro_rules! impl_logic { 232 | ($T:ty, $count:expr, $M:ident, $name:ident, $name_stateful:ident, $mask:expr) => { 233 | doc_comment! { 234 | concat!( 235 | "Detect ", 236 | $count, 237 | " consecutive logical-high states.\n\n", 238 | "This type should not be used directly. ", 239 | "Instead, construct a [`Debouncer`](struct.Debouncer.html) through [`debounce_", 240 | $count, 241 | "()`](fn.debounce_", 242 | $count, 243 | ".html).", 244 | ), 245 | pub struct $M; 246 | } 247 | 248 | doc_comment! { 249 | concat!( 250 | "Create a new debouncer that can detect a rising or falling edge of ", 251 | $count, 252 | " consecutive logical states.", 253 | ), 254 | pub fn $name(initial_state_pressed: bool) -> Debouncer<$T, $M> { 255 | Debouncer { 256 | state: if initial_state_pressed { $mask } else { 0 }, 257 | mask: core::marker::PhantomData, 258 | } 259 | } 260 | } 261 | 262 | doc_comment! { 263 | concat!( 264 | "Create a new stateful debouncer that can detect stable state changes after ", 265 | $count, 266 | " consecutive logical states.", 267 | ), 268 | pub fn $name_stateful(initial_state_pressed: bool) -> DebouncerStateful<$T, $M> { 269 | DebouncerStateful { 270 | debouncer: $name(initial_state_pressed), 271 | last_edge: if initial_state_pressed {Edge::Rising} else {Edge::Falling}, 272 | } 273 | } 274 | } 275 | 276 | impl Debouncer<$T, $M> { 277 | /// Update the state. 278 | pub fn update(&mut self, pressed: bool) -> Option { 279 | // If all bits are already 1 or 0 and there was no change, 280 | // we can immediately return. 281 | if self.state == $mask && pressed { 282 | return None; 283 | } 284 | if self.state == 0 && !pressed { 285 | return None; 286 | } 287 | 288 | // Update state by shifting in the press state & masking 289 | self.state = ((self.state << 1) | (pressed as $T)) & $mask; 290 | 291 | // Query updated value 292 | if self.state == $mask { 293 | Some(Edge::Rising) 294 | } else if self.state == 0 { 295 | Some(Edge::Falling) 296 | } else { 297 | None 298 | } 299 | } 300 | 301 | /// Return `true` if the debounced state is logical high. 302 | pub fn is_high(&self) -> bool { 303 | self.state == $mask 304 | } 305 | 306 | /// Return `true` if the debounced state is logical low. 307 | pub fn is_low(&self) -> bool { 308 | self.state == 0 309 | } 310 | } 311 | 312 | impl DebouncerStateful<$T, $M> { 313 | /// Update the state. 314 | pub fn update(&mut self, pressed: bool) -> Option { 315 | self.debouncer.update(pressed).and_then(|edge| { 316 | if edge != self.last_edge { 317 | self.last_edge = edge; 318 | Some(edge) 319 | } else { 320 | None 321 | } 322 | }) 323 | } 324 | 325 | /// Return `true` if the debounced state is logical high. 326 | pub fn is_high(&self) -> bool { 327 | self.debouncer.is_high() 328 | } 329 | 330 | /// Return `true` if the debounced state is logical low. 331 | pub fn is_low(&self) -> bool { 332 | self.debouncer.is_low() 333 | } 334 | } 335 | }; 336 | } 337 | 338 | impl_logic!(u8, 2, Repeat2, debounce_2, debounce_stateful_2, 0b0000_0011); 339 | impl_logic!(u8, 3, Repeat3, debounce_3, debounce_stateful_3, 0b0000_0111); 340 | impl_logic!(u8, 4, Repeat4, debounce_4, debounce_stateful_4, 0b0000_1111); 341 | impl_logic!(u8, 5, Repeat5, debounce_5, debounce_stateful_5, 0b0001_1111); 342 | impl_logic!(u8, 6, Repeat6, debounce_6, debounce_stateful_6, 0b0011_1111); 343 | impl_logic!(u8, 7, Repeat7, debounce_7, debounce_stateful_7, 0b0111_1111); 344 | impl_logic!(u8, 8, Repeat8, debounce_8, debounce_stateful_8, 0b1111_1111); 345 | impl_logic!(u16, 9, Repeat9, debounce_9, debounce_stateful_9, 0b0000_0001_1111_1111); 346 | impl_logic!(u16, 10, Repeat10, debounce_10, debounce_stateful_10, 0b0000_0011_1111_1111); 347 | impl_logic!(u16, 11, Repeat11, debounce_11, debounce_stateful_11, 0b0000_0111_1111_1111); 348 | impl_logic!(u16, 12, Repeat12, debounce_12, debounce_stateful_12, 0b0000_1111_1111_1111); 349 | impl_logic!(u16, 13, Repeat13, debounce_13, debounce_stateful_13, 0b0001_1111_1111_1111); 350 | impl_logic!(u16, 14, Repeat14, debounce_14, debounce_stateful_14, 0b0011_1111_1111_1111); 351 | impl_logic!(u16, 15, Repeat15, debounce_15, debounce_stateful_15, 0b0111_1111_1111_1111); 352 | impl_logic!(u16, 16, Repeat16, debounce_16, debounce_stateful_16, 0b1111_1111_1111_1111); 353 | 354 | #[cfg(test)] 355 | mod tests { 356 | use super::*; 357 | 358 | #[test] 359 | fn test_rising_edge() { 360 | // Initially not pressed 361 | let mut debouncer: Debouncer = debounce_3(false); 362 | assert!(debouncer.is_low()); 363 | 364 | // Three pressed updates required 365 | assert_eq!(debouncer.update(true), None); 366 | assert_eq!(debouncer.update(true), None); 367 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 368 | 369 | // Further presses do not indicate a rising edge anymore 370 | assert_eq!(debouncer.update(true), None); 371 | 372 | // A depressed state resets counting 373 | assert_eq!(debouncer.update(false), None); 374 | assert_eq!(debouncer.update(true), None); 375 | assert_eq!(debouncer.update(true), None); 376 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 377 | } 378 | 379 | #[test] 380 | fn test_falling_edge() { 381 | // Initially not pressed 382 | let mut debouncer: Debouncer = debounce_3(false); 383 | assert!(debouncer.is_low()); 384 | 385 | // A single non-pressed update does not trigger 386 | assert_eq!(debouncer.update(false), None); 387 | assert!(debouncer.is_low()); 388 | 389 | // Trigger a falling edge 390 | assert_eq!(debouncer.update(true), None); 391 | assert_eq!(debouncer.update(false), None); 392 | assert_eq!(debouncer.update(false), None); 393 | assert_eq!(debouncer.update(false), Some(Edge::Falling)); 394 | assert_eq!(debouncer.update(false), None); 395 | assert!(debouncer.is_low()); 396 | } 397 | 398 | #[test] 399 | fn test_debounce_16() { 400 | // Sixteen pressed updates required 401 | let mut debouncer: Debouncer = debounce_16(false); 402 | assert!(debouncer.is_low()); 403 | for _ in 0..15 { 404 | assert_eq!(debouncer.update(true), None); 405 | assert!(!debouncer.is_high()); 406 | } 407 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 408 | assert!(debouncer.is_high()); 409 | assert_eq!(debouncer.update(true), None); 410 | assert!(debouncer.is_high()); 411 | } 412 | 413 | #[test] 414 | fn test_is_low_high() { 415 | // Initially low 416 | let mut debouncer: Debouncer = debounce_8(false); 417 | assert!(debouncer.is_low()); 418 | assert!(!debouncer.is_high()); 419 | 420 | // Depressed updates don't change the situation 421 | debouncer.update(false); 422 | assert!(debouncer.is_low()); 423 | assert!(!debouncer.is_high()); 424 | 425 | // A pressed update causes neither low nor high state 426 | for _ in 0..7 { 427 | assert!(debouncer.update(true).is_none()); 428 | assert!(!debouncer.is_low()); 429 | assert!(!debouncer.is_high()); 430 | } 431 | 432 | // Once complete, the state is high 433 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 434 | assert!(!debouncer.is_low()); 435 | assert!(debouncer.is_high()); 436 | 437 | // Consecutive pressed updates don't trigger an edge but are still high 438 | assert!(debouncer.update(true).is_none()); 439 | assert!(!debouncer.is_low()); 440 | assert!(debouncer.is_high()); 441 | } 442 | 443 | /// Ensure the promised low RAM consumption. 444 | #[test] 445 | fn test_ram_consumption() { 446 | // Regular debouncers 447 | assert_eq!(std::mem::size_of_val(&debounce_2(false)), 1); 448 | assert_eq!(std::mem::size_of_val(&debounce_8(false)), 1); 449 | assert_eq!(std::mem::size_of_val(&debounce_9(false)), 2); 450 | assert_eq!(std::mem::size_of_val(&debounce_16(false)), 2); 451 | 452 | // Stateful debouncers 453 | assert_eq!(std::mem::size_of_val(&debounce_stateful_8(false)), 2); 454 | assert_eq!(std::mem::size_of_val(&debounce_stateful_9(false)), 4); 455 | } 456 | 457 | /// Ensure that the initial state can be specified. 458 | #[test] 459 | fn test_initial_state() { 460 | let mut debouncer = debounce_2(false); 461 | assert_eq!(debouncer.update(false), None); 462 | assert_eq!(debouncer.update(false), None); 463 | assert_eq!(debouncer.update(true), None); 464 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 465 | 466 | let mut debouncer = debounce_2(false); 467 | assert_eq!(debouncer.update(true), None); 468 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 469 | assert_eq!(debouncer.update(false), None); 470 | assert_eq!(debouncer.update(false), Some(Edge::Falling)); 471 | 472 | let mut debouncer = debounce_2(true); 473 | assert_eq!(debouncer.update(false), None); 474 | assert_eq!(debouncer.update(false), Some(Edge::Falling)); 475 | assert_eq!(debouncer.update(true), None); 476 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 477 | 478 | let mut debouncer = debounce_2(true); 479 | assert_eq!(debouncer.update(true), None); 480 | assert_eq!(debouncer.update(true), None); 481 | assert_eq!(debouncer.update(false), None); 482 | assert_eq!(debouncer.update(false), Some(Edge::Falling)); 483 | 484 | // Stateful debouncers 485 | let mut debouncer = debounce_stateful_2(false); 486 | assert_eq!(debouncer.update(false), None); 487 | assert_eq!(debouncer.update(false), None); 488 | assert_eq!(debouncer.update(true), None); 489 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 490 | 491 | let mut debouncer = debounce_stateful_2(false); 492 | assert_eq!(debouncer.update(true), None); 493 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 494 | assert_eq!(debouncer.update(false), None); 495 | assert_eq!(debouncer.update(false), Some(Edge::Falling)); 496 | 497 | let mut debouncer = debounce_stateful_2(true); 498 | assert_eq!(debouncer.update(false), None); 499 | assert_eq!(debouncer.update(false), Some(Edge::Falling)); 500 | assert_eq!(debouncer.update(true), None); 501 | assert_eq!(debouncer.update(true), Some(Edge::Rising)); 502 | 503 | let mut debouncer = debounce_stateful_2(true); 504 | assert_eq!(debouncer.update(true), None); 505 | assert_eq!(debouncer.update(true), None); 506 | assert_eq!(debouncer.update(false), None); 507 | assert_eq!(debouncer.update(false), Some(Edge::Falling)); 508 | 509 | } 510 | } 511 | --------------------------------------------------------------------------------