From 6dd58fd0e08d819153b77e585ca36853b4cfd773 Mon Sep 17 00:00:00 2001 From: Rafa Couto Date: Sun, 14 Apr 2024 23:49:58 +0200 Subject: [PATCH 1/7] EncoderController trait --- Cargo.toml | 1 + libs/hal_encoder/Cargo.toml | 8 +++ libs/hal_encoder/src/lib.rs | 111 ++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 libs/hal_encoder/Cargo.toml create mode 100644 libs/hal_encoder/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index dfc4d90..42b3006 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "libs/engine", "libs/timer_based_buzzer_interface", "libs/hal_button", + "libs/hal_encoder", "apps/hello_world", ] diff --git a/libs/hal_encoder/Cargo.toml b/libs/hal_encoder/Cargo.toml new file mode 100644 index 0000000..b2ba10e --- /dev/null +++ b/libs/hal_encoder/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hal_encoder" +description = "Abstractions for an encoder controller" +version = "0.1.0" +authors = ["Rafa Couto "] +edition = "2021" + +[dependencies] diff --git a/libs/hal_encoder/src/lib.rs b/libs/hal_encoder/src/lib.rs new file mode 100644 index 0000000..9963fe5 --- /dev/null +++ b/libs/hal_encoder/src/lib.rs @@ -0,0 +1,111 @@ +#![no_std] + +pub trait EncoderController { + // This function returns the absolute step count + fn read_steps(&self) -> u16; + + // This function returns a mutable reference to store the last step count + fn last_steps_ref(&mut self) -> &mut u16; + + // This function resets the step count to zero. + fn reset(&mut self); + + // This function returns the delta of the step count since the last time this function was called. + fn delta(&mut self) -> isize { + let steps = self.read_steps(); + let last_steps = self.last_steps_ref(); + let mut delta = steps as isize - *last_steps as isize; + if steps >> 15 != *last_steps >> 15 { + delta += match steps > *last_steps { + true => -(1 << 16), // u16 underflow + false => 1 << 16, // u16 overflow + } + }; + *last_steps = steps; + delta + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct MockEncoderController { + pub steps: u16, + pub last_steps: u16, + } + + impl MockEncoderController { + pub fn new() -> Self { + MockEncoderController { + steps: 0, + last_steps: 0, + } + } + } + + impl EncoderController for MockEncoderController { + fn read_steps(&self) -> u16 { + self.steps + } + + fn last_steps_ref(&mut self) -> &mut u16 { + &mut self.last_steps + } + + fn reset(&mut self) { + self.steps = 0; + self.last_steps = 0; + } + } + + #[test] + fn test_delta() { + // initial state + let mut encoder = MockEncoderController::new(); + + // no movement + assert_eq!(encoder.delta(), 0); + + // advance 1 step + encoder.steps = 1; + assert_eq!(encoder.delta(), 1); + + // advance 10 steps + encoder.steps = 11; + assert_eq!(encoder.delta(), 10); + + // back 5 steps + encoder.steps = 6; + assert_eq!(encoder.delta(), -5); + } + + #[test] + fn test_overflow() { + let mut encoder = MockEncoderController::new(); + + // overflow with 16-bit encoder + encoder.last_steps = u16::MAX; + encoder.steps = u16::MIN; + assert_eq!(1, encoder.delta()); + + // overflow with 16-bit encoder (> 1 step) + encoder.last_steps = u16::MAX - 10; + encoder.steps = u16::MIN + 10; + assert_eq!(21, encoder.delta()); + } + + #[test] + fn test_underflow() { + let mut encoder = MockEncoderController::new(); + // underflow with 16-bit encoder (1 step) + encoder.last_steps = u16::MIN; + encoder.steps = u16::MAX; + assert_eq!(-1, encoder.delta()); + + // underflow with 16-bit encoder (> 1 step) + encoder.last_steps = u16::MIN + 10; + encoder.steps = u16::MAX - 10; + assert_eq!(-21, encoder.delta()); + } +} From a1a60c1c5667cdb105856d26089c7737437fa38c Mon Sep 17 00:00:00 2001 From: Rafa Couto Date: Mon, 15 Apr 2024 02:08:51 +0200 Subject: [PATCH 2/7] EncoderController parameterized to different bit precisions --- libs/hal_encoder/src/lib.rs | 105 +++------------------- libs/hal_encoder/src/tests.rs | 163 ++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 91 deletions(-) create mode 100644 libs/hal_encoder/src/tests.rs diff --git a/libs/hal_encoder/src/lib.rs b/libs/hal_encoder/src/lib.rs index 9963fe5..b18dd0b 100644 --- a/libs/hal_encoder/src/lib.rs +++ b/libs/hal_encoder/src/lib.rs @@ -1,24 +1,28 @@ #![no_std] -pub trait EncoderController { - // This function returns the absolute step count - fn read_steps(&self) -> u16; - - // This function returns a mutable reference to store the last step count - fn last_steps_ref(&mut self) -> &mut u16; +pub trait EncoderController { + // This function returns the absolute step count. + fn read_steps(&self) -> usize; // This function resets the step count to zero. fn reset(&mut self); + // This function returns a mutable reference to store the last step count. + // It is used to save the state and calculate the delta. + fn last_steps_ref(&mut self) -> &mut usize; + + // MSB_MASK is used to detect overflow and underflow when the most significant bit changes. + const MSB_MASK : usize = 1 << (BITS - 1); + // This function returns the delta of the step count since the last time this function was called. fn delta(&mut self) -> isize { let steps = self.read_steps(); let last_steps = self.last_steps_ref(); let mut delta = steps as isize - *last_steps as isize; - if steps >> 15 != *last_steps >> 15 { + if steps & Self::MSB_MASK != *last_steps & Self::MSB_MASK { delta += match steps > *last_steps { - true => -(1 << 16), // u16 underflow - false => 1 << 16, // u16 overflow + true => -(1 << BITS), // underflow + false => 1 << BITS, // overflow } }; *last_steps = steps; @@ -27,85 +31,4 @@ pub trait EncoderController { } #[cfg(test)] -mod tests { - use super::*; - - struct MockEncoderController { - pub steps: u16, - pub last_steps: u16, - } - - impl MockEncoderController { - pub fn new() -> Self { - MockEncoderController { - steps: 0, - last_steps: 0, - } - } - } - - impl EncoderController for MockEncoderController { - fn read_steps(&self) -> u16 { - self.steps - } - - fn last_steps_ref(&mut self) -> &mut u16 { - &mut self.last_steps - } - - fn reset(&mut self) { - self.steps = 0; - self.last_steps = 0; - } - } - - #[test] - fn test_delta() { - // initial state - let mut encoder = MockEncoderController::new(); - - // no movement - assert_eq!(encoder.delta(), 0); - - // advance 1 step - encoder.steps = 1; - assert_eq!(encoder.delta(), 1); - - // advance 10 steps - encoder.steps = 11; - assert_eq!(encoder.delta(), 10); - - // back 5 steps - encoder.steps = 6; - assert_eq!(encoder.delta(), -5); - } - - #[test] - fn test_overflow() { - let mut encoder = MockEncoderController::new(); - - // overflow with 16-bit encoder - encoder.last_steps = u16::MAX; - encoder.steps = u16::MIN; - assert_eq!(1, encoder.delta()); - - // overflow with 16-bit encoder (> 1 step) - encoder.last_steps = u16::MAX - 10; - encoder.steps = u16::MIN + 10; - assert_eq!(21, encoder.delta()); - } - - #[test] - fn test_underflow() { - let mut encoder = MockEncoderController::new(); - // underflow with 16-bit encoder (1 step) - encoder.last_steps = u16::MIN; - encoder.steps = u16::MAX; - assert_eq!(-1, encoder.delta()); - - // underflow with 16-bit encoder (> 1 step) - encoder.last_steps = u16::MIN + 10; - encoder.steps = u16::MAX - 10; - assert_eq!(-21, encoder.delta()); - } -} +mod tests; diff --git a/libs/hal_encoder/src/tests.rs b/libs/hal_encoder/src/tests.rs new file mode 100644 index 0000000..f12e861 --- /dev/null +++ b/libs/hal_encoder/src/tests.rs @@ -0,0 +1,163 @@ +use super::*; + +struct MockEncoder { + pub steps: usize, + pub last_steps: usize, +} + +impl MockEncoder { + pub fn get_max_val(&self) -> usize { + (1 << BITS) - 1 + } + + pub fn get_min_val(&self) -> usize { + 0 + } + + pub fn simulate_move(&mut self, from: usize, to: usize) { + self.last_steps = from; + self.steps = to; + } +} + +impl Default for MockEncoder { + fn default() -> Self { + Self { + steps: 0, + last_steps: 0, + } + } +} + +impl EncoderController for MockEncoder { + fn read_steps(&self) -> usize { + self.steps + } + + fn last_steps_ref(&mut self) -> &mut usize { + &mut self.last_steps + } + + fn reset(&mut self) { + self.steps = 0; + self.last_steps = 0; + } +} + +#[test] +fn delta_16bit() { + // initial state + let mut encoder: MockEncoder<16> = Default::default(); + + // no movement + assert_eq!(encoder.delta(), 0); + + // advance 1 step + encoder.steps = 1; + assert_eq!(encoder.delta(), 1); + + // advance 10 steps + encoder.steps = 11; + assert_eq!(encoder.delta(), 10); + + // back 5 steps + encoder.steps = 6; + assert_eq!(encoder.delta(), -5); +} + +#[test] +fn delta_10bit() { + // initial state + let mut encoder: MockEncoder<10> = Default::default(); + + // no movement + assert_eq!(encoder.delta(), 0); + + // advance 1 step + encoder.steps = 1; + assert_eq!(encoder.delta(), 1); + + // advance 10 steps + encoder.steps = 11; + assert_eq!(encoder.delta(), 10); + + // back 5 steps + encoder.steps = 6; + assert_eq!(encoder.delta(), -5); +} + +#[test] +fn overflow_16bit() { + let mut encoder: MockEncoder<16> = Default::default(); + + // max and min values for 16-bit encoder + let min = encoder.get_min_val(); + assert_eq!(0, min); + let max = encoder.get_max_val(); + assert_eq!(65535, max); + + // overflow step with 16-bit encoder + encoder.simulate_move(max, min); + assert_eq!(1, encoder.delta()); + + // overflow with 16-bit encoder (> 1 step) + encoder.simulate_move(max - 10, min + 10); + assert_eq!(21, encoder.delta()); +} + +#[test] +fn overflow_10bit() { + let mut encoder: MockEncoder<10> = Default::default(); + + // max and min values for 10-bit encoder + let min = encoder.get_min_val(); + assert_eq!(0, min); + let max = encoder.get_max_val(); + assert_eq!(1023, max); + + // overflow with 10-bit encoder + encoder.simulate_move(max, min); + assert_eq!(1, encoder.delta()); + + // overflow with 10-bit encoder (> 1 step) + encoder.simulate_move(max - 10, min + 10); + assert_eq!(21, encoder.delta()); +} + +#[test] +fn underflow_16bit() { + let mut encoder: MockEncoder<16> = Default::default(); + + // max and min values for 16-bit encoder + let min = encoder.get_min_val(); + assert_eq!(0, min); + let max = encoder.get_max_val(); + assert_eq!(65535, max); + + // underflow with 16-bit encoder (1 step) + encoder.simulate_move(min, max); + assert_eq!(-1, encoder.delta()); + + // underflow with 16-bit encoder (> 1 step) + encoder.simulate_move(min + 10, max - 10); + assert_eq!(-21, encoder.delta()); +} + +#[test] +fn underflow_10bit() { + let mut encoder: MockEncoder<10> = Default::default(); + + // max and min values for 10-bit encoder + let min = encoder.get_min_val(); + assert_eq!(0, min); + let max = encoder.get_max_val(); + assert_eq!(1023, max); + + // underflow with 10-bit encoder (1 step) + encoder.simulate_move(min, max); + assert_eq!(-1, encoder.delta()); + + // underflow with 10-bit encoder (> 1 step) + encoder.simulate_move(min + 10, max - 10); + assert_eq!(-21, encoder.delta()); +} From 15159f22d3638bb803ca1e3a80a8108bbe78c4da Mon Sep 17 00:00:00 2001 From: Rafa Couto Date: Mon, 15 Apr 2024 13:03:52 +0200 Subject: [PATCH 3/7] Function names changed to match issue requirements --- libs/hal_encoder/src/lib.rs | 6 +++--- libs/hal_encoder/src/tests.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/hal_encoder/src/lib.rs b/libs/hal_encoder/src/lib.rs index b18dd0b..3f2529a 100644 --- a/libs/hal_encoder/src/lib.rs +++ b/libs/hal_encoder/src/lib.rs @@ -2,13 +2,13 @@ pub trait EncoderController { // This function returns the absolute step count. - fn read_steps(&self) -> usize; + fn steps(&self) -> usize; // This function resets the step count to zero. fn reset(&mut self); // This function returns a mutable reference to store the last step count. - // It is used to save the state and calculate the delta. + // It is implemented to save the state and calculate the delta. fn last_steps_ref(&mut self) -> &mut usize; // MSB_MASK is used to detect overflow and underflow when the most significant bit changes. @@ -16,7 +16,7 @@ pub trait EncoderController { // This function returns the delta of the step count since the last time this function was called. fn delta(&mut self) -> isize { - let steps = self.read_steps(); + let steps = self.steps(); let last_steps = self.last_steps_ref(); let mut delta = steps as isize - *last_steps as isize; if steps & Self::MSB_MASK != *last_steps & Self::MSB_MASK { diff --git a/libs/hal_encoder/src/tests.rs b/libs/hal_encoder/src/tests.rs index f12e861..a7895c0 100644 --- a/libs/hal_encoder/src/tests.rs +++ b/libs/hal_encoder/src/tests.rs @@ -30,7 +30,7 @@ impl Default for MockEncoder { } impl EncoderController for MockEncoder { - fn read_steps(&self) -> usize { + fn steps(&self) -> usize { self.steps } From f7757851292d8930d04f7d6b38da3c88db104b59 Mon Sep 17 00:00:00 2001 From: Rafa Couto Date: Tue, 30 Apr 2024 03:08:25 +0200 Subject: [PATCH 4/7] Incremental encoder with timers TIM2-TIM5 in STM32F1xx --- Cargo.toml | 2 +- libs/hal_encoder/Cargo.toml | 2 +- libs/hal_encoder/src/lib.rs | 4 +- libs/hal_encoder/src/tests.rs | 32 +++--- libs/hal_encoder_stm32f1xx/Cargo.toml | 10 ++ libs/hal_encoder_stm32f1xx/src/lib.rs | 14 +++ .../hal_encoder_stm32f1xx/src/tim2_to_tim5.rs | 102 ++++++++++++++++++ 7 files changed, 146 insertions(+), 20 deletions(-) create mode 100644 libs/hal_encoder_stm32f1xx/Cargo.toml create mode 100644 libs/hal_encoder_stm32f1xx/src/lib.rs create mode 100644 libs/hal_encoder_stm32f1xx/src/tim2_to_tim5.rs diff --git a/Cargo.toml b/Cargo.toml index 42b3006..a3b0b5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "libs/engine", "libs/timer_based_buzzer_interface", "libs/hal_button", - "libs/hal_encoder", + "libs/hal_encoder_stm32f1xx", "apps/hello_world", ] diff --git a/libs/hal_encoder/Cargo.toml b/libs/hal_encoder/Cargo.toml index b2ba10e..cbf1603 100644 --- a/libs/hal_encoder/Cargo.toml +++ b/libs/hal_encoder/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hal_encoder" +name = "hal-encoder" description = "Abstractions for an encoder controller" version = "0.1.0" authors = ["Rafa Couto "] diff --git a/libs/hal_encoder/src/lib.rs b/libs/hal_encoder/src/lib.rs index 3f2529a..48b41c8 100644 --- a/libs/hal_encoder/src/lib.rs +++ b/libs/hal_encoder/src/lib.rs @@ -15,7 +15,7 @@ pub trait EncoderController { const MSB_MASK : usize = 1 << (BITS - 1); // This function returns the delta of the step count since the last time this function was called. - fn delta(&mut self) -> isize { + fn delta(&mut self) -> (isize, isize) { let steps = self.steps(); let last_steps = self.last_steps_ref(); let mut delta = steps as isize - *last_steps as isize; @@ -26,7 +26,7 @@ pub trait EncoderController { } }; *last_steps = steps; - delta + (delta, steps as isize) } } diff --git a/libs/hal_encoder/src/tests.rs b/libs/hal_encoder/src/tests.rs index a7895c0..9e45765 100644 --- a/libs/hal_encoder/src/tests.rs +++ b/libs/hal_encoder/src/tests.rs @@ -50,19 +50,19 @@ fn delta_16bit() { let mut encoder: MockEncoder<16> = Default::default(); // no movement - assert_eq!(encoder.delta(), 0); + assert_eq!(0, encoder.delta().0); // advance 1 step encoder.steps = 1; - assert_eq!(encoder.delta(), 1); + assert_eq!(1, encoder.delta().0); // advance 10 steps encoder.steps = 11; - assert_eq!(encoder.delta(), 10); + assert_eq!(10, encoder.delta().0); // back 5 steps encoder.steps = 6; - assert_eq!(encoder.delta(), -5); + assert_eq!(-5, encoder.delta().0); } #[test] @@ -71,19 +71,19 @@ fn delta_10bit() { let mut encoder: MockEncoder<10> = Default::default(); // no movement - assert_eq!(encoder.delta(), 0); + assert_eq!(0, encoder.delta().0); // advance 1 step encoder.steps = 1; - assert_eq!(encoder.delta(), 1); + assert_eq!(1, encoder.delta().0); // advance 10 steps encoder.steps = 11; - assert_eq!(encoder.delta(), 10); + assert_eq!(10, encoder.delta().0); // back 5 steps encoder.steps = 6; - assert_eq!(encoder.delta(), -5); + assert_eq!(-5, encoder.delta().0); } #[test] @@ -98,11 +98,11 @@ fn overflow_16bit() { // overflow step with 16-bit encoder encoder.simulate_move(max, min); - assert_eq!(1, encoder.delta()); + assert_eq!(1, encoder.delta().0); // overflow with 16-bit encoder (> 1 step) encoder.simulate_move(max - 10, min + 10); - assert_eq!(21, encoder.delta()); + assert_eq!(21, encoder.delta().0); } #[test] @@ -117,11 +117,11 @@ fn overflow_10bit() { // overflow with 10-bit encoder encoder.simulate_move(max, min); - assert_eq!(1, encoder.delta()); + assert_eq!(1, encoder.delta().0); // overflow with 10-bit encoder (> 1 step) encoder.simulate_move(max - 10, min + 10); - assert_eq!(21, encoder.delta()); + assert_eq!(21, encoder.delta().0); } #[test] @@ -136,11 +136,11 @@ fn underflow_16bit() { // underflow with 16-bit encoder (1 step) encoder.simulate_move(min, max); - assert_eq!(-1, encoder.delta()); + assert_eq!(-1, encoder.delta().0); // underflow with 16-bit encoder (> 1 step) encoder.simulate_move(min + 10, max - 10); - assert_eq!(-21, encoder.delta()); + assert_eq!(-21, encoder.delta().0); } #[test] @@ -155,9 +155,9 @@ fn underflow_10bit() { // underflow with 10-bit encoder (1 step) encoder.simulate_move(min, max); - assert_eq!(-1, encoder.delta()); + assert_eq!(-1, encoder.delta().0); // underflow with 10-bit encoder (> 1 step) encoder.simulate_move(min + 10, max - 10); - assert_eq!(-21, encoder.delta()); + assert_eq!(-21, encoder.delta().0); } diff --git a/libs/hal_encoder_stm32f1xx/Cargo.toml b/libs/hal_encoder_stm32f1xx/Cargo.toml new file mode 100644 index 0000000..00662cc --- /dev/null +++ b/libs/hal_encoder_stm32f1xx/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "hal-encoder-stm32f1xx" +description = "Implementations of hal_encoder abstraction for STM32F1xx" +version = "0.1.0" +authors = ["Rafa Couto "] +edition = "2021" + +[dependencies] +hal-encoder = { version = "0.1.0", path = "../hal_encoder" } +stm32f1xx-hal = { version = "0.10.0", features = ["rt", "stm32f103", "medium"] } diff --git a/libs/hal_encoder_stm32f1xx/src/lib.rs b/libs/hal_encoder_stm32f1xx/src/lib.rs new file mode 100644 index 0000000..db131ec --- /dev/null +++ b/libs/hal_encoder_stm32f1xx/src/lib.rs @@ -0,0 +1,14 @@ +// This crate is a library to manage the Quadrature Encoder interface of STM32F1xx MCUs +// The timers supported are TIM2 to TIM5 (General-purpose timers) and TIM1 and TIM8 (Advanced-control timers). +// Each timer has 4 channels, so the library supports up to 2 encoders by timer since a quadrature encoder uses 2 channels. + +#![no_std] + +// re-export the HAL +pub use hal_encoder::EncoderController; + +// General-purpose timers (TIM2 to TIM5) +pub mod tim2_to_tim5; + +// Advanced-control timers (TIM1 and TIM8) +// to-do \ No newline at end of file diff --git a/libs/hal_encoder_stm32f1xx/src/tim2_to_tim5.rs b/libs/hal_encoder_stm32f1xx/src/tim2_to_tim5.rs new file mode 100644 index 0000000..6c2bf2c --- /dev/null +++ b/libs/hal_encoder_stm32f1xx/src/tim2_to_tim5.rs @@ -0,0 +1,102 @@ +pub use hal_encoder::EncoderController; +use stm32f1xx_hal::pac::tim2; + +pub struct IncrementalEncoder { + tim: *const tim2::RegisterBlock, + last_steps: usize, +} + +pub enum TimerChannels { + Ch1Ch2, + Ch3Ch4, +} + +pub enum EncoderPolarity { + PolarityAB, + PolarityBA, +} + +impl IncrementalEncoder { + const SLAVE_MODE_SELECTION: u8 = tim2::smcr::SMS_A::EncoderMode3 as u8; + + pub fn new(tim: *const tim2::RegisterBlock, channels: TimerChannels, polarity: EncoderPolarity) -> Self { + unsafe { + // up/down on TI1FP1+TI2FP2 edges depending on complementary input + (*tim).smcr.modify(|_, w| w.sms().bits(IncrementalEncoder::SLAVE_MODE_SELECTION)); + + // quadrature encoder mode, input capture channels + match channels { + TimerChannels::Ch1Ch2 => { + (*tim).ccmr1_input().modify(|_, w| w.cc1s().ti1().cc2s().ti2()); + } + TimerChannels::Ch3Ch4 => { + (*tim).ccmr2_input().modify(|_, w| w.cc3s().ti3().cc4s().ti4()); + } + } + + // polarity of the input channels + match polarity { + EncoderPolarity::PolarityAB => { + (*tim).ccer.modify(|_, w| w.cc1p().clear_bit().cc2p().clear_bit()); + } + EncoderPolarity::PolarityBA => { + (*tim).ccer.modify(|_, w| w.cc1p().set_bit().cc2p().clear_bit()); + } + } + + // initial value to the middle + (*tim).cnt.modify(|_, w| w.bits(0)); + + // auto-reload value to the maximum + (*tim).arr.modify(|_, w| w.bits(u16::MAX as u32)); + } + + Self { tim, last_steps: 0 } + } + + // return the current number of steps + pub fn get_steps(&self) -> u16 { + unsafe { + // read the counter register + (*self.tim).cnt.read().cnt().bits() as u16 + } + } + + // set the current number of steps + pub fn set_steps(&mut self, steps: u16) { + unsafe { + // set the counter register + (*self.tim).cnt.write(|w| w.bits(steps as u32)); + } + } + + // enable the encoder + pub fn enable(&mut self) { + unsafe { + // set the counter enable bit + (*self.tim).cr1.modify(|_, w| w.cen().set_bit()); + } + } + + // disable the encoder + pub fn disable(&mut self) { + unsafe { + // clear the counter enable bit + (*self.tim).cr1.modify(|_, w| w.cen().clear_bit()); + } + } +} + +impl EncoderController<16> for IncrementalEncoder { + fn steps(&self) -> usize { + self.get_steps() as usize + } + + fn reset(&mut self) { + self.set_steps(0) + } + + fn last_steps_ref(&mut self) -> &mut usize { + &mut self.last_steps + } +} From a040a9da474e05eb22d468b5eb0b411fd1fb4d14 Mon Sep 17 00:00:00 2001 From: Rafa Couto Date: Thu, 2 May 2024 05:52:37 +0200 Subject: [PATCH 5/7] Sample code to read the encoders --- apps/hello_world/src/main.rs | 69 +++++++++++++++++++++++++++++++----- mightybuga_bsc/Cargo.toml | 1 + mightybuga_bsc/src/lib.rs | 52 +++++++++++++++++++-------- 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/apps/hello_world/src/main.rs b/apps/hello_world/src/main.rs index 9ed2f47..a4ec0f1 100644 --- a/apps/hello_world/src/main.rs +++ b/apps/hello_world/src/main.rs @@ -10,6 +10,7 @@ use mightybuga_bsc::prelude::*; use mightybuga_bsc::timer::SysDelay; use mightybuga_bsc::timer_based_buzzer::TimerBasedBuzzer; use mightybuga_bsc::timer_based_buzzer::TimerBasedBuzzerInterface; +use mightybuga_bsc::EncoderController; use engine::engine::EngineController; @@ -26,7 +27,7 @@ use logging::Logger; #[entry] fn main() -> ! { - let board = board::Mightybuga_BSC::take().unwrap(); + let mut board = board::Mightybuga_BSC::take().unwrap(); let mut delay = board.delay; let mut uart = board.serial; let mut led_d1 = board.led_d1; @@ -34,14 +35,6 @@ fn main() -> ! { let mut buzzer = board.buzzer; let mut engine = board.engine; - // Initialize the allocator BEFORE you use it - { - use core::mem::MaybeUninit; - const HEAP_SIZE: usize = 1024; - static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } - } - let mut logger = Logger::new(&mut uart.tx); loop { @@ -72,6 +65,14 @@ fn main() -> ! { // Turn off the LED D1 led_d2.set_low(); } + b'6' => { + // Read the left encoder + read_encoder(&mut board.encoder_l, &mut logger, &mut delay); + } + b'7' => { + // Read the right encoder + read_encoder(&mut board.encoder_r, &mut logger, &mut delay); + } b'a' => { // Move the robot forward engine.forward(u16::MAX / 4); @@ -117,6 +118,8 @@ fn print_menu(logger: &mut Logger) { logger.log(" 3. Turn off the LED D1\r\n"); logger.log(" 4. Turn on the LED D2\r\n"); logger.log(" 5. Turn off the LED D2\r\n"); + logger.log(" 6. Read the left encoder\r\n"); + logger.log(" 7. Read the right encoder\r\n"); logger.log(" a. Move the robot forward\r\n"); logger.log(" s. Move the robot backward\r\n"); logger.log(" d. Turn the robot right\r\n"); @@ -158,3 +161,51 @@ fn play_notes(logger: &mut Logger, buzzer: &mut TimerBasedBuzzer, delay: &mut Sy delay.delay(300.millis()); buzzer.turn_off(); } + +fn read_encoder(encoder: &mut mightybuga_bsc::IncrementalEncoder, logger: &mut Logger, delay: &mut SysDelay) { + encoder.enable(); + logger.log("move the encoder! (and reset MCU to exit)\r\n"); + + let mut last = 0; + loop { + let (delta, steps) = encoder.delta(); + if last != steps{ + logger.log("(steps,delta): ("); + print_number(steps, logger); + logger.log(","); + print_number(delta, logger); + logger.log(")\r\n"); + last = steps; + } + + // don't burn the CPU + delay.delay(20.millis()); + } +} + +fn print_number(n: isize, logger: &mut Logger) { + let mut len = 0; + let mut digits = [0_u8; 20]; + let mut d = 0; + let mut binary = n.abs(); + while binary > 0 { + digits[d] = (binary % 10) as u8; + d += 1; + binary /= 10; + } + if d == 0 { + d = 1; + } + let mut ascii = [0_u8; 20]; + if n < 0 { + ascii[0] = b'-'; + len = 1; + } + while d > 0 { + d -= 1; + ascii[len] = digits[d] + b'0'; + len += 1; + } + let s = core::str::from_utf8(ascii[0..len].as_ref()).unwrap(); + logger.log(s); +} \ No newline at end of file diff --git a/mightybuga_bsc/Cargo.toml b/mightybuga_bsc/Cargo.toml index d42bdbe..3e76b47 100644 --- a/mightybuga_bsc/Cargo.toml +++ b/mightybuga_bsc/Cargo.toml @@ -23,6 +23,7 @@ nb = "1.1.0" engine = { path = "../libs/engine/" } hal_button = { path = "../libs/hal_button" } +hal-encoder-stm32f1xx = { path = "../libs/hal_encoder_stm32f1xx" } [profile.release] codegen-units = 1 # better optimizations diff --git a/mightybuga_bsc/src/lib.rs b/mightybuga_bsc/src/lib.rs index 39669af..ea13771 100644 --- a/mightybuga_bsc/src/lib.rs +++ b/mightybuga_bsc/src/lib.rs @@ -1,6 +1,8 @@ #![no_std] #![allow(non_camel_case_types)] +use core::ops::Deref; + use hal::gpio::PullDown; // reexport hal crates to allow users to directly refer to them // like in https://github.com/therealprof/nucleo-f103rb/blob/master/src/lib.rs @@ -20,6 +22,8 @@ pub use crate::hal::*; pub mod timer_based_buzzer; use timer_based_buzzer::TimerBasedBuzzer; +pub use hal_encoder_stm32f1xx::tim2_to_tim5::*; + pub mod prelude { pub use cortex_m_rt::entry; pub use stm32f1xx_hal::prelude::{ @@ -33,9 +37,9 @@ pub struct Mightybuga_BSC { pub led_d2: gpio::Pin<'B', 12, gpio::Output>, // UART pub serial: Serial< - USART1, + USART1, (gpio::Pin<'A',9, gpio::Alternate>, gpio::Pin<'A', 10, gpio::Input>), - >, + >, // delay provider pub delay: SysDelay, // Buzzer @@ -56,6 +60,8 @@ pub struct Mightybuga_BSC { pub btn_1: hal_button::Button>, false>, pub btn_2: hal_button::Button>, false>, pub btn_3: hal_button::Button>, false>, + pub encoder_r: IncrementalEncoder, + pub encoder_l: IncrementalEncoder, } impl Mightybuga_BSC { @@ -65,15 +71,15 @@ impl Mightybuga_BSC { // HAL structs let mut flash = dp.FLASH.constrain(); - // We need to enable the clocks here for the peripherals we want to use because the - // `constrain` frees the `RCC` register proxy. - - // Enable the timer 3 clock in the RCC register (we net to do this before the constrain) - dp.RCC.apb1enr.modify(|_, w| w.tim3en().set_bit()); - - let rcc = dp.RCC.constrain(); + // reset and clock control + let rcc = dp.RCC; + rcc.apb1enr.modify(|_, w| w.tim2en().set_bit()); + rcc.apb1enr.modify(|_, w| w.tim3en().set_bit()); + rcc.apb1enr.modify(|_, w| w.tim4en().set_bit()); + // Use external crystal for clock let clocks = rcc + .constrain() .cfgr .use_hse(8.MHz()) .sysclk(72.MHz()) @@ -83,13 +89,15 @@ impl Mightybuga_BSC { let mut delay = cp.SYST.delay(&clocks); delay.delay(300.millis()); - let mut afio = dp.AFIO.constrain(); - // GPIO ports let mut gpioa = dp.GPIOA.split(); let mut gpiob = dp.GPIOB.split(); let mut gpioc = dp.GPIOC.split(); + // Alternate function I/O remapping + let mut afio = dp.AFIO.constrain(); + let (_pa15, _pb3, pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4); + // LEDs configuration let d1 = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); let d2 = gpiob.pb12.into_push_pull_output(&mut gpiob.crh); @@ -139,8 +147,6 @@ impl Mightybuga_BSC { let engine = Engine::new(motor_left, motor_right); // Buzzer configuration - let (_pa15, _pb3, pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4); - // Remap TIM3 gpio pin afio.mapr .modify_mapr(|_, w| unsafe { w.tim3_remap().bits(0b10) }); let buzzer_pin = pb4.into_alternate_push_pull(&mut gpiob.crl); @@ -151,6 +157,22 @@ impl Mightybuga_BSC { let btn_2 = hal_button::Button::new(gpioc.pc15.into_pull_down_input(&mut gpioc.crh)); let btn_3 = hal_button::Button::new(gpioc.pc14.into_pull_down_input(&mut gpioc.crh)); + // Encoder right + let encoder_r = IncrementalEncoder::new( + dp.TIM4.deref(), + TimerChannels::Ch1Ch2, + EncoderPolarity::PolarityBA, + ); + + // Encoder left + afio.mapr + .modify_mapr(|_, w| unsafe { w.tim2_remap().bits(0b01) }); + let encoder_l = IncrementalEncoder::new( + dp.TIM2.deref(), + TimerChannels::Ch1Ch2, + EncoderPolarity::PolarityBA, + ); + // Return the initialized struct Ok(Mightybuga_BSC { led_d1: d1, @@ -161,7 +183,9 @@ impl Mightybuga_BSC { engine, btn_1: btn_1, btn_2: btn_2, - btn_3: btn_3, + btn_3: btn_3, + encoder_r, + encoder_l, }) } } From 03c65825df683ff8507dc06765143fa2dad18565 Mon Sep 17 00:00:00 2001 From: Rafa Couto Date: Fri, 7 Jun 2024 23:15:26 +0200 Subject: [PATCH 6/7] Enable timers on RCC register --- mightybuga_bsc/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mightybuga_bsc/src/lib.rs b/mightybuga_bsc/src/lib.rs index 60fd735..879b3c0 100644 --- a/mightybuga_bsc/src/lib.rs +++ b/mightybuga_bsc/src/lib.rs @@ -6,12 +6,11 @@ pub use stm32f1xx_hal as hal; use hal::adc::Adc; -use hal::pac::*; use hal::gpio::PullDown; +use hal::pac::*; use hal::prelude::*; use hal::serial::*; use hal::timer::SysDelay; -use hal::gpio::PullDown; use core::cell::RefCell; use core::ops::Deref; @@ -90,11 +89,6 @@ impl Mightybuga_BSC { rcc.apb1enr.modify(|_, w| w.tim3en().set_bit()); rcc.apb1enr.modify(|_, w| w.tim4en().set_bit()); - // Enable the timer 3 clock in the RCC register (we need to do this before the constrain) - dp.RCC.apb1enr.modify(|_, w| w.tim3en().set_bit()); - - let rcc = dp.RCC.constrain(); - // Use external crystal for clock let clocks = rcc .constrain() From a17e86211fdbc1e5a56d435430e229b073170912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mu=C3=B1oz?= Date: Sat, 8 Jun 2024 13:25:27 +0200 Subject: [PATCH 7/7] Set ubuntu version on CI --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4c94819..66aeb8f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -12,7 +12,7 @@ env: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu:22.04 steps: - name: Checkout code