diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 04ee2bf..32d4dc1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: rust: [1.62.0, stable] - features: ['', '--all-features'] + features: ['', '--features defmt', '--no-default-features --features hal_v1', '--no-default-features --features defmt,hal_v1'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -34,6 +34,10 @@ jobs: run: cargo audit stm32f4-single-motor-example: + strategy: + fail-fast: false + matrix: + features: ['', '--no-default-features --features hal_v1'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -45,17 +49,17 @@ jobs: - name: Install flip-link run: cargo install flip-link - name: build - run: cargo build + run: cargo build ${{ matrix.features }} working-directory: examples/stm32f4-single-motor-example - name: check - run: cargo check + run: cargo check ${{ matrix.features }} working-directory: examples/stm32f4-single-motor-example # no tests available for now => no test step as it'd fail otherwise - name: check formatting run: cargo fmt --all -- --check working-directory: examples/stm32f4-single-motor-example - name: clippy - run: cargo clippy + run: cargo clippy ${{ matrix.features }} working-directory: examples/stm32f4-single-motor-example - name: audit run: cargo audit diff --git a/CHANGELOG.md b/CHANGELOG.md index c11b074..c653ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Added +* Support for `embedded-hal` version `1.0.0-alpha.10`. To use this you must disable the default features and instead enable the `hal_v1` feature. + ### Changed * Due to dependency updates the MSRV has been updated from 1.60 to 1.62. This should only be relevant if you use the `defmt` feature, but we now only test with 1.62 and not older releases, so it's not guaranteed to work otherwise. diff --git a/Cargo.toml b/Cargo.toml index 0fbf67d..51ebc55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,16 @@ keywords = ["tb6612fng", "driver", "motor", "controller", "embedded-hal-driver"] license = "MIT OR Apache-2.0" [dependencies] -embedded-hal = "0.2" +embedded-hal = { version = "0.2", optional = true } +embedded-hal-one = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true } defmt = { version = "0.3", optional = true } [dev-dependencies] -embedded-hal-mock = "0.9" +embedded-hal-mock = { version = "0.9" } +embedded-hal-mock-one = { package = "embedded-hal-mock", git = "https://github.com/rursprung/embedded-hal-mock.git", branch = "add-SetDutyCycle-to-1-alpha-a11" } + +[features] +default = ["hal_v02"] +hal_v02 = ["dep:embedded-hal"] +hal_v1 = ["dep:embedded-hal-one"] diff --git a/README.md b/README.md index c2acd30..ec310c0 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ 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` +## `embedded-hal`: `v0.2` vs. `v1.0.0-alpha.*` +This crate can be used both with `embedded-hal` `v0.2` versions as well as `v1.0.0` pre-releases. +By default, the `v0.2` support is being compiled, but you can switch to the `v1.0.0` pre-release by disabling the +default features and instead enabling the optional `hal-v1`. + ## Optional features * `defmt`: you can enable the [`defmt`](https://defmt.ferrous-systems.com/) feature to get a `defmt::debug!` call for every speed change. diff --git a/examples/stm32f4-single-motor-example/Cargo.lock b/examples/stm32f4-single-motor-example/Cargo.lock index 3c1f544..53bc80a 100644 --- a/examples/stm32f4-single-motor-example/Cargo.lock +++ b/examples/stm32f4-single-motor-example/Cargo.lock @@ -186,10 +186,17 @@ dependencies = [ [[package]] name = "embedded-hal" -version = "1.0.0-alpha.8" +version = "1.0.0-alpha.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3babfc7fd332142a0b11aebf592992f211f4e01b6222fb04b03aba1bd80018d" +checksum = "f7724ebabcadfeb15920571dd727bc8ccde8586e52f2890bdb8182fdf42c3ff2" + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a09e4c3f8a54e60803405e1cc17e36c963ab32e654f8d6bb49d48cd8116360d7" dependencies = [ + "embedded-hal 1.0.0-alpha.11", "nb 1.1.0", ] @@ -456,9 +463,8 @@ dependencies = [ [[package]] name = "stm32f4xx-hal" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eaaf3fec476e723cd6e1b8033fc9e5c89520b7e907a6fd95726cfec7366376" +version = "0.16.2" +source = "git+https://github.com/stm32-rs/stm32f4xx-hal.git?rev=552a9c5#552a9c547a0c1dd86b7282f2d6717784158c846b" dependencies = [ "bare-metal 1.0.0", "bitflags 2.2.1", @@ -466,7 +472,8 @@ dependencies = [ "cortex-m-rt", "embedded-dma", "embedded-hal 0.2.7", - "embedded-hal 1.0.0-alpha.8", + "embedded-hal 1.0.0-alpha.11", + "embedded-hal-nb", "embedded-storage", "fugit", "fugit-timer", @@ -516,7 +523,9 @@ dependencies = [ name = "tb6612fng" version = "0.1.0" dependencies = [ + "defmt", "embedded-hal 0.2.7", + "embedded-hal 1.0.0-alpha.11", ] [[package]] diff --git a/examples/stm32f4-single-motor-example/Cargo.toml b/examples/stm32f4-single-motor-example/Cargo.toml index 3f27ef2..172ca79 100644 --- a/examples/stm32f4-single-motor-example/Cargo.toml +++ b/examples/stm32f4-single-motor-example/Cargo.toml @@ -4,18 +4,24 @@ version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" +[features] +default = [ "hal_v02" ] +hal_v02 = [ "tb6612fng/hal_v02" ] +hal_v1 = [ "tb6612fng/hal_v1" ] + [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"]} cortex-m-rtic = "1.1.4" panic-probe = { version = "0.3", features = ["print-defmt"] } -stm32f4xx-hal = { version = "0.16.1", features = ["stm32f401", "rtic", "rtic-monotonic"] } +# TODO: switch back to release version once it exists +stm32f4xx-hal = { git = "https://github.com/stm32-rs/stm32f4xx-hal.git", rev = "552a9c5", features = ["stm32f401", "rtic", "rtic-monotonic"] } defmt = "0.3.5" defmt-rtt = "0.4" # use `tb6612fng = "0.1"` in reality; path used here to ensure that the example always compiles against the latest master -tb6612fng = { path = "../.." } +tb6612fng = { path = "../..", default-features = false, features = ["defmt"] } [profile.release] codegen-units = 1 diff --git a/examples/stm32f4-single-motor-example/README.md b/examples/stm32f4-single-motor-example/README.md index 37bfb6a..70e8fe9 100644 --- a/examples/stm32f4-single-motor-example/README.md +++ b/examples/stm32f4-single-motor-example/README.md @@ -29,3 +29,5 @@ to stop (coast, first press), actively brake (second press) and drive again (thi 1. Connect the board via USB 2. Run `cargo run` (the correct chip & target is already defined in `Cargo.toml` and `.cargo/config`) 3. Enjoy your running program :) + +To instead compile & run this with `embedded-hal` v1.0.0 pre-release support you can build it with `cargo run --no-default-features --features hal_v1`. diff --git a/examples/stm32f4-single-motor-example/src/main.rs b/examples/stm32f4-single-motor-example/src/main.rs index f24e0a4..0c0c70c 100644 --- a/examples/stm32f4-single-motor-example/src/main.rs +++ b/examples/stm32f4-single-motor-example/src/main.rs @@ -51,6 +51,10 @@ mod app { .TIM2 .pwm_hz(Channel3::new(gpiob.pb10), 100.kHz(), &clocks) .split(); + #[cfg(feature = "hal_v1")] + let mut motor_pwm = motor_pwm; + #[cfg(feature = "hal_v1")] + motor_pwm.enable(); let mut motor = Motor::new(motor_in1, motor_in2, motor_pwm); motor.drive_backwards(0).expect(""); diff --git a/src/lib.rs b/src/lib.rs index 2818818..7c4320b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,16 +14,35 @@ //! * `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)] -#![forbid(warnings)] +#![deny(warnings)] #![forbid(missing_docs)] #![forbid(missing_debug_implementations)] -#![forbid(unused)] +#![deny(unused)] #![no_std] #[cfg(feature = "defmt")] use defmt::Format; +#[cfg(feature = "hal_v02")] use embedded_hal::digital::v2::OutputPin; -use embedded_hal::PwmPin; + +/// hacky workaround to make the compiler happy. see the underlying [`embedded_hal::PwmPin`] for further details. +#[cfg(feature = "hal_v02")] +pub trait PwmPin: embedded_hal::PwmPin {} + +#[cfg(feature = "hal_v02")] +impl PwmPin for U where U: embedded_hal::PwmPin {} + +#[cfg(feature = "hal_v1")] +use embedded_hal_one::digital::OutputPin; +#[cfg(feature = "hal_v1")] +use embedded_hal_one::pwm::SetDutyCycle as PwmPin; + +#[cfg(not(any(feature = "hal_v02", feature = "hal_v1")))] +compile_error!("either the embedded-hal v0.2 or v1.0 implementation must be selected!"); +#[cfg(all(feature = "hal_v02", feature = "hal_v1"))] +compile_error!( + "the embedded-hal v0.2 or v1.0 implementations cannot both be selected at the same time!" +); /// Defines errors which can happen while trying to set a speed. #[derive(PartialEq, Eq, Debug, Copy, Clone)] @@ -67,10 +86,10 @@ impl where MAIN1: OutputPin, MAIN2: OutputPin, - MAPWM: PwmPin, + MAPWM: PwmPin, MBIN1: OutputPin, MBIN2: OutputPin, - MBPWM: PwmPin, + MBPWM: PwmPin, STBY: OutputPin, { /// Instantiate a new [`Tb6612fng`] with the defined pins. @@ -79,16 +98,24 @@ where /// /// Usage example: /// ``` - /// # use embedded_hal_mock::pin::Mock as PinMock; - /// # use embedded_hal_mock::pin::Transaction as PinTransaction; + /// # #[cfg(feature = "hal_v02")] + /// # use embedded_hal_mock::pin::{Mock as PinMock, Transaction as PinTransaction}; + /// # #[cfg(feature = "hal_v1")] + /// # use embedded_hal_mock_one::pin::{Mock as PinMock, Transaction as PinTransaction}; + /// # #[cfg(feature = "hal_v1")] + /// # use embedded_hal_mock_one::pwm::{Mock as PwmMock, Transaction as PwmTransaction}; /// # let motor_a_in1 = PinMock::new([]); /// # let motor_a_in2 = PinMock::new([]); - /// # let motor_a_pwm_expectations = [PinTransaction::enable()]; - /// # let motor_a_pwm = PinMock::new(&motor_a_pwm_expectations); + /// # #[cfg(feature = "hal_v02")] + /// # let motor_a_pwm = PinMock::new(&[PinTransaction::enable()]); + /// # #[cfg(feature = "hal_v1")] + /// # let motor_a_pwm = PwmMock::new([]); /// # let motor_b_in1 = PinMock::new([]); /// # let motor_b_in2 = PinMock::new([]); - /// # let motor_b_pwm_expectations = [PinTransaction::enable()]; - /// # let motor_b_pwm = PinMock::new(&motor_a_pwm_expectations); + /// # #[cfg(feature = "hal_v02")] + /// # let motor_b_pwm = PinMock::new(&[PinTransaction::enable()]); + /// # #[cfg(feature = "hal_v1")] + /// # let motor_b_pwm = PwmMock::new([]); /// # let standby = PinMock::new([]); /// use tb6612fng::Tb6612fng; /// @@ -148,7 +175,7 @@ impl Motor where IN1: OutputPin, IN2: OutputPin, - PWM: PwmPin, + PWM: PwmPin, { /// Instantiate a new [`Motor`] with the defined pins. /// This also automatically enables the PWM pin. @@ -156,12 +183,18 @@ where /// /// Usage example: /// ``` - /// # use embedded_hal_mock::pin::Mock as PinMock; - /// # use embedded_hal_mock::pin::Transaction as PinTransaction; + /// # #[cfg(feature = "hal_v02")] + /// # use embedded_hal_mock::pin::{Mock as PinMock, Transaction as PinTransaction}; + /// # #[cfg(feature = "hal_v1")] + /// # use embedded_hal_mock_one::pin::{Mock as PinMock, Transaction as PinTransaction}; + /// # #[cfg(feature = "hal_v1")] + /// # use embedded_hal_mock_one::pwm::{Mock as PwmMock, Transaction as PwmTransaction}; /// # let motor_in1 = PinMock::new([]); /// # let motor_in2 = PinMock::new([]); - /// # let motor_pwm_expectations = [PinTransaction::enable()]; - /// # let motor_pwm = PinMock::new(&motor_pwm_expectations); + /// # #[cfg(feature = "hal_v02")] + /// # let motor_pwm = PinMock::new(&[PinTransaction::enable()]); + /// # #[cfg(feature = "hal_v1")] + /// # let motor_pwm = PwmMock::new(&[]); /// use tb6612fng::Motor; /// /// let motor = Motor::new( @@ -170,7 +203,9 @@ where /// motor_pwm, /// ); /// ``` + #[cfg_attr(feature = "hal_v1", allow(unused_mut))] pub fn new(in1: IN1, in2: IN2, mut pwm: PWM) -> Motor { + #[cfg(feature = "hal_v02")] pwm.enable(); Motor { in1, @@ -232,19 +267,29 @@ where } } - let max_duty = self.pwm.get_max_duty(); - - let duty = (speed as f32 * (max_duty as f32 / 100.0)) as u16; // speed given in percentage - - #[cfg(feature = "defmt")] - defmt::debug!( - "driving {} with duty {} (max duty: {})", - drive_command, - duty, - max_duty - ); + #[cfg(feature = "hal_v02")] + { + let max_duty = self.pwm.get_max_duty(); + let duty = (speed as f32 * (max_duty as f32 / 100.0)) as u16; + // speed given in percentage + #[cfg(feature = "defmt")] + defmt::debug!( + "driving {} with duty {} (max duty: {})", + drive_command, + duty, + max_duty + ); + self.pwm.set_duty(duty); + } - self.pwm.set_duty(duty); + #[cfg(feature = "hal_v1")] + { + #[cfg(feature = "defmt")] + defmt::debug!("driving {} with speed {}", drive_command, speed); + self.pwm + .set_duty_cycle_percent(speed) + .map_err(|_| DriveError::InvalidSpeed)?; + } self.current_drive_command = drive_command; @@ -275,30 +320,46 @@ where #[cfg(test)] mod tests { use crate::{DriveCommand, DriveError, Motor}; - use embedded_hal_mock::pin::State::{High, Low}; - use embedded_hal_mock::pin::Transaction as PinTransaction; - use embedded_hal_mock::pin::{Mock as PinMock, PwmDuty}; + #[cfg(feature = "hal_v02")] + use embedded_hal_mock::pin::{ + Mock as PinMock, Mock as PwmMock, PwmDuty, State::*, Transaction as PinTransaction, + }; + #[cfg(feature = "hal_v1")] + use embedded_hal_mock_one::{ + pin::{Mock as PinMock, State::*, Transaction as PinTransaction}, + pwm::{Mock as PwmMock, Transaction as PwmTransaction}, + }; #[test] fn test_motor_stop() { let max_duty = 100; let motor_in1_expectations = [PinTransaction::set(Low)]; let motor_in2_expectations = [PinTransaction::set(Low)]; + #[cfg(feature = "hal_v02")] let motor_pwm_expectations = [ PinTransaction::enable(), PinTransaction::get_max_duty(max_duty), PinTransaction::set_duty(0), ]; - let motor_in1 = PinMock::new(&motor_in1_expectations); - let motor_in2 = PinMock::new(&motor_in2_expectations); - let motor_pwm = PinMock::new(&motor_pwm_expectations); + #[cfg(feature = "hal_v1")] + let motor_pwm_expectations = [ + PwmTransaction::get_max_duty_cycle(max_duty), + PwmTransaction::set_duty_cycle(0), + ]; + let mut motor_in1 = PinMock::new(&motor_in1_expectations); + let mut motor_in2 = PinMock::new(&motor_in2_expectations); + let mut motor_pwm = PwmMock::new(&motor_pwm_expectations); - let mut motor = Motor::new(motor_in1, motor_in2, motor_pwm); + let mut motor = Motor::new(motor_in1.clone(), motor_in2.clone(), motor_pwm.clone()); motor.stop(); assert_eq!(*motor.current_drive_command(), DriveCommand::Stop); assert_eq!(motor.current_speed(), 0); + + motor_in1.done(); + motor_in2.done(); + motor_pwm.done(); } #[test] @@ -306,21 +367,31 @@ mod tests { let max_duty = 100; let motor_in1_expectations = [PinTransaction::set(High)]; let motor_in2_expectations = [PinTransaction::set(High)]; + #[cfg(feature = "hal_v02")] let motor_pwm_expectations = [ PinTransaction::enable(), PinTransaction::get_max_duty(max_duty), PinTransaction::set_duty(0), ]; - let motor_in1 = PinMock::new(&motor_in1_expectations); - let motor_in2 = PinMock::new(&motor_in2_expectations); - let motor_pwm = PinMock::new(&motor_pwm_expectations); + #[cfg(feature = "hal_v1")] + let motor_pwm_expectations = [ + PwmTransaction::get_max_duty_cycle(max_duty), + PwmTransaction::set_duty_cycle(0), + ]; + let mut motor_in1 = PinMock::new(&motor_in1_expectations); + let mut motor_in2 = PinMock::new(&motor_in2_expectations); + let mut motor_pwm = PwmMock::new(&motor_pwm_expectations); - let mut motor = Motor::new(motor_in1, motor_in2, motor_pwm); + let mut motor = Motor::new(motor_in1.clone(), motor_in2.clone(), motor_pwm.clone()); motor.brake(); assert_eq!(*motor.current_drive_command(), DriveCommand::Brake); assert_eq!(motor.current_speed(), 0); + + motor_in1.done(); + motor_in2.done(); + motor_pwm.done(); } #[test] @@ -329,21 +400,31 @@ mod tests { let speed: u8 = 100; let motor_in1_expectations = [PinTransaction::set(High)]; let motor_in2_expectations = [PinTransaction::set(Low)]; + #[cfg(feature = "hal_v02")] let motor_pwm_expectations = [ PinTransaction::enable(), PinTransaction::get_max_duty(max_duty), PinTransaction::set_duty(speed as PwmDuty), ]; - let motor_in1 = PinMock::new(&motor_in1_expectations); - let motor_in2 = PinMock::new(&motor_in2_expectations); - let motor_pwm = PinMock::new(&motor_pwm_expectations); + #[cfg(feature = "hal_v1")] + let motor_pwm_expectations = [ + PwmTransaction::get_max_duty_cycle(max_duty), + PwmTransaction::set_duty_cycle(speed as u16), + ]; + let mut motor_in1 = PinMock::new(&motor_in1_expectations); + let mut motor_in2 = PinMock::new(&motor_in2_expectations); + let mut motor_pwm = PwmMock::new(&motor_pwm_expectations); - let mut motor = Motor::new(motor_in1, motor_in2, motor_pwm); + let mut motor = Motor::new(motor_in1.clone(), motor_in2.clone(), motor_pwm.clone()); motor.drive_forward(speed).expect("speed can be set"); assert_eq!(*motor.current_drive_command(), DriveCommand::Forward(100)); assert_eq!(motor.current_speed(), speed as i8); + + motor_in1.done(); + motor_in2.done(); + motor_pwm.done(); } #[test] @@ -352,38 +433,46 @@ mod tests { let speed: u8 = 100; let motor_in1_expectations = [PinTransaction::set(Low)]; let motor_in2_expectations = [PinTransaction::set(High)]; + #[cfg(feature = "hal_v02")] let motor_pwm_expectations = [ PinTransaction::enable(), PinTransaction::get_max_duty(max_duty), PinTransaction::set_duty(speed as PwmDuty), ]; - let motor_in1 = PinMock::new(&motor_in1_expectations); - let motor_in2 = PinMock::new(&motor_in2_expectations); - let motor_pwm = PinMock::new(&motor_pwm_expectations); + #[cfg(feature = "hal_v1")] + let motor_pwm_expectations = [ + PwmTransaction::get_max_duty_cycle(max_duty), + PwmTransaction::set_duty_cycle(speed as u16), + ]; + let mut motor_in1 = PinMock::new(&motor_in1_expectations); + let mut motor_in2 = PinMock::new(&motor_in2_expectations); + let mut motor_pwm = PwmMock::new(&motor_pwm_expectations); - let mut motor = Motor::new(motor_in1, motor_in2, motor_pwm); + let mut motor = Motor::new(motor_in1.clone(), motor_in2.clone(), motor_pwm.clone()); motor.drive_backwards(speed).expect("speed can be set"); assert_eq!(*motor.current_drive_command(), DriveCommand::Backwards(100)); assert_eq!(motor.current_speed(), -(speed as i8)); + + motor_in1.done(); + motor_in2.done(); + motor_pwm.done(); } #[test] fn test_motor_drive_invalid_speed() { - let max_duty = 100; - let motor_in1_expectations = [PinTransaction::set(Low)]; - let motor_in2_expectations = [PinTransaction::set(High)]; - let motor_pwm_expectations = [ - PinTransaction::enable(), - PinTransaction::get_max_duty(max_duty), - PinTransaction::set_duty(100), - ]; - let motor_in1 = PinMock::new(&motor_in1_expectations); - let motor_in2 = PinMock::new(&motor_in2_expectations); - let motor_pwm = PinMock::new(&motor_pwm_expectations); - - let mut motor = Motor::new(motor_in1, motor_in2, motor_pwm); + let motor_in1_expectations = []; + let motor_in2_expectations = []; + #[cfg(feature = "hal_v02")] + let motor_pwm_expectations = [PinTransaction::enable()]; + #[cfg(feature = "hal_v1")] + let motor_pwm_expectations = []; + let mut motor_in1 = PinMock::new(&motor_in1_expectations); + let mut motor_in2 = PinMock::new(&motor_in2_expectations); + let mut motor_pwm = PwmMock::new(&motor_pwm_expectations); + + let mut motor = Motor::new(motor_in1.clone(), motor_in2.clone(), motor_pwm.clone()); let current_drive_command = motor.current_drive_command().clone(); let current_speed = motor.current_speed(); @@ -398,5 +487,9 @@ mod tests { // this should still be what was set before the invalid command assert_eq!(*motor.current_drive_command(), current_drive_command); assert_eq!(motor.current_speed(), current_speed); + + motor_in1.done(); + motor_in2.done(); + motor_pwm.done(); } }