Hello everyone,
I could discard problem 3 and 2 as mechanical problems. I tried code not written by me and I could see the same behaviour. I cannot reproduce problem 1 neither.
So allow me to change my request
!
Is there anyone willing to share with me their wisdom with a traditional code review? I want the code to be as respectful to the Rust convention as possible, and as close to the Rust spirit as possible. Also, if you see big NO-NO, please tell me.
I want to make this an easy Rust project to help beginners like myself to transition from Arduino to Rust.
Thank you in advance for reading and your time. Of course even partial advice is very much appreciated 
Main:
//! ## Classical obstacle avoiding robot with Rust
//! This project takes and Arduino classic and tries to port it to
//! Rust as a beginner project.
//! It uses avr-hal crate for abstraction.
//! For hardware it uses a simple HC-SR04 sensor, an L298N motor driver and a SG90
//! servo motor (details in the Readme).
//! The sensor is reading distance every > 100ms and the robot should take appropriate
//! action if an obstacle is detected.
// Macros to inform rust that the project will not use
// main and the standard library (lib std adds a layer to build the usual functions.)
#![no_std]
#![no_main]
// Pull in the panic handler from panic-halt
extern crate panic_halt;
use arduino_uno::hal::port::mode::Floating;
use arduino_uno::prelude::*;
use crate::motors::{go_backward, go_forward, stop, turn_left, turn_right};
use crate::sensor::{return_distance, SensorUnit};
use crate::servo::ServoUnit;
use arduino_uno::pwm;
mod motors;
mod sensor;
mod servo;
const WAIT_BETWEEN_ACTIONS: u16 = 1000u16;
const MINIMAL_DISTANCE: u16 = 10u16;
const ACCEPTABLE_DISTANCE: u16 = 10u16;
// creates the main function
// attribute macro -> transforms the next as the entry point
// "!" is a never type. It informs nothing should return from the main function.
#[arduino_uno::entry]
fn main() -> ! {
// we acquire first a singleton of all the peripherals (everything inside the MCU)
// more information on raw registers abstraction here:
// https://docs.rs/avr-device/0.2.1/avr_device/atmega328p/struct.Peripherals.html
let dp = arduino_uno::Peripherals::take().unwrap();
let mut delay = arduino_uno::Delay::new();
// all the ports are collapsed into the variable pins
// docs on all pins: https://rahix.github.io/avr-hal/arduino_uno/struct.Pins.html
// by default all pins are configured as Inputs and Floating
// (pull up is to avoid undefined state. For arduino boards (5V), pull-up will allow up or down.
let mut pins = arduino_uno::Pins::new(dp.PORTB, dp.PORTC, dp.PORTD);
// this is the console. To see the output do (on mac)
// screen /dev/tty/<your_tty_here> 57600
// ls /dev/tty* | grep usb --> get the usb connected
// 57600 is the baud rate
let mut serial = arduino_uno::Serial::new(
// protocol to communicate bytes in 2 directions
// USART0 is moved to serial, serial becomes the new owner
// https://rahix.github.io/avr-hal/atmega328p_hal/usart/struct.Usart0.html
dp.USART0,
// the values below correspond to :
// rx: receive pin (hardwired into the MCU)
// tx : PD1 is the "hardcoded output"
// the ownership is moved by writing explicitely input, output is enforced at compile time,
pins.d0,
pins.d1.into_output(&mut pins.ddr),
// other well known baud rates are possible (9600)
57600,
);
// initialisation of timer 1 : we write over and set prescaler to 64
// (1/(16e6/64)) * 2^16 (size of register) ~> takes 262 ms for a cycle
// timer1 is shared with the sensor unit
let timer1 = dp.TC1;
timer1.tccr1b.write(|w| w.cs1().prescale_64());
let mut timer2 = pwm::Timer2Pwm::new(dp.TC2, pwm::Prescaler::Prescale1024);
let mut pd3 = pins.d3.into_output(&mut pins.ddr).into_pwm(&mut timer2);
pd3.enable();
let mut servo_unit = ServoUnit{
servo: pd3,
};
// servo is best set as a struct for clarity, it will be send to
let mut sensor_unit = SensorUnit {
// We do not use pin 13, because it is also connected to an onboard LED marked "L"
// ownership issues: we are moving the pins.d13 into first, the function into_output
// second, into led. It needs the ddr register for configuration
// (DDRx are used to configure the respective PORT as output/input)
trig: pins.d12.into_output(&mut pins.ddr),
// floating input is set by default so we can configure echo without ddr
echo: pins.d11,
timer: timer1,
};
// downgrading the pins allow to put them in an array and simplify functions:
// according to docs : Downgrade this pin into a type that is generic over all pins.
let left_forw = pins.d4.into_output(&mut pins.ddr).downgrade();
let left_back = pins.d5.into_output(&mut pins.ddr).downgrade();
let right_forw = pins.d6.into_output(&mut pins.ddr).downgrade();
let right_back = pins.d7.into_output(&mut pins.ddr).downgrade();
// we have now mutable wheels that can be sent to motor functions
let mut wheels = [left_forw, left_back, right_forw, right_back];
// the car is always going forward (and printing distance to console if connected to screen)
// until it meets an obstacle.
'outer: loop {
servo_unit.look_front();
go_forward(&mut wheels);
let value = return_distance(&mut sensor_unit);
ufmt::uwriteln!( & mut serial, "Hello, we are {} cms away from target!\r", value).void_unwrap();
if value < MINIMAL_DISTANCE {
// the 'obstacle_avoidance loop. I would like to name it, but the compiler will complain :)
loop {
stop(&mut wheels);
servo_unit.look_right();
let value_right = return_distance(&mut sensor_unit);
ufmt::uwriteln!( & mut serial, "On right, we are {} cms away from target!\r", value).void_unwrap();
delay.delay_ms(WAIT_BETWEEN_ACTIONS);
servo_unit.look_left();
let value_left = return_distance(&mut sensor_unit);
ufmt::uwriteln!( & mut serial, "On left, we are {} cms away from target!\r", value).void_unwrap();
delay.delay_ms(WAIT_BETWEEN_ACTIONS);
if (value_left > value_right) && value_left > ACCEPTABLE_DISTANCE {
turn_left(&mut wheels);
} else if (value_right > value_left) && value_right > ACCEPTABLE_DISTANCE {
turn_right(&mut wheels);
} else {
go_backward(&mut wheels);
delay.delay_ms(WAIT_BETWEEN_ACTIONS);
turn_right(&mut wheels);
}
continue 'outer;
}
}
// the sensor needs to wait approximately 60 ms between two waves.
// we ensure that by waiting while the register reaches 25000
// one count == 4 us, and 4us*0.000004 == 100 ms
while sensor_unit.timer.tcnt1.read().bits() < 25000 {}
// I honestly forgot why I print that twice...
ufmt::uwriteln!( & mut serial, "Hello, we are {} cms away from target!\r", value).void_unwrap();
}
}
MOTORS:
//! ### The Motors Module
//! Handles the movement functions.
//! It unpacks the wheel pins in an array.
use arduino_uno::prelude::*;
const TURNING_TIME: u16 = 700u16;
/// The mutable wheels array is destructured for easier manipulation.
pub fn go_forward<>(wheels: &mut [arduino_uno::hal::port::Pin<arduino_uno::hal::port::mode::Output>; 4]) {
let [left_forw, left_back, right_forw, right_back] = wheels;
left_forw.set_high().void_unwrap();
right_forw.set_high().void_unwrap();
left_back.set_low().void_unwrap();
right_back.set_low().void_unwrap();
}
pub fn go_backward(wheels: &mut [arduino_uno::hal::port::Pin<arduino_uno::hal::port::mode::Output>; 4]) {
let [left_forw, left_back, right_forw, right_back] = wheels;
left_forw.set_low().void_unwrap();
right_forw.set_low().void_unwrap();
left_back.set_high().void_unwrap();
right_back.set_high().void_unwrap();
}
pub fn turn_right(wheels: &mut [arduino_uno::hal::port::Pin<arduino_uno::hal::port::mode::Output>; 4]) {
stop(wheels);
let [left_forw, _, _, _] = wheels;
let mut delay = arduino_uno::Delay::new();
left_forw.set_high().void_unwrap();
delay.delay_ms(TURNING_TIME);
}
pub fn turn_left(wheels: &mut [arduino_uno::hal::port::Pin<arduino_uno::hal::port::mode::Output>; 4]) {
stop(wheels);
let [_, _, right_forw, _] = wheels;
let mut delay = arduino_uno::Delay::new();
right_forw.set_high().void_unwrap();
delay.delay_ms(TURNING_TIME);
}
pub fn stop(wheels: &mut [arduino_uno::hal::port::Pin<arduino_uno::hal::port::mode::Output>; 4]) {
let [left_forw, left_back, right_forw, right_back] = wheels;
left_forw.set_low().void_unwrap();
left_back.set_low().void_unwrap();
right_forw.set_low().void_unwrap();
right_back.set_low().void_unwrap();
}
SENSOR:
//! ### The sensor Module
//! The sensor module takes a SensorUnit value struct with a trigger, an echo
//! and a timer. As per HC-SR04 documentation, the trigger needs to be up 10 Ξs
use arduino_uno::prelude::*;
use arduino_uno::hal::port::mode::{Floating};
const TRIGGER_UP_TIME: u16 = 10u16;
/// struct sensor_unit is instantiated in main, as it needs
/// pins and timer.
pub struct SensorUnit {
pub trig: arduino_uno::hal::port::portb::PB4<arduino_uno::hal::port::mode::Output>,
pub echo: arduino_uno::hal::port::portb::PB3<arduino_uno::hal::port::mode::Input<Floating>>,
pub timer: arduino_uno::atmega328p::TC1,
}
pub fn return_distance(sensor_unit: &mut SensorUnit) -> u16 {
let mut delay = arduino_uno::Delay::new();
// we are writing to the tcnt1 register:
// https://docs.rs/avr-device/0.2.1/avr_device/atmega328p/tc1/tcnt1/type.W.html
// when no special methods are listed, it is meant to use bits(), and
// we can click on the W(rite) or R(ead) to see the implementation details.
// Writing a value directly into a register is unsafe,
// in case another register needs so you need to explicitely specify
sensor_unit.timer.tcnt1.write(|w| unsafe { w.bits(0) });
// void_unwrap() --> set high could return an error
// we are using a crate named void + unwrap
// if not, there will be a warning on the fact that result is not used
sensor_unit.trig.set_high().void_unwrap();
delay.delay_us(TRIGGER_UP_TIME);
sensor_unit.trig.set_low().void_unwrap();
'outer: while sensor_unit.echo.is_low().void_unwrap() {
// if more than 200 ms ( = 50000) we might have not detected anything and can continue.
if sensor_unit.timer.tcnt1.read().bits() >= 65000 {
continue 'outer;
}
}
// restarting the timer by writing 0 bits to it
sensor_unit.timer.tcnt1.write(|w| unsafe { w.bits(0) });
// waiting for the echo to get low again
while sensor_unit.echo.is_high().void_unwrap() {}
// Taking the time the echo was high, which is as long as the time the signal was out.
// 1 timer count == 4 us so * 4 to get a value in microsecs
// we divide by 58 to get the distance in cm, since (34000 cm/s * 1e-6 us time)/2 (back and forth measurement)
// == 0.017 more or less 1/58
let value = (sensor_unit.timer.tcnt1.read().bits() * 4) / 58;
// !! AVR only natively supports 8 and 16 bit integers, so *do not* return bigger
u16::from(value)
}
SERVO:
const SERVO_CENTER: u8 = 23;
const SERVO_RIGHT: u8 = 15;
const SERVO_LEFT: u8 = 31;
/// We use a generic for the pin
pub struct ServoUnit<S: embedded_hal::PwmPin<Duty=u8>> {
pub servo: S,
}
/// We implement embedded_hal::PwmPin for the struct ServoUnit,
/// with rotations as methods and not lost functions
impl<S: embedded_hal::PwmPin<Duty=u8>> ServoUnit<S> {
pub fn look_right(&mut self) {
self.servo.set_duty(SERVO_RIGHT);
}
pub fn look_left(&mut self) {
self.servo.set_duty(SERVO_LEFT);
}
pub fn look_front(&mut self) {
self.servo.set_duty(SERVO_CENTER);
}
}