Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

38 encoder #46

Merged
merged 10 commits into from
Jun 9, 2024
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ env:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu:22.04

steps:
- name: Checkout code
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"libs/engine",
"libs/timer_based_buzzer_interface",
"libs/hal_button",
"libs/hal_encoder_stm32f1xx",
"apps/hello_world",
]

Expand Down
69 changes: 60 additions & 9 deletions apps/hello_world/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -26,22 +27,14 @@ 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;
let mut led_d2 = board.led_d2;
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<u8>; 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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
}
8 changes: 8 additions & 0 deletions libs/hal_encoder/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "hal-encoder"
description = "Abstractions for an encoder controller"
version = "0.1.0"
authors = ["Rafa Couto <[email protected]>"]
edition = "2021"

[dependencies]
34 changes: 34 additions & 0 deletions libs/hal_encoder/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![no_std]

pub trait EncoderController<const BITS: u8> {
// This function returns the absolute step count.
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 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.
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, isize) {
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 {
delta += match steps > *last_steps {
true => -(1 << BITS), // underflow
false => 1 << BITS, // overflow
}
};
*last_steps = steps;
(delta, steps as isize)
}
}

#[cfg(test)]
mod tests;
163 changes: 163 additions & 0 deletions libs/hal_encoder/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use super::*;

struct MockEncoder<const BITS: u8> {
pub steps: usize,
pub last_steps: usize,
}

impl<const BITS: u8> MockEncoder<BITS> {
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<const BITS: u8> Default for MockEncoder<BITS> {
fn default() -> Self {
Self {
steps: 0,
last_steps: 0,
}
}
}

impl<const BITS: u8> EncoderController<BITS> for MockEncoder<BITS> {
fn 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!(0, encoder.delta().0);

// advance 1 step
encoder.steps = 1;
assert_eq!(1, encoder.delta().0);

// advance 10 steps
encoder.steps = 11;
assert_eq!(10, encoder.delta().0);

// back 5 steps
encoder.steps = 6;
assert_eq!(-5, encoder.delta().0);
}

#[test]
fn delta_10bit() {
// initial state
let mut encoder: MockEncoder<10> = Default::default();

// no movement
assert_eq!(0, encoder.delta().0);

// advance 1 step
encoder.steps = 1;
assert_eq!(1, encoder.delta().0);

// advance 10 steps
encoder.steps = 11;
assert_eq!(10, encoder.delta().0);

// back 5 steps
encoder.steps = 6;
assert_eq!(-5, encoder.delta().0);
}

#[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().0);

// overflow with 16-bit encoder (> 1 step)
encoder.simulate_move(max - 10, min + 10);
assert_eq!(21, encoder.delta().0);
}

#[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().0);

// overflow with 10-bit encoder (> 1 step)
encoder.simulate_move(max - 10, min + 10);
assert_eq!(21, encoder.delta().0);
}

#[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().0);

// underflow with 16-bit encoder (> 1 step)
encoder.simulate_move(min + 10, max - 10);
assert_eq!(-21, encoder.delta().0);
}

#[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().0);

// underflow with 10-bit encoder (> 1 step)
encoder.simulate_move(min + 10, max - 10);
assert_eq!(-21, encoder.delta().0);
}
10 changes: 10 additions & 0 deletions libs/hal_encoder_stm32f1xx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "hal-encoder-stm32f1xx"
description = "Implementations of hal_encoder abstraction for STM32F1xx"
version = "0.1.0"
authors = ["Rafa Couto <[email protected]>"]
edition = "2021"

[dependencies]
hal-encoder = { version = "0.1.0", path = "../hal_encoder" }
stm32f1xx-hal = { version = "0.10.0", features = ["rt", "stm32f103", "medium"] }
14 changes: 14 additions & 0 deletions libs/hal_encoder_stm32f1xx/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading