diff --git a/Software/CAN_Sensor/src/main.rs b/Software/CAN_Sensor/src/main.rs index 85e9030..8acdeba 100644 --- a/Software/CAN_Sensor/src/main.rs +++ b/Software/CAN_Sensor/src/main.rs @@ -19,12 +19,12 @@ use embassy_sync::channel::Channel; use embassy_time::{Delay, Duration, Instant, Timer}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::{Builder, UsbDevice}; +use embedded_alloc::LlffHeap as Heap; +use embedded_can::nb::Can as nb_can; use embedded_can::{Id, StandardId}; use hal::bind_interrupts; use hal::usbd::Driver; use {ch32_hal as hal, panic_halt as _}; -use embedded_alloc::LlffHeap as Heap; -use embedded_can::nb::Can as nb_can; macro_rules! mk_static { ($t:ty,$val:expr) => {{ @@ -60,49 +60,74 @@ async fn main(spawner: Spawner) { // Build driver and USB stack using 'static buffers let driver = Driver::new(p.USBD, Irqs, p.PA12, p.PA11); - - - let mut probe_gnd = Flex::new(p.PB1); + let mut probe_gnd = Flex::new(p.PA2); probe_gnd.set_as_input(Pull::None); - // Create GPIO for 555 Q output (PB0) - let q_out = Output::new(p.PB0, Level::Low, Speed::Low); - // Built-in LED on PB2 mirrors Q state - let mut info = Output::new(p.PB2, Level::Low, Speed::Low); + let q_out = Output::new(p.PA0, Level::Low, Speed::Low); + let info = Output::new(p.PA10, Level::Low, Speed::Low); + let mut warn = mk_static!(Output, Output::new(p.PA9, Level::Low, Speed::Low)); // Read configuration switches on PB3..PB7 at startup with floating detection // PB3: Sensor A/B selector (Low=A, High=B) // PB4..PB7: address bits (1,2,4,8) - let mut pb3 = Flex::new(p.PB3); - let mut pb4 = Flex::new(p.PB4); - let mut pb5 = Flex::new(p.PB5); - let mut pb6 = Flex::new(p.PB6); - let mut pb7 = Flex::new(p.PB7); + let mut sensor_ab_pin = Flex::new(p.PA3); + let mut sensor_address_bit_1_pin = Flex::new(p.PA4); + let mut sensor_address_bit_2_pin = Flex::new(p.PA5); + let mut sensor_address_bit_3_pin = Flex::new(p.PA6); + let mut sensor_address_bit_4_pin = Flex::new(p.PA7); // Validate all config pins; if any is floating, stay in an error loop until fixed // Try read PB3..PB7 - let res_pb3 = detect_stable_pin(&mut pb3).await; - let res_pb4 = detect_stable_pin(&mut pb4).await; - let res_pb5 = detect_stable_pin(&mut pb5).await; - let res_pb6 = detect_stable_pin(&mut pb6).await; - let res_pb7 = detect_stable_pin(&mut pb7).await; + let sensor_ab_config = detect_stable_pin(&mut sensor_ab_pin).await; + let sensor_address_bit_1_config = detect_stable_pin(&mut sensor_address_bit_1_pin).await; + let sensor_address_bit_2_config = detect_stable_pin(&mut sensor_address_bit_2_pin).await; + let sensor_address_bit_3_config = detect_stable_pin(&mut sensor_address_bit_3_pin).await; + let sensor_address_bit_4_config = detect_stable_pin(&mut sensor_address_bit_4_pin).await; - let slot = if res_pb3.unwrap_or(false) { SensorSlot::B } else { SensorSlot::A }; + let slot = if sensor_ab_config.unwrap_or(false) { + SensorSlot::B + } else { + SensorSlot::A + }; let mut addr: u8 = 0; - if res_pb4.unwrap_or(false) { addr |= 1; } - if res_pb5.unwrap_or(false) { addr |= 2; } - if res_pb6.unwrap_or(false) { addr |= 4; } - if res_pb7.unwrap_or(false) { addr |= 8; } + if sensor_address_bit_1_config.unwrap_or(false) { + addr |= 1; + } + if sensor_address_bit_2_config.unwrap_or(false) { + addr |= 2; + } + if sensor_address_bit_3_config.unwrap_or(false) { + addr |= 4; + } + if sensor_address_bit_4_config.unwrap_or(false) { + addr |= 8; + } let moisture_id = plant_id(MOISTURE_DATA_OFFSET, slot, addr as u16); let identify_id = plant_id(IDENTIFY_CMD_OFFSET, slot, addr as u16); - let invalid_config = res_pb3.is_none() || res_pb4.is_none() || res_pb5.is_none() || res_pb6.is_none() || res_pb7.is_none(); + let invalid_config = sensor_ab_config.is_none() + || sensor_address_bit_1_config.is_none() + || sensor_address_bit_2_config.is_none() + || sensor_address_bit_3_config.is_none() + || sensor_address_bit_4_config.is_none(); let mut config = embassy_usb::Config::new(0xC0DE, 0xCAFE); config.manufacturer = Some("Can Sensor v0.2"); - let msg = mk_static!(heapless::String<128>, heapless::String::new());; - let _ = core::fmt::Write::write_fmt(msg, format_args!("Sensor {:?} plant {}", slot, addr)); + let msg = mk_static!(heapless::String<128>, heapless::String::new()); + if invalid_config { + let _ = core::fmt::Write::write_fmt( + msg, + format_args!( + "CFG err: {:?} {:?} {:?} {:?} {:?}", + to_info(sensor_ab_config), to_info(sensor_address_bit_1_config), to_info(sensor_address_bit_2_config), to_info(sensor_address_bit_3_config), to_info(sensor_address_bit_4_config) + ), + ); + + } else { + let _ = core::fmt::Write::write_fmt(msg, format_args!("Sensor {:?} plant {}", slot, addr)); + } + config.product = Some(msg.as_str()); config.serial_number = Some("12345678"); config.max_power = 100; @@ -131,56 +156,100 @@ async fn main(spawner: Spawner) { // Build USB device let usb = mk_static!(UsbDevice>, builder.build()); - + spawner.spawn(usb_task(usb)).unwrap(); + spawner.spawn(usb_writer(class)).unwrap(); if invalid_config { // At least one floating: report and blink code for the first one found. let mut msg: heapless::String<128> = heapless::String::new(); - let code = if res_pb4.is_none() { 1 } else if res_pb5.is_none() { 2 } else if res_pb6.is_none() { 3 } else if res_pb7.is_none() { 4 } else { 5 }; // PB3 -> 5 - let which = match code { 1 => "PB4", 2 => "PB5", 3 => "PB6", 4 => "PB7", _ => "PB3 (A/B)" }; - let _ = core::fmt::Write::write_fmt(&mut msg, format_args!("Config pin floating detected on {} -> blinking code {}. Fix jumpers.\r\n", which, code)); + let code = if sensor_address_bit_1_config.is_none() { + 1 + } else if sensor_address_bit_2_config.is_none() { + 2 + } else if sensor_address_bit_3_config.is_none() { + 3 + } else if sensor_address_bit_4_config.is_none() { + 4 + } else { + 5 + }; // PB3 -> 5 + let which = match code { + 1 => "PB4", + 2 => "PB5", + 3 => "PB6", + 4 => "PB7", + _ => "PB3 (A/B)", + }; + let _ = core::fmt::Write::write_fmt( + &mut msg, + format_args!( + "Config pin floating detected on {} -> blinking code {}. Fix jumpers.\r\n", + which, code + ), + ); log(msg); - blink_error(&mut info, code).await; - }; + spawner.spawn(blink_error(warn, code)).unwrap(); + } else { + // Log startup configuration and derived CAN IDs + { + let mut msg: heapless::String<128> = heapless::String::new(); + let slot_chr = match slot { + SensorSlot::A => 'a', + SensorSlot::B => 'b', + }; + let _ = core::fmt::Write::write_fmt( + &mut msg, + format_args!( + "Startup: slot={} addr={} moisture_id=0x{:03X} identity_id=0x{:03X}\r\n", + slot_chr, addr, moisture_id, identify_id + ), + ); + log(msg); + } - // Log startup configuration and derived CAN IDs + // Create ADC on ADC1 and use PA1 as analog input (Threshold/Trigger) + let adc = Adc::new(p.ADC1, Default::default()); + let ain = p.PA1; + let config = can::can::Config::default(); + let can: Can = Can::new_nb( + p.CAN1, + p.PB8, + p.PB9, + CanFifo::Fifo0, + CanMode::Normal, + 125_000, + config, + ) + .expect("Valid"); + ch32_hal::pac::AFIO.pcfr1().write(|w| w.set_can1_rm(2)); - { - let mut msg: heapless::String<128> = heapless::String::new(); - let slot_chr = match slot { SensorSlot::A => 'a', SensorSlot::B => 'b' }; - let _ = core::fmt::Write::write_fmt(&mut msg, format_args!( - "Startup: slot={} addr={} moisture_id=0x{:03X} identity_id=0x{:03X}\r\n", - slot_chr, addr, moisture_id, identify_id - )); - log(msg); + // move Q output, LED, ADC and analog input into worker task + spawner + .spawn(worker( + probe_gnd, + q_out, + info, + warn, + adc, + ain, + can, + StandardId::new(moisture_id).unwrap(), + StandardId::new(identify_id).unwrap(), + )) + .unwrap(); } - - // Create ADC on ADC1 and use PA1 as analog input (Threshold/Trigger) - let adc = Adc::new(p.ADC1, Default::default()); - let ain = p.PA1; - let config = can::can::Config::default(); - let can: Can = Can::new_nb( - p.CAN1, - p.PB8, - p.PB9, - CanFifo::Fifo0, - CanMode::Normal, - 125_000, - config, - ) - .expect("Valid"); - ch32_hal::pac::AFIO.pcfr1().write(|w| w.set_can1_rm(2)); - - spawner.spawn(usb_task(usb)).unwrap(); - spawner.spawn(usb_writer(class)).unwrap(); - // move Q output, LED, ADC and analog input into worker task - spawner.spawn(worker(probe_gnd, q_out, info, adc, ain, can, StandardId::new(moisture_id).unwrap(), StandardId::new(identify_id).unwrap())).unwrap(); - // Prevent main from exiting core::future::pending::<()>().await; } +fn to_info(res: Option) -> i8 { + match res { + Some(true) => 1, + Some(false) => -1, + None => 0, + } +} // Helper closure: detect stable pin by comparing readings under Pull::Down and Pull::Up async fn detect_stable_pin(pin: &mut Flex<'static>) -> Option { @@ -190,9 +259,14 @@ async fn detect_stable_pin(pin: &mut Flex<'static>) -> Option { pin.set_as_input(Pull::Up); Timer::after_millis(2).await; let high_read = pin.is_high(); - if low_read == high_read { Some(high_read) } else { None } + if low_read == high_read { + Some(high_read) + } else { + None + } } -async fn blink_error(mut info_led: &mut Output<'static>, code: u8) -> !{ +#[task] +async fn blink_error(info_led: &'static mut Output<'static>, code: u8) -> ! { loop { // code: 1-4 for PB4..PB7, 5 for PB3 (A/B) for _ in 0..code { @@ -206,17 +280,17 @@ async fn blink_error(mut info_led: &mut Output<'static>, code: u8) -> !{ } } - #[task] async fn worker( mut probe_gnd: Flex<'static>, mut q: Output<'static>, mut info: Output<'static>, + mut warn: &'static mut Output<'static>, mut adc: Adc<'static, hal::peripherals::ADC1>, mut ain: hal::peripherals::PA1, mut can: Can<'static, CAN1, NonBlocking>, moisture_id: StandardId, - identify_id: StandardId + identify_id: StandardId, ) { // 555 emulation state: Q initially Low let mut q_high = false; @@ -284,8 +358,10 @@ async fn worker( let mut msg: heapless::String<128> = heapless::String::new(); let _ = write!( &mut msg, - "555 window={}ms pulses={} freq={} Hz (A1->Q on PB0)\r\n", probe_duration.as_millis(), - pulses, freq_hz + "555 window={}ms pulses={} freq={} Hz (A1->Q on PB0)\r\n", + probe_duration.as_millis(), + pulses, + freq_hz ); log(msg); @@ -298,9 +374,9 @@ async fn worker( } Err(err) => { for _ in 0..3 { - info.set_high(); + warn.set_high(); Timer::after_millis(100).await; - info.set_low(); + warn.set_low(); Timer::after_millis(100).await; } let mut msg: heapless::String<128> = heapless::String::new(); @@ -318,7 +394,6 @@ async fn worker( ); log(msg); - yield_now().await; match can.receive() { Ok(frame) => match frame.id() { diff --git a/Software/MainBoard/rust/src/hal/v4_hal.rs b/Software/MainBoard/rust/src/hal/v4_hal.rs index c48b37a..7a593ce 100644 --- a/Software/MainBoard/rust/src/hal/v4_hal.rs +++ b/Software/MainBoard/rust/src/hal/v4_hal.rs @@ -130,15 +130,14 @@ pub struct V4<'a> { pump_ina: Option< SyncIna219>, UnCalibrated>, >, - twai_peripheral: Option>, - twai_rx_pin: Option>, - twai_tx_pin: Option>, can_power: Output<'static>, extra1: Output<'a>, extra2: Output<'a>, + can_mutex: embassy_sync::mutex::Mutex, } + pub(crate) async fn create_v4( peripherals: FreePeripherals<'static>, esp: Esp<'static>, @@ -257,35 +256,41 @@ pub(crate) async fn create_v4( config, battery_monitor, pump_ina, - twai_peripheral, - twai_rx_pin, - twai_tx_pin, charger, extra1, extra2, can_power, + can_mutex: embassy_sync::mutex::Mutex::new(()), }; Ok(Box::new(v)) } -impl<'a> V4<'a> { - fn teardown_twai(&mut self, old: TwaiConfiguration) { - drop(old); - // Re-acquire the peripheral and pins - let twai = unsafe { peripherals::TWAI0::steal() }; - let rx_pin = unsafe { peripherals::GPIO2::steal() }; - let tx_pin = unsafe { peripherals::GPIO0::steal() }; +fn teardown_twai(old: Twai) { + let config = old.stop(); + drop(config); + // Re-acquire the peripheral and pins + let rx_pin = unsafe { peripherals::GPIO2::steal() }; + let tx_pin = unsafe { peripherals::GPIO0::steal() }; - // Set pins to low to avoid parasitic powering - let mut rx = Input::new(rx_pin, InputConfig::default().with_pull(Pull::None)); - let mut tx = Input::new(tx_pin, InputConfig::default().with_pull(Pull::None)); + // Set pins to low to avoid parasitic powering + let _ = Input::new(rx_pin, InputConfig::default().with_pull(Pull::None)); + let _ = Input::new(tx_pin, InputConfig::default().with_pull(Pull::None)); +} - // Release the pins from Output back to raw pins and store everything - self.twai_peripheral = Some(twai); - self.twai_rx_pin = Some(unsafe { peripherals::GPIO2::steal() }); - self.twai_tx_pin = Some(unsafe { peripherals::GPIO0::steal() }); - self.can_power.set_low(); - } +fn create_twai<'a>() -> Twai<'a, Async> { + // Release the pins from Output back to raw pins and store everything + let twai = unsafe { peripherals::TWAI0::steal() }; + let twai_rx_pin = unsafe { peripherals::GPIO2::steal() }; + let twai_tx_pin = unsafe { peripherals::GPIO0::steal() }; + + let twai_config = TwaiConfiguration::new( + twai, + twai_rx_pin, + twai_tx_pin, + TWAI_BAUDRATE, + TwaiMode::Normal, + ); + twai_config.into_async().start() } #[async_trait(?Send)] @@ -379,14 +384,8 @@ impl<'a> BoardInteraction<'a> for V4<'a> { } async fn measure_moisture_hz(&mut self) -> FatResult { self.can_power.set_high(); - let twai_config = TwaiConfiguration::new( - self.twai_peripheral.take().unwrap(), - self.twai_rx_pin.take().unwrap(), - self.twai_tx_pin.take().unwrap(), - TWAI_BAUDRATE, - TwaiMode::Normal, - ); - let mut twai = twai_config.into_async().start(); + let mut twai = create_twai(); + loop { let rec = twai.receive(); @@ -405,11 +404,64 @@ impl<'a> BoardInteraction<'a> for V4<'a> { let _ = wait_for_can_measurements(&mut twai, &mut moistures) .with_timeout(Duration::from_millis(2000)) .await; - self.teardown_twai(twai.stop().into_blocking()); - + teardown_twai(twai); + self.can_power.set_low(); Ok(moistures) } + async fn detect_sensors(&mut self) -> FatResult { + self.can_power.set_high(); + let mut twai = create_twai(); + // Give CAN some time to stabilize + Timer::after_millis(10).await; + + info!("Sending info messages now"); + // Send a few test messages per potential sensor node + for plant in 0..PLANT_COUNT { + for sensor in [Sensor::A, Sensor::B] { + let target = + StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, sensor.into(), plant as u16)) + .context(">> Could not create address for sensor! (plant: {}) <<")?; + let can_buffer = [0_u8; 0]; + if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) { + // Try a few times; we intentionally ignore rx here and rely on stub logic + let resu = twai + .transmit_async(&frame) + .with_timeout(Duration::from_millis(1000)) + .await; + match resu { + Ok(_) => { + info!("Sent test message to plant {plant} sensor {sensor:?}"); + } + Err(err) => { + info!( + "Error sending test message to plant {plant} sensor {sensor:?}: {err:?}" + ); + } + } + } else { + info!("Error building CAN frame"); + } + } + } + + let mut moistures = Moistures::default(); + let _ = wait_for_can_measurements(&mut twai, &mut moistures) + .with_timeout(Duration::from_millis(1000)) + .await; + + + teardown_twai(twai); + self.can_power.set_low(); + + + let result = moistures.into(); + + info!("Autodetection result: {result:?}"); + Ok(result) + } + + async fn general_fault(&mut self, enable: bool) { hold_disable(23); self.general_fault.set_level(enable.into()); @@ -470,69 +522,9 @@ impl<'a> BoardInteraction<'a> for V4<'a> { async fn get_mptt_current(&mut self) -> FatResult { self.charger.get_mppt_current() } - - async fn detect_sensors(&mut self) -> FatResult { - // Power on CAN transceiver and start controller - self.can_power.set_high(); - let twai_config = TwaiConfiguration::new( - self.twai_peripheral.take().unwrap(), - self.twai_rx_pin.take().unwrap(), - self.twai_tx_pin.take().unwrap(), - TWAI_BAUDRATE, - TwaiMode::Normal, - ); - - - info!("convert can"); - let mut as_async = twai_config.into_async().start(); - // Give CAN some time to stabilize - Timer::after_millis(10).await; - - info!("Sending info messages now"); - // Send a few test messages per potential sensor node - for plant in 0..PLANT_COUNT { - for sensor in [Sensor::A, Sensor::B] { - let target = - StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, sensor.into(), plant as u16)) - .context(">> Could not create address for sensor! (plant: {}) <<")?; - let can_buffer = [0_u8; 0]; - if let Some(frame) = EspTwaiFrame::new(target, &can_buffer) { - // Try a few times; we intentionally ignore rx here and rely on stub logic - let resu = as_async - .transmit_async(&frame) - .with_timeout(Duration::from_millis(1000)) - .await; - match resu { - Ok(_) => { - info!("Sent test message to plant {plant} sensor {sensor:?}"); - } - Err(err) => { - info!( - "Error sending test message to plant {plant} sensor {sensor:?}: {err:?}" - ); - } - } - } else { - info!("Error building CAN frame"); - } - } - } - - let mut moistures = Moistures::default(); - let _ = wait_for_can_measurements(&mut as_async, &mut moistures) - .with_timeout(Duration::from_millis(1000)) - .await; - - let config = as_async.stop().into_blocking(); - self.teardown_twai(config); - - let result = moistures.into(); - - info!("Autodetection result: {result:?}"); - Ok(result) - } } + async fn wait_for_can_measurements( as_async: &mut Twai<'_, Async>, moistures: &mut Moistures, diff --git a/website/themes/blowfish b/website/themes/blowfish index 26d1205..f9eb1d4 160000 --- a/website/themes/blowfish +++ b/website/themes/blowfish @@ -1 +1 @@ -Subproject commit 26d1205439b460bee960fd4c29f3c5c20948875f +Subproject commit f9eb1d4e811d6da744848c35fb842cf386f6df39