diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f1874f5..e38c585 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,11 +13,8 @@ jobs: strategy: fail-fast: false matrix: - rust: [1.63.0, stable] - features: ['', '--all-features'] - exclude: - - rust: 1.63.0 - features: '--all-features' + rust: [1.81.0, stable] + features: [''] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -95,3 +92,20 @@ jobs: with: sarif_file: rust-clippy-results.sarif wait-for-processing: true + + # simplify GH settings: have one single build to be required + build-results: + name: Final Results + if: ${{ always() }} + runs-on: ubuntu-latest + needs: [lib, stm32f4-single-motor-example, clippy] + steps: + - name: check for failed builds of the library + if: ${{ needs.lib.result != 'success' }} + run: exit 1 + - name: check for failed builds of the example + if: ${{ needs.stm32f4-single-motor-example.result != 'success' }} + run: exit 1 + - name: check for failed clippy builds + if: ${{ needs.clippy.result != 'success' }} + run: exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f6097..ab7294d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate + +## [1.0.0] - 2024-09-23 ### Added + * Add a `current_standby()` method to check if the driver is currently in standby mode. +* `MotorError` and `Tb6612fngError` now implement `core::error::Error` (newly stabilised in Rust 1.81) ### Changed @@ -15,16 +19,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 initialisation to the documented defaults. * `Motor::new()` and `Driver::new()` methods now also return errors if they fail to set their outputs upon initialisation. -* Breaking: update to `embedded-hal` 1.0 -* Renamed error types to their struct names -* Renamed `DriveCommand::Backwards` to `DriveCommand::Backward` to match +* **Breaking**: update to `embedded-hal` 1.0 +* **Breaking**: Renamed error types to their struct names +* **Breaking**: Renamed `DriveCommand::Backwards` to `DriveCommand::Backward` to match `DriveCommand::Forward` +* The MSRV has been updated to 1.81.0 due to `core::error::Error` being implemented ### Removed * Removed the `drive_forward`, `drive_backward`, `stop` and `brake` functions as they are duplicates to the `drive` function with the different enum variants and make the API surface larger +* Removed the `defmt` feature: it was only used for debugging and since the `enum`s & `struct`s implement `Debug` + consuming code can use `defmt::Debug2Format` when needed. The single `defmt::debug!` statement in `Motor::drive` was + not very helpful anyway if two motors were connected ## [0.2.0] - 2023-11-28 @@ -37,5 +45,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 If your HAL does not yet implement this, then please use the previous release of the library. -[Unreleased]: https://github.com/rust-embedded-community/tb6612fng-rs/compare/v0.2.0...HEAD +[Unreleased]: https://github.com/rust-embedded-community/tb6612fng-rs/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/rust-embedded-community/tb6612fng-rs/compare/v0.2.0...v1.0.0 [0.2.0]: https://github.com/rust-embedded-community/tb6612fng-rs/compare/v0.1.1...v0.2.0 diff --git a/Cargo.toml b/Cargo.toml index db7258c..f474c79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "tb6612fng" -version = "0.2.0" +version = "1.0.0" edition = "2021" -rust-version = "1.63" +rust-version = "1.81" description = "A `no_std` driver for the TB6612FNG motor driver." repository = "https://github.com/rust-embedded-community/tb6612fng-rs" categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] keywords = ["tb6612fng", "driver", "motor", "controller", "embedded-hal-driver"] license = "MIT OR Apache-2.0" +authors = ["Ralph Ursprung ", "ripytide "] + +[features] [dependencies] embedded-hal = "1.0" @@ -16,4 +19,4 @@ embedded-hal = "1.0" defmt = { version = "0.3", optional = true } [dev-dependencies] -embedded-hal-mock = "0.11" +embedded-hal-mock = { version = "0.11", default-features = false, features = ["eh1"] } diff --git a/README.md b/README.md index 24b0b2d..08a8acd 100644 --- a/README.md +++ b/README.md @@ -20,21 +20,25 @@ See the documentation for usage examples. * You plan on using a single motor with the standby feature: use `Motor` and control the standby pin manually * You plan on using a single motor without the standby feature: use `Motor` -## Optional features -* `defmt`: you can enable the [`defmt`](https://defmt.ferrous-systems.com/) feature to get a `defmt::debug!` call for every speed change. - ## Examples A simple example for the STM32F4 microcontrollers is [available](examples/stm32f4-single-motor-example/README.md). ## Changelog For the changelog please see the dedicated [CHANGELOG.md](CHANGELOG.md). -## Roadmap to v1.0.0 -This crate is already stable, however it's based on a release candidate version of [`embedded-hal`](https://github.com/rust-embedded/embedded-hal/), -making the API unstable (the change from 1.0.0-rc.1 to 1.0.0 of e-h will be breaking from a dependency management point of view). - -See [the tracking issue](https://github.com/rust-embedded-community/tb6612fng-rs/issues/4) for the roadmap to v1.0.0. - ## Minimum Supported Rust Version (MSRV) -This crate is guaranteed to compile on stable Rust 1.62 and up. It *might* +This crate is guaranteed to compile on stable Rust 1.81 and up. It *might* compile with older versions but that may change in any new patch release. + +## License +Licensed under either of + +* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +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 +dual licensed as above, without any additional terms or conditions. diff --git a/examples/stm32f4-single-motor-example/Cargo.lock b/examples/stm32f4-single-motor-example/Cargo.lock index b561241..a114dfd 100644 --- a/examples/stm32f4-single-motor-example/Cargo.lock +++ b/examples/stm32f4-single-motor-example/Cargo.lock @@ -113,9 +113,9 @@ dependencies = [ [[package]] name = "critical-section" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" +checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" [[package]] name = "defmt" @@ -137,7 +137,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" dependencies = [ "litrs", ] @@ -220,22 +220,22 @@ checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" [[package]] name = "enumflags2" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -286,7 +286,7 @@ checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "spin", "stable_deref_trait", ] @@ -380,18 +380,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -437,9 +437,9 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver 1.0.23", ] @@ -553,9 +553,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.65" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -582,22 +582,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.77", ] [[package]] @@ -632,9 +632,9 @@ checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "void" diff --git a/src/lib.rs b/src/lib.rs index 2f2ae9d..4097271 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,9 +9,6 @@ //! * You plan on using both motors without the standby feature: use two separate [`Motor`]s //! * You plan on using a single motor with the standby feature: use [`Motor`] and control the standby pin manually //! * You plan on using a single motor without the standby feature: use [`Motor`] -//! -//! ## Optional features -//! * `defmt`: you can enable the `defmt` feature to get a `defmt::Format` implementation for all structs & enums in this crate and a `defmt::debug` call for every speed change. #![forbid(unsafe_code)] #![deny(warnings)] @@ -20,14 +17,13 @@ #![deny(unused)] #![no_std] -#[cfg(feature = "defmt")] -use defmt::Format; +use core::error::Error; +use core::fmt::{Debug, Formatter}; use embedded_hal::digital::{OutputPin, StatefulOutputPin}; use embedded_hal::pwm::SetDutyCycle; /// Defines errors which can happen when calling [`Motor::drive()`]. #[derive(PartialEq, Eq, Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(Format))] pub enum MotorError { /// An invalid speed has been defined. The speed must be given as a percentage value between 0 and 100 to be valid. InvalidSpeed, @@ -39,29 +35,64 @@ pub enum MotorError { PwmError(PWMError), } +impl core::fmt::Display + for MotorError +{ + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + use MotorError::*; + match self { + InvalidSpeed => write!(f, "an invalid speed has been specified"), + In1Error(_) => write!(f, "failed to set the output of the IN1 pin"), + In2Error(_) => write!(f, "failed to set the output of the IN2 pin"), + PwmError(_) => write!(f, "failed to set the output of the PWM pin"), + } + } +} + +impl< + IN1Error: Debug + Error + 'static, + IN2Error: Debug + Error + 'static, + PWMError: Debug + Error + 'static, + > Error for MotorError +{ + fn source(&self) -> Option<&(dyn Error + 'static)> { + use MotorError::*; + match self { + InvalidSpeed => None, + In1Error(e) => Some(e), + In2Error(e) => Some(e), + PwmError(e) => Some(e), + } + } +} + /// Defines errors which can happen when calling [`Tb6612fng::new()`]. #[derive(PartialEq, Eq, Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(Format))] -pub enum Tb6612fngError< - MAIN1Error, - MAIN2Error, - MAPWMError, - MBIN1Error, - MBIN2Error, - MBPWMError, - STBYError, -> { - /// An error in setting the initial `drive()` of `motor_a` - MotorA(MotorError), - /// An error in setting the initial `drive()` of `motor_b` - MotorB(MotorError), +pub enum Tb6612fngError { /// An error in setting the initial output of the standby pin Standby(STBYError), } +impl core::fmt::Display for Tb6612fngError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + use Tb6612fngError::*; + match self { + Standby(_) => write!(f, "failed to set the output of the standby pin"), + } + } +} + +impl Error for Tb6612fngError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + use Tb6612fngError::*; + match self { + Standby(e) => Some(e), + } + } +} + /// Defines the possible drive commands. #[derive(PartialEq, Eq, Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(Format))] pub enum DriveCommand { /// Drive forward with the defined speed (in percentage) Forward(u8), @@ -78,7 +109,6 @@ pub enum DriveCommand { /// Use the [`Motor`] struct directly if you only have one motor. /// See the crate-level comment for further details on when to use what. #[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(Format))] pub struct Tb6612fng { /// The first motor, labelled as 'A' on the chip pub motor_a: Motor, @@ -104,8 +134,14 @@ where /// The initial state of the motors will be set to [stopped](DriveCommand::Stop). /// The initial state of standby will be *disabled*. /// - /// Usage example: + /// # Errors + /// If any of the underlying pin interactions fail these errors will be propagated up. + /// The errors are specific to your HAL. + /// + /// # Usage example /// ``` + /// + /// # fn main() -> Result<(), Box> { /// # use embedded_hal_mock::eh1::digital::Mock as PinMock; /// # use embedded_hal_mock::eh1::pwm::Mock as PwmMock; /// # use embedded_hal_mock::eh1::pwm::Transaction as PwmTransaction; @@ -129,15 +165,10 @@ where /// # let standby = PinMock::new(&[PinTransaction::set(High)]); /// # let mut standby_ = standby.clone(); /// - /// use tb6612fng::Tb6612fng; - /// + /// use tb6612fng::{Motor, Tb6612fng}; /// let controller = Tb6612fng::new( - /// motor_a_in1, - /// motor_a_in2, - /// motor_a_pwm, - /// motor_b_in1, - /// motor_b_in2, - /// motor_b_pwm, + /// Motor::new(motor_a_in1, motor_a_in2, motor_a_pwm)?, + /// Motor::new(motor_b_in1, motor_b_in2, motor_b_pwm)?, /// standby, /// ); /// @@ -148,33 +179,21 @@ where /// # motor_b_in2_.done(); /// # motor_b_pwm_.done(); /// # standby_.done(); + /// # Ok(()) + /// # } /// ``` #[allow(clippy::type_complexity)] pub fn new( - motor_a_in1: MAIN1, - motor_a_in2: MAIN2, - motor_a_pwm: MAPWM, - motor_b_in1: MBIN1, - motor_b_in2: MBIN2, - motor_b_pwm: MBPWM, + motor_a: Motor, + motor_b: Motor, standby: STBY, ) -> Result< Tb6612fng, - Tb6612fngError< - MAIN1::Error, - MAIN2::Error, - MAPWM::Error, - MBIN1::Error, - MBIN2::Error, - MBPWM::Error, - STBY::Error, - >, + Tb6612fngError, > { let mut controller = Tb6612fng { - motor_a: Motor::new(motor_a_in1, motor_a_in2, motor_a_pwm) - .map_err(Tb6612fngError::MotorA)?, - motor_b: Motor::new(motor_b_in1, motor_b_in2, motor_b_pwm) - .map_err(Tb6612fngError::MotorB)?, + motor_a, + motor_b, standby, }; @@ -189,11 +208,19 @@ where /// /// Note that this does not change any commands on the motors, i.e. the PWM signal will continue /// and once [`Tb6612fng::disable_standby`] is called the motor will pick up where it left off (unless the command was changed in-between). + /// + /// # Errors + /// If the underlying pin interaction fails this error will be propagated up. + /// The error is specific to your HAL. pub fn enable_standby(&mut self) -> Result<(), STBY::Error> { self.standby.set_low() } /// Disable standby. Note that the last active commands on the motors will resume. + /// + /// # Errors + /// If the underlying pin interaction fails this error will be propagated up. + /// The error is specific to your HAL. pub fn disable_standby(&mut self) -> Result<(), STBY::Error> { self.standby.set_high() } @@ -201,6 +228,10 @@ where /// Returns whether the standby mode is enabled. /// /// *NOTE* this does *not* read the electrical state of the pin, see [`StatefulOutputPin`] + /// + /// # Errors + /// If the underlying pin interaction fails this error will be propagated up. + /// The error is specific to your HAL. pub fn current_standby(&mut self) -> Result where STBY: StatefulOutputPin, @@ -210,10 +241,10 @@ where } /// Represents a single motor (either motor A or motor B) hooked up to a TB6612FNG controller. +/// /// This is unaware of the standby pin. If you plan on using both motors and the standby feature then use the [`Tb6612fng`] struct instead. /// See the crate-level comment for further details on when to use what. #[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(Format))] pub struct Motor { in1: IN1, in2: IN2, @@ -231,7 +262,11 @@ where /// This also automatically enables the PWM pin. /// The initial state of the motor will be set to [stopped](DriveCommand::Stop). /// - /// Usage example: + /// # Errors + /// If any of the underlying pin interactions fail these errors will be propagated up. + /// The errors are specific to your HAL. + /// + /// # Usage example /// ``` /// # use embedded_hal_mock::eh1::digital::Mock as PinMock; /// # use embedded_hal_mock::eh1::pwm::Mock as PwmMock; @@ -275,6 +310,13 @@ where } /// Drive with the defined speed (or brake or stop the motor). + /// + /// # Errors + /// If the underlying pin interaction fails this error will be propagated up. + /// The error is specific to your HAL. + /// + /// The specified speed must be between 0 and 100 (inclusive), otherwise you will get a + /// [`MotorError::InvalidSpeed`] error. #[allow(clippy::type_complexity)] pub fn drive( &mut self, @@ -308,9 +350,6 @@ where } } - #[cfg(feature = "defmt")] - defmt::debug!("driving {} with speed {}", drive_command, speed); - self.pwm .set_duty_cycle_percent(speed) .map_err(MotorError::PwmError)?; @@ -330,7 +369,7 @@ where /// Return the current speed of the motor (in percentage). Note that driving forward returns a positive number /// while driving backward returns a negative number and both [`DriveCommand::Brake`] and [`DriveCommand::Stop`] return 0. /// - /// If you need to know in more details what the current status is consider calling [`Motor::current_drive_command`] instead. + /// If you need to know in more details what the current status is, consider calling [`Motor::current_drive_command`] instead. pub fn current_speed(&self) -> i8 { match self.current_drive_command() { DriveCommand::Forward(s) => *s as i8,