Skip to content

Commit

Permalink
Impl heater + wip PID
Browse files Browse the repository at this point in the history
  • Loading branch information
nbars committed Feb 15, 2024
1 parent bf56f1f commit 72c5c3d
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 57 deletions.
48 changes: 33 additions & 15 deletions src/bin/flow_calibration.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
#![no_std]
#![no_main]

use bambino_fw::hardware::{
buttons::{self, ButtonState},
flow_meter::FlowMeter,
leds, pump,
};
use bambino_fw::{hardware::{
buttons::{self, ButtonState}, flow_meter::{self, FlowMeter}, heater::Heater, leds, pump, temperature::{self, Temperature}
}, logic::temperature_pid::TemperaturePID};
use defmt::*;
use embassy_executor::Spawner;
use embassy_futures::select::select;
Expand All @@ -18,6 +16,11 @@ async fn main(mut spawner: Spawner) -> ! {
let mut pump = unsafe { pump::Pump::new() };

let mut flow_meter = unsafe { FlowMeter::new(&mut spawner) };
flow_meter.enable();

let mut temperatur = unsafe { Temperature::new(&mut spawner) };

let mut heater = unsafe { Heater::new(&mut spawner) };

let mut buttons = unsafe { buttons::Buttons::new(&mut spawner) };
let mut leds = unsafe { leds::LEDs::new(&mut spawner) };
Expand All @@ -38,10 +41,15 @@ async fn main(mut spawner: Spawner) -> ! {
// Timer::after_millis(3000).await;
// pump.disable();

let mut pid = TemperaturePID::new();
let mut start_flowed_value = 0;

pid.set_target_temperature(63);

loop {
let event = select(
buttons.wait_for_button_state_change(),
Timer::after_millis(100),
Timer::after_millis(50),
);
match event.await {
embassy_futures::select::Either::First(event) => {
Expand All @@ -54,29 +62,39 @@ async fn main(mut spawner: Spawner) -> ! {
if new_state == ButtonState::Pressed {
leds.set_state(leds::LEDKind::OneCup, leds::LEDState::Blinking(2));
pump.set_power(pump::PumpPower::Fraction(0.5));
let pulses_before = flow_meter.pulse_ctr();
start_flowed_value = flow_meter.flowed_mg();
pump.enable();
flow_meter.wait_for_amount(50000).await;
pump.disable();
info!("pulses={}", flow_meter.pulse_ctr() - pulses_before);
}
}
buttons::ButtonKind::TwoCup => {
leds.set_state(leds::LEDKind::TwoCup, leds::LEDState::Blinking(3));
if new_state == ButtonState::Pressed {
leds.set_state(leds::LEDKind::OneCup, leds::LEDState::Blinking(2));
pump.set_power(pump::PumpPower::Fraction(1.0));
let pulses_before = flow_meter.pulse_ctr();
pump.enable();
flow_meter.wait_for_amount(50000).await;
pump.disable();
info!("pulses={}", flow_meter.pulse_ctr() - pulses_before);
start_flowed_value = flow_meter.flowed_mg();
pump.enable();
}
}
buttons::ButtonKind::Steam => {
pid.set_target_temperature(0);
}
buttons::ButtonKind::HotWater => {
pid.set_target_temperature(0);
}
_ => (),
}
}
embassy_futures::select::Either::Second(_) => {}
embassy_futures::select::Either::Second(_) => {
let temperature = temperatur.temperature_in_c();
info!("temperatur={}°C", temperature);
let next_value = pid.update(temperature);
info!("pid_next_power_value={}", next_value);
heater.set_power(next_value);
if flow_meter.flowed_mg() - start_flowed_value > 100000 {
pump.disable();
}
}
}
}
}
4 changes: 2 additions & 2 deletions src/hardware/flow_meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ impl<'a> FlowMeter<'a> {

/// Wait until the flowed milligram value received an update and return the new value.
pub async fn wait_for_next_update(&self) -> u32 {
defmt::debug_assert!(self.is_enabled());
defmt::assert!(self.is_enabled());
TOTAL_FLOW_IN_MG_SIGNAL.wait().await
}

/// Wait until the specified amount of water has been poured.
pub async fn wait_for_amount(&self, amount_in_mg: u32) {
defmt::debug_assert!(self.is_enabled());
defmt::assert!(self.is_enabled());
let start_mg = self.flowed_mg();
loop {
let new_value = self.wait_for_next_update().await;
Expand Down
94 changes: 82 additions & 12 deletions src/hardware/heater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,114 @@
//! Module to control the heater.
//!
use defmt::info;
use embassy_executor::Spawner;
use embassy_futures::select::{self, select};
use embassy_stm32::{
gpio::{AnyPin, Level, Output, Pin, Speed},
Peripherals,
};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal};
use embassy_time::{Duration, Ticker, Timer};

static DUTY_CYCLE: Signal<ThreadModeRawMutex, u32> = Signal::new();


pub struct Heater;

impl Heater {

/// Create a new `Heater` instance.
///
/// # Safety
/// This is only safe to be called once.
///
/// # Panics
/// If there is not enough memory to spawn a new task.
pub unsafe fn new(spawner: &mut Spawner) -> Self {
spawner.spawn(heater_task()).unwrap();

Heater {

}
}

pub fn set_power(&mut self, power_in_percent: u32) {
assert!(power_in_percent <= 100);
DUTY_CYCLE.signal(power_in_percent);
}
}

impl Drop for Heater {
fn drop(&mut self) {
DUTY_CYCLE.signal(0);
}
}


/// The heater used to boil the water.
pub struct Heater<'a> {
struct HeaterTask<'a> {
pin: Output<'a, AnyPin>,
}

impl<'a> Heater<'a> {
impl<'a> HeaterTask<'a> {
/// Create a new heater instance.
/// # Safety
/// This is only safe when called once and without concurrently calling any of the `new()``
/// methods of the other hardware components.
pub unsafe fn new(p: Peripherals) -> Self {
unsafe fn new() -> Self {
let p = Peripherals::steal();
let pin = Output::new(p.PB6.degrade(), Level::Low, Speed::Low);
Heater { pin }
HeaterTask { pin }
}

/// Turn the heater on.
pub fn on(&mut self) {
fn on(&mut self) {
self.pin.set_high();
}

/// Turn the heater off.
pub fn off(&mut self) {
fn off(&mut self) {
self.pin.set_low();
}

/// Check whether the heater is currently on.
pub fn is_on(&self) -> bool {
self.pin.is_set_high()
}
}

impl<'a> Drop for Heater<'a> {
impl<'a> Drop for HeaterTask<'a> {
fn drop(&mut self) {
self.off();
}
}

#[embassy_executor::task]
async fn heater_task() -> ! {
let mut heater = unsafe { HeaterTask::new() };
heater.off();

let frequency = Duration::from_hz(10);
let mut ticker = Ticker::every(frequency);
let mut current_duty_cycle = None;

loop {
let new_duty_cycle = DUTY_CYCLE.wait();

match select::select(new_duty_cycle, ticker.next()).await {
select::Either::First(new_duty_cycle) => {
if new_duty_cycle > 0 {
let duty_cycle_ms = frequency.as_millis() as f32 * (new_duty_cycle as f32 / 100f32);
current_duty_cycle = Some(duty_cycle_ms as u32);
} else {
current_duty_cycle = None;
}
},
select::Either::Second(_) => {
if let Some(current_duty_cycle) = current_duty_cycle {
heater.on();
info!("current_duty_cycle={}", current_duty_cycle);
Timer::after_millis(current_duty_cycle as u64).await;
heater.off();
}
},
}

}
}
2 changes: 0 additions & 2 deletions src/hardware/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
//! of the portafilter machine.
//!
#![allow(clippy::new_without_default)]

pub mod buttons;
pub mod flow_meter;
pub mod heater;
Expand Down
100 changes: 74 additions & 26 deletions src/hardware/temperature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,112 @@
use core::num::NonZeroU16;

use embassy_executor::{Executor, Spawner};
use embassy_stm32::{
adc::{self, Adc},
bind_interrupts,
peripherals::{ADC, PB1},
Peripherals,
};
use embassy_time::Delay;
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal};
use embassy_time::{Delay, Timer};
use portable_atomic::AtomicU32;

pub struct Temperature<'a> {
static RAW_TEMPERATURE_SIGNAL: Signal<ThreadModeRawMutex, u32> = Signal::new();
static RAW_TEMPERATURE_C: AtomicU32 = AtomicU32::new(0);

pub struct Temperature;

impl Temperature {

/// Create a new `Temperature` instance.
///
/// # Safety
/// This is only safe to be called once.
///
/// # Panics
/// If there is not enough memory to spawn a new task.
pub unsafe fn new(spawner: &mut Spawner) -> Self {
spawner.spawn(temperature_task()).unwrap();

Temperature {

}
}

pub fn temperature_in_c(&self) -> u32 {
let raw_value = RAW_TEMPERATURE_C.load(portable_atomic::Ordering::Relaxed);
Temperature::raw_into_celsius(raw_value)
}

fn raw_into_celsius(raw_value: u32) -> u32 {
/*
ADC Value -> Temperature
1000.0 -> 17
1339 -> 25
2064 -> 44
2997 -> 71
3341 -> 81
https://www.wolframalpha.com/input?i=quadratic+fit+calculator&assumption=%7B%22F%22%2C+%22QuadraticFitCalculator%22%2C+%22data2%22%7D+-%3E%22%7B%281000%2C+17%29%2C+%281339%2C25%29%2C+%282064%2C44%29%2C+%282997%2C71%29%2C+%283341%2C+81%29%7D%22
1.50104×10^-6 x^2 + 0.0209623 x - 5.59606
*/
let raw_value = raw_value as f32;
let f1 = 1.50104f32 * (1f32 / (10u64.pow(6) as f32));
let result = f1 * (raw_value * raw_value) + 0.0209623f32 * raw_value - 5.59606f32;
result as u32
}

}



struct TemperatureTask<'a> {
adc: Adc<'a, ADC>,
ntc_pin: PB1,
}

impl<'a> Temperature<'a> {
/// Create a new `Temperature` instrance in order to measure the water temperature.
impl<'a> TemperatureTask<'a> {
/// Create a new `Temperature` instance in order to measure the water temperature.
/// # Safety
/// This is only safe when called once and without concurrently calling any of the `new()``
/// methods of the other hardware components.
pub unsafe fn new() -> Self {
unsafe fn new() -> Self {
let p = unsafe { Peripherals::steal() };

bind_interrupts!(struct Irqs {
ADC1 => adc::InterruptHandler<ADC>;
});
let mut adc = Adc::new(p.ADC, Irqs, &mut Delay);
adc.set_sample_time(adc::SampleTime::Cycles239_5);
// TODO: Check sample time.
adc.set_sample_time(adc::SampleTime::Cycles55_5);
let _vref = adc.enable_vref(&mut Delay);

Temperature {
TemperatureTask {
adc,
ntc_pin: p.PB1,
}
}

pub async fn read_raw_averaged(&mut self, iterations: NonZeroU16) -> f32 {
let mut mean: u64 = 0;
async fn read_raw_averaged(&mut self, iterations: NonZeroU16) -> u32 {
let mut mean: u32 = 0;
for _ in 0..iterations.get() {
let reading = self.adc.read(&mut self.ntc_pin).await;
mean += reading as u64;
mean += reading as u32;
}

mean as f32 / iterations.get() as f32
mean / iterations.get() as u32
}
}

pub async fn read_averaged_celcius(&mut self, iterations: NonZeroU16) -> f64 {
/*
ADC Value -> Temperature
1000.0 -> 17
1339 -> 25
2064 -> 44
2997 -> 71
3341 -> 81
#[embassy_executor::task]
async fn temperature_task() -> ! {
let mut task = unsafe { TemperatureTask::new() };

https://www.wolframalpha.com/input?i=quadratic+fit+calculator&assumption=%7B%22F%22%2C+%22QuadraticFitCalculator%22%2C+%22data2%22%7D+-%3E%22%7B%281000%2C+17%29%2C+%281339%2C25%29%2C+%282064%2C44%29%2C+%282997%2C71%29%2C+%283341%2C+81%29%7D%22
1.50104×10^-6 x^2 + 0.0209623 x - 5.59606
*/
let raw_mean = self.read_raw_averaged(iterations).await as f64;
let f1 = 1.50104f64 * (1f64 / (10u64.pow(6) as f64));
f1 * (raw_mean * raw_mean) + 0.0209623f64 * raw_mean - 5.59606f64
loop {
let raw_temperature = task.read_raw_averaged(NonZeroU16::new(10).unwrap()).await;
RAW_TEMPERATURE_C.store(raw_temperature, portable_atomic::Ordering::Relaxed);
RAW_TEMPERATURE_SIGNAL.signal(raw_temperature);
Timer::after_millis(10).await;
}
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//!
#![no_std]
#![no_main]
#![allow(clippy::new_without_default)]
#![warn(clippy::cognitive_complexity, missing_docs)]
#![deny(
clippy::missing_safety_doc,
Expand All @@ -13,3 +14,4 @@
)]

pub mod hardware;
pub mod logic;
Loading

0 comments on commit 72c5c3d

Please sign in to comment.