Refactor flow meter logic: replace global mutex with per-instance flow_unit and use critical-section for thread safety.

This commit is contained in:
2026-05-05 16:27:21 +02:00
parent f8f76674ce
commit d903c2bf52
3 changed files with 31 additions and 49 deletions

View File

@@ -1901,6 +1901,7 @@ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz",
"crc", "crc",
"critical-section",
"deranged", "deranged",
"ds323x", "ds323x",
"edge-dhcp", "edge-dhcp",

View File

@@ -101,6 +101,7 @@ chrono-tz = { version = "0.10.4", default-features = false, features = ["filter-
heapless = { version = "0.7.17", features = ["serde"] } # stay in sync with mcutie version heapless = { version = "0.7.17", features = ["serde"] } # stay in sync with mcutie version
static_cell = "2.1.1" static_cell = "2.1.1"
portable-atomic = "1.11.1" portable-atomic = "1.11.1"
critical-section = "1"
crc = "3.3.0" crc = "3.3.0"
bytemuck = { version = "1.24.0", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] } bytemuck = { version = "1.24.0", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] }
deranged = "0.5.5" deranged = "0.5.5"

View File

@@ -1,8 +1,6 @@
use crate::bail; use crate::bail;
use crate::fat_error::FatError; use crate::fat_error::FatError;
use crate::hal::{ADC1, TANK_MULTI_SAMPLE}; use crate::hal::{ADC1, TANK_MULTI_SAMPLE};
use core::cell::RefCell;
use embassy_sync::blocking_mutex::CriticalSectionMutex;
use embassy_time::Timer; use embassy_time::Timer;
use esp_hal::analog::adc::{Adc, AdcCalLine, AdcConfig, AdcPin, Attenuation}; use esp_hal::analog::adc::{Adc, AdcCalLine, AdcConfig, AdcPin, Attenuation};
use esp_hal::delay::Delay; use esp_hal::delay::Delay;
@@ -19,14 +17,13 @@ use portable_atomic::{AtomicUsize, Ordering};
unsafe impl Send for TankSensor<'_> {} unsafe impl Send for TankSensor<'_> {}
static FLOW_OVERFLOW_COUNTER: AtomicUsize = AtomicUsize::new(0); static FLOW_OVERFLOW_COUNTER: AtomicUsize = AtomicUsize::new(0);
static FLOW_UNIT: CriticalSectionMutex<RefCell<Option<Unit<'static, 1>>>> =
CriticalSectionMutex::new(RefCell::new(None));
pub struct TankSensor<'a> { pub struct TankSensor<'a> {
one_wire_bus: OneWire<Flex<'a>>, one_wire_bus: OneWire<Flex<'a>>,
tank_channel: Adc<'a, ADC1<'a>, Async>, tank_channel: Adc<'a, ADC1<'a>, Async>,
tank_power: Output<'a>, tank_power: Output<'a>,
tank_pin: AdcPin<GPIO5<'a>, ADC1<'a>, AdcCalLine<ADC1<'a>>>, tank_pin: AdcPin<GPIO5<'a>, ADC1<'a>, AdcCalLine<ADC1<'a>>>,
flow_unit: Unit<'static, 1>,
} }
impl<'a> TankSensor<'a> { impl<'a> TankSensor<'a> {
@@ -64,61 +61,47 @@ impl<'a> TankSensor<'a> {
pcnt1.listen(); pcnt1.listen();
FLOW_UNIT.lock(|refcell| {
refcell.borrow_mut().replace(pcnt1);
});
Ok(TankSensor { Ok(TankSensor {
one_wire_bus, one_wire_bus,
tank_channel, tank_channel,
tank_power, tank_power,
tank_pin, tank_pin,
flow_unit: pcnt1,
}) })
} }
pub fn reset_flow_meter(&mut self) { pub fn reset_flow_meter(&mut self) {
// Pause, clear counter, clear any pending interrupt, then reset the overflow counter —
// all inside a single critical section to prevent a race where the interrupt fires
// between the overflow reset and the pause.
critical_section::with(|_| {
self.flow_unit.pause();
self.flow_unit.clear();
self.flow_unit.reset_interrupt();
FLOW_OVERFLOW_COUNTER.store(0, Ordering::SeqCst); FLOW_OVERFLOW_COUNTER.store(0, Ordering::SeqCst);
FLOW_UNIT.lock(|refcell| {
if let Some(unit) = refcell.borrow_mut().as_mut() {
unit.pause();
unit.clear();
}
}); });
} }
pub fn start_flow_meter(&mut self) { pub fn start_flow_meter(&mut self) {
FLOW_UNIT.lock(|refcell| { self.flow_unit.resume();
if let Some(unit) = refcell.borrow_mut().as_mut() {
unit.resume();
}
});
}
fn get_flow_meter_value(&mut self) -> i16 {
FLOW_UNIT.lock(|refcell| {
refcell.borrow_mut().as_mut().map_or(0, |unit| unit.value())
})
} }
pub fn stop_flow_meter(&mut self) -> i16 { pub fn stop_flow_meter(&mut self) -> i16 {
FLOW_UNIT.lock(|refcell| { critical_section::with(|_| {
let mut borrowed = refcell.borrow_mut(); let val = self.flow_unit.value();
if let Some(unit) = borrowed.as_mut() { self.flow_unit.pause();
let val = unit.value();
unit.pause();
val val
} else {
0
}
}) })
} }
pub fn get_full_flow_count(&self) -> u32 { pub fn get_full_flow_count(&self) -> u32 {
let current = FLOW_UNIT.lock(|refcell| { // Read both values inside a single critical section so an overflow interrupt cannot
refcell.borrow().as_ref().map_or(0, |unit| unit.value() as u32) // fire between the two reads and produce an inconsistent result.
}); critical_section::with(|_| {
let overflowed = FLOW_OVERFLOW_COUNTER.load(Ordering::SeqCst) as u32; let overflowed = FLOW_OVERFLOW_COUNTER.load(Ordering::SeqCst) as u32;
overflowed * (i16::MAX.wrapping_add(1) as u32) + current let current = self.flow_unit.value() as u32;
overflowed * (i16::MAX as u32 + 1) + current
})
} }
pub async fn water_temperature_c(&mut self) -> Result<f32, FatError> { pub async fn water_temperature_c(&mut self) -> Result<f32, FatError> {
@@ -218,15 +201,12 @@ impl<'a> TankSensor<'a> {
#[esp_hal::handler] #[esp_hal::handler]
pub fn flow_interrupt_handler() { pub fn flow_interrupt_handler() {
FLOW_UNIT.lock(|refcell| { use esp_hal::peripherals::PCNT;
if let Some(unit) = refcell.borrow_mut().as_mut() { let pcnt = PCNT::regs();
if unit.interrupt_is_set() { if pcnt.int_raw().read().cnt_thr_event_u(1).bit() {
let events = unit.events(); if pcnt.u_status(1).read().h_lim().bit() {
if events.high_limit {
FLOW_OVERFLOW_COUNTER.fetch_add(1, Ordering::SeqCst); FLOW_OVERFLOW_COUNTER.fetch_add(1, Ordering::SeqCst);
} }
unit.reset_interrupt(); pcnt.int_clr().write(|w| w.cnt_thr_event_u(1).set_bit());
} }
}
});
} }