From a9b8f1fca49beca3a2f9c96eac14ad944fca4ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mu=C3=B1oz?= <1619522+punkto@users.noreply.github.com> Date: Wed, 31 Jul 2024 20:21:10 +0200 Subject: [PATCH] 27 add battery sensor to the bsc (#54) * Add mightybuga_bsc/examples/no_bsc_batt_sensor.rs * Update apps/hello_world with light sensor reading * Add battery sensor and example to the hello world app * Fix unused import warning --- apps/hello_world/Cargo.toml | 2 + apps/hello_world/src/main.rs | 25 ++++ libs/battery_sensor_controller/Cargo.toml | 9 ++ libs/battery_sensor_controller/src/lib.rs | 7 + mightybuga_bsc/Cargo.toml | 1 + mightybuga_bsc/examples/no_bsc_batt_sensor.rs | 139 ++++++++++++++++++ mightybuga_bsc/src/battery_sensor.rs | 41 ++++++ mightybuga_bsc/src/lib.rs | 11 ++ 8 files changed, 235 insertions(+) create mode 100644 libs/battery_sensor_controller/Cargo.toml create mode 100644 libs/battery_sensor_controller/src/lib.rs create mode 100644 mightybuga_bsc/examples/no_bsc_batt_sensor.rs create mode 100644 mightybuga_bsc/src/battery_sensor.rs diff --git a/apps/hello_world/Cargo.toml b/apps/hello_world/Cargo.toml index d2d65a0..b13e1c3 100644 --- a/apps/hello_world/Cargo.toml +++ b/apps/hello_world/Cargo.toml @@ -15,6 +15,8 @@ embedded-alloc = "0.5.1" logging = { path = "../../libs/logging" } engine = { path = "../../libs/engine" } +light_sensor_array_controller = { path = "../../libs/light_sensor_array_controller" } +battery_sensor_controller = { path = "../../libs/battery_sensor_controller" } [profile.release] codegen-units = 1 # better optimizations diff --git a/apps/hello_world/src/main.rs b/apps/hello_world/src/main.rs index a4ec0f1..6a9564b 100644 --- a/apps/hello_world/src/main.rs +++ b/apps/hello_world/src/main.rs @@ -13,6 +13,8 @@ use mightybuga_bsc::timer_based_buzzer::TimerBasedBuzzerInterface; use mightybuga_bsc::EncoderController; use engine::engine::EngineController; +use light_sensor_array_controller::LightSensorArrayController; +use battery_sensor_controller::BatterySensorController; use nb::block; @@ -34,6 +36,8 @@ fn main() -> ! { let mut led_d2 = board.led_d2; let mut buzzer = board.buzzer; let mut engine = board.engine; + let mut light_sensor_array = board.light_sensor_array; + let mut battery_sensor = board.battery_sensor; let mut logger = Logger::new(&mut uart.tx); @@ -97,6 +101,25 @@ fn main() -> ! { delay.delay(1000.millis()); engine.stop(); } + b'g' => { + // Read the light sensors + light_sensor_array.set_led(true); + delay.delay(500.millis()); + logger.log("Light sensor values: "); + let light_map = light_sensor_array.get_light_map(); + light_sensor_array.set_led(false); + for i in 0..8 { + logger.log(" "); + print_number(light_map[i] as isize, &mut logger); + } + logger.log("\r\n"); + } + b'h' => { + // Read the battery sensor + logger.log("Battery sensor value: "); + print_number(battery_sensor.get_battery_millivolts() as isize, &mut logger); + logger.log(" milli Volts\r\n"); + } _ => { // Print the menu print_menu(&mut logger); @@ -124,6 +147,8 @@ fn print_menu(logger: &mut Logger) { logger.log(" s. Move the robot backward\r\n"); logger.log(" d. Turn the robot right\r\n"); logger.log(" f. Turn the robot left\r\n"); + logger.log(" g. Read the light sensors\r\n"); + logger.log(" h. Read the battery sensor\r\n"); logger.log(" Any other key prints this menu\r\n"); } diff --git a/libs/battery_sensor_controller/Cargo.toml b/libs/battery_sensor_controller/Cargo.toml new file mode 100644 index 0000000..700c95d --- /dev/null +++ b/libs/battery_sensor_controller/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "battery_sensor_controller" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + diff --git a/libs/battery_sensor_controller/src/lib.rs b/libs/battery_sensor_controller/src/lib.rs new file mode 100644 index 0000000..774b20f --- /dev/null +++ b/libs/battery_sensor_controller/src/lib.rs @@ -0,0 +1,7 @@ +#![no_std] + +/// The trait implemented by the battery sensor to get the battery voltage in millivolts. +pub trait BatterySensorController { + fn get_battery_millivolts(&mut self) -> u16; +} + diff --git a/mightybuga_bsc/Cargo.toml b/mightybuga_bsc/Cargo.toml index c683f4a..585c145 100644 --- a/mightybuga_bsc/Cargo.toml +++ b/mightybuga_bsc/Cargo.toml @@ -25,6 +25,7 @@ hal_button = { path = "../libs/hal_button" } hal-encoder-stm32f1xx = { path = "../libs/hal_encoder_stm32f1xx" } timer_based_buzzer_interface = { path = "../libs/timer_based_buzzer_interface/" } light_sensor_array_controller = { path = "../libs/light_sensor_array_controller/" } +battery_sensor_controller = { path = "../libs/battery_sensor_controller/" } [profile.release] codegen-units = 1 # better optimizations diff --git a/mightybuga_bsc/examples/no_bsc_batt_sensor.rs b/mightybuga_bsc/examples/no_bsc_batt_sensor.rs new file mode 100644 index 0000000..7a21b9d --- /dev/null +++ b/mightybuga_bsc/examples/no_bsc_batt_sensor.rs @@ -0,0 +1,139 @@ +//! battery sensor example without using the BSC +//! The battery sensor is a voltage divider with a 47k resistor and a 20k resistor. +//! The voltage is read from the middle of the two resistors. +//! The voltage is then converted to millivolts and printed to the serial port. +//! +//! Hardware connections: +//! - VBATT to the 47k resistor. The battery has 2 cells in series, so the maximum voltage is 8.4V +//! - GND to the 20k resistor +//! - The middle of the two resistors to PB0 (ADC1) +//! - Led to PC13 +//! - Serial port to PA9 (tx) and PA10 (rx) +//! +//! Run with: +//! cargo xtask mightybuga_bsc example no_bsc_batt_sensor +//! or +//! cd mightybuga-bsc; cargo run --example no_bsc_batt_sensor + +#![no_std] +#![cfg_attr(not(doc), no_main)] +use panic_halt as _; + +use mightybuga_bsc as board; + +use board::hal::adc::*; +use board::hal::serial::*; +use board::hal::{pac, prelude::*}; +use cortex_m_rt::entry; +use nb::block; + +#[entry] +fn main() -> ! { + // Get access to the core peripherals from the cortex-m crate + let cp = cortex_m::Peripherals::take().unwrap(); + // Get access to the device specific peripherals from the peripheral access crate + let dp = pac::Peripherals::take().unwrap(); + + // Take ownership over the raw flash and rcc devices and convert them into the corresponding + // HAL structs + let mut flash = dp.FLASH.constrain(); + let rcc = dp.RCC.constrain(); + + let mut afio = dp.AFIO.constrain(); + + // Freeze the configuration of all the clocks in the system and store the frozen frequencies in + // `clocks` + let clocks = rcc.cfgr.freeze(&mut flash.acr); + + // create a delay abstraction based on SysTick + let mut delay = cp.SYST.delay(&clocks); + + // Acquire the GPIOC peripheral + let mut gpioc = dp.GPIOC.split(); + + // Configure gpio C pin 13 as a push-pull output. The `crh` register is passed to the function + // in order to configure the port. For pins 0-7, crl should be passed instead. + let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + + // turn on the led + led.set_high(); + + // Acquire the GPIOA peripheral + let mut gpioa = dp.GPIOA.split(); + + // Configure gpio A pins 9 and 10 as a push-pull output. The `crh` register is passed to the + // function in order to configure the port. For pins 0-7, crl should be passed instead. + let tx = gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh); + let rx = gpioa.pa10; + + // Configure the serial peripheral + let serial = Serial::new( + dp.USART1, + (tx, rx), + &mut afio.mapr, + Config::default() + .baudrate(115_200.bps()) + .wordlength_8bits() + .parity_none(), + &clocks, + ); + + // Split the serial struct into a receiving and a transmitting part + let (mut tx, _rx) = serial.split(); + + let s = b"\r\nBattery readings:\r\n"; + let _ = s.iter().map(|c| block!(tx.write(*c))).last(); + + // Acquire the ADC1 peripheral + let mut adc1 = Adc::adc1(dp.ADC1, clocks); + + // Get the GPIOB port + let mut gpiob = dp.GPIOB.split(); + + // Configure PB0 as an analog input + let mut pb0 = gpiob.pb0.into_analog(&mut gpiob.crl); + + loop { + // Read the battery voltage, take 100 samples and average them (later, so we don't lose precision): + let mut battery_voltage_by_100: u32 = 0; + for _ in 0..100 { + let sample: u16 = adc1.read(&mut pb0).unwrap(); + battery_voltage_by_100 += sample as u32; + } + + // Convert the voltage to millivolts. We multiply the raw value by the battery voltage and + // divide by the resister divider. The maximum value is 8.4V and the resister divider is + // 47k and 20k. The raw value is 12 bits, so the maximum value is 4096 that corresponds to + // 2.5 volts in the ADC. The formula is: + let raw_to_millivolts_multiplier_by_1000 = 2857; + let battery_voltage_mv = + (battery_voltage_by_100 * raw_to_millivolts_multiplier_by_1000 / 100000) as u16; + + // Print the raw value and the battery voltage to the serial port + let mut buf = [0; 5]; + u16_to_str((battery_voltage_by_100 / 100) as u16, &mut buf); + let _ = buf.iter().map(|c| block!(tx.write(*c))).last(); + + let s = b" - "; + let _ = s.iter().map(|c| block!(tx.write(*c))).last(); + + let mut buf = [0; 5]; + u16_to_str(battery_voltage_mv as u16, &mut buf); + let _ = buf.iter().map(|c| block!(tx.write(*c))).last(); + + let s = b" mVolts\r\n"; + let _ = s.iter().map(|c| block!(tx.write(*c))).last(); + + delay.delay_ms(500_u16); + led.toggle(); + } +} + +// function that fills a buffer with the string representation of a number +fn u16_to_str(n: u16, buf: &mut [u8; 5]) { + let mut n = n; + for i in (0..5).rev() { + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } +} diff --git a/mightybuga_bsc/src/battery_sensor.rs b/mightybuga_bsc/src/battery_sensor.rs new file mode 100644 index 0000000..8056932 --- /dev/null +++ b/mightybuga_bsc/src/battery_sensor.rs @@ -0,0 +1,41 @@ +use crate::{ + hal::{ + gpio::{Analog, Pin}, + prelude::_embedded_hal_adc_OneShot, + }, + ADC_POOL, +}; +use battery_sensor_controller::BatterySensorController; +use heapless::pool::arc::Arc; + +/// The battery sensor used to detect the battery level. +/// It uses 1 analog pin connected to the resistor divider and the ADC1 to read the voltage. +pub struct BatterySensor { + /// The pin for the battery sensor + pub sensor_0: Pin<'B', 0, Analog>, + + pub adc: Arc, +} + +impl BatterySensorController for BatterySensor { + fn get_battery_millivolts(&mut self) -> u16 { + let mut adc = self.adc.borrow_mut(); + + // Read the battery voltage, take 10 samples and average them (later, so we don't lose precision): + let mut battery_voltage_by_10: u32 = 0; + for _ in 0..10 { + let sample: u16 = adc.read(&mut self.sensor_0).unwrap(); + battery_voltage_by_10 += sample as u32; + } + + // Convert the voltage to millivolts. We multiply the raw value by the battery voltage and + // divide by the resister divider. The maximum value is 8.4V and the resister divider is + // 47k and 20k. The raw value is 12 bits, so the maximum value is 4096 that corresponds to + // 2.5 volts in the ADC. The formula is: + let raw_to_millivolts_multiplier_by_1000 = 2857; + let battery_voltage_mv = + (battery_voltage_by_10 * raw_to_millivolts_multiplier_by_1000 / 10000) as u16; + + battery_voltage_mv + } +} diff --git a/mightybuga_bsc/src/lib.rs b/mightybuga_bsc/src/lib.rs index 8251b21..d4dfc0d 100644 --- a/mightybuga_bsc/src/lib.rs +++ b/mightybuga_bsc/src/lib.rs @@ -28,6 +28,9 @@ use stm32f1xx_hal::timer::PwmChannel; mod light_sensor_array; use light_sensor_array::LightSensorArray; +mod battery_sensor; +use battery_sensor::BatterySensor; + pub use crate::hal::*; pub mod timer_based_buzzer; @@ -83,6 +86,8 @@ pub struct Mightybuga_BSC { pub encoder_l: IncrementalEncoder, // Light sensor array pub light_sensor_array: LightSensorArray, + // Battery sensor + pub battery_sensor: BatterySensor, } impl Mightybuga_BSC { @@ -220,6 +225,11 @@ impl Mightybuga_BSC { adc: adc_arc.clone(), }; + let battery_sensor = BatterySensor { + sensor_0: gpiob.pb0.into_analog(&mut gpiob.crl), + adc: adc_arc.clone(), + }; + // Return the initialized struct Ok(Mightybuga_BSC { led_d1: d1, @@ -234,6 +244,7 @@ impl Mightybuga_BSC { btn_2, btn_3, light_sensor_array, + battery_sensor, }) } }