diff --git a/Software/CAN_Sensor/Cargo.lock b/Software/CAN_Sensor/Cargo.lock index 618477a..395b477 100644 --- a/Software/CAN_Sensor/Cargo.lock +++ b/Software/CAN_Sensor/Cargo.lock @@ -66,6 +66,7 @@ dependencies = [ "embedded-can", "heapless", "log", + "nb 1.1.0", "panic-halt", "qingke", "qingke-rt", diff --git a/Software/CAN_Sensor/Cargo.toml b/Software/CAN_Sensor/Cargo.toml index 3caa0fe..3665dfa 100644 --- a/Software/CAN_Sensor/Cargo.toml +++ b/Software/CAN_Sensor/Cargo.toml @@ -28,6 +28,7 @@ embassy-usb = { version = "0.3.0" } embassy-futures = { version = "0.1.0" } embassy-sync = { version = "0.6.0" } embedded-can = "0.4.1" +nb = "1.1" embedded-alloc = { version = "0.6.0", default-features = false, features = ["llff"] } # This is okay because we should automatically use whatever ch32-hal uses diff --git a/Software/CAN_Sensor/src/main.rs b/Software/CAN_Sensor/src/main.rs index e0f480a..a66422f 100644 --- a/Software/CAN_Sensor/src/main.rs +++ b/Software/CAN_Sensor/src/main.rs @@ -26,7 +26,6 @@ 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) => {{ static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); @@ -61,9 +60,50 @@ 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); + 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); + + // 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); + + // 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 slot = if res_pb3.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; } + 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 mut config = embassy_usb::Config::new(0xC0DE, 0xCAFE); - config.manufacturer = Some("Embassy"); - config.product = Some("USB-serial example"); + 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)); + config.product = Some(msg.as_str()); config.serial_number = Some("12345678"); config.max_power = 100; config.max_packet_size_0 = 64; @@ -91,29 +131,9 @@ async fn main(spawner: Spawner) { // Build USB device let usb = mk_static!(UsbDevice>, builder.build()); - // 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); - // 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); - // 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; - - if res_pb3.is_none() || res_pb4.is_none() || res_pb5.is_none() || res_pb6.is_none() || res_pb7.is_none() { + 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 @@ -122,16 +142,10 @@ async fn main(spawner: Spawner) { log(msg); blink_error(&mut info, code).await; }; - let slot = if res_pb3.unwrap() { SensorSlot::B } else { SensorSlot::A }; - let mut addr: u8 = 0; - if res_pb4.unwrap() { addr |= 1; } - if res_pb5.unwrap() { addr |= 2; } - if res_pb6.unwrap() { addr |= 4; } - if res_pb7.unwrap() { addr |= 8; } + // Log startup configuration and derived CAN IDs - 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 mut msg: heapless::String<128> = heapless::String::new(); let slot_chr = match slot { SensorSlot::A => 'a', SensorSlot::B => 'b' }; @@ -161,7 +175,7 @@ async fn main(spawner: Spawner) { 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(q_out, info, adc, ain, can, StandardId::new(moisture_id).unwrap(), StandardId::new(identify_id).unwrap())).unwrap(); + 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; @@ -195,6 +209,7 @@ 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 adc: Adc<'static, hal::peripherals::ADC1>, @@ -215,8 +230,8 @@ async fn worker( .unwrap() .set(identify_id.into(), Default::default()); - can.add_filter(filter); - //can.add_filter(CanFilter::accept_all()); + //can.add_filter(filter); + can.add_filter(CanFilter::accept_all()); loop { // Count rising edges of Q in a 100 ms window @@ -224,6 +239,8 @@ async fn worker( let mut pulses: u32 = 0; let mut last_q = q_high; + probe_gnd.set_as_output(Speed::Low); + probe_gnd.set_low(); while Instant::now() .checked_duration_since(start) .unwrap_or(Duration::from_millis(0)) @@ -258,6 +275,7 @@ async fn worker( // Yield to allow USB and other tasks to run yield_now().await; } + probe_gnd.set_as_input(Pull::None); // Compute frequency from 100 ms window let freq_hz = pulses; // pulses per 0.1s => Hz @@ -270,7 +288,7 @@ async fn worker( ); log(msg); - let mut moisture = CanFrame::new(moisture_id, &[freq_hz as u8]).unwrap(); + let mut moisture = CanFrame::new(moisture_id, &(freq_hz as u16).to_be_bytes()).unwrap(); match can.transmit(&mut moisture) { Ok(..) => { let mut msg: heapless::String<128> = heapless::String::new(); @@ -278,6 +296,12 @@ async fn worker( log(msg); } Err(err) => { + for _ in 0..3 { + info.set_high(); + Timer::after_millis(100).await; + info.set_low(); + Timer::after_millis(100).await; + } let mut msg: heapless::String<128> = heapless::String::new(); let _ = write!(&mut msg, "err {:?}", err); log(msg); @@ -285,6 +309,15 @@ async fn worker( } loop { + let mut msg: heapless::String<128> = heapless::String::new(); + let _ = write!( + &mut msg, + "Check identity addr received: {:#x} \r\n", + identify_id.as_raw() + ); + log(msg); + + yield_now().await; match can.receive() { Ok(frame) => match frame.id() { @@ -307,7 +340,8 @@ async fn worker( } Id::Extended(_) => {} }, - _ => { + + Err(err) => { break; } } diff --git a/Software/MainBoard/rust/src/hal/v4_sensor.rs b/Software/MainBoard/rust/src/hal/v4_sensor.rs index f38a47b..2bc1ccd 100644 --- a/Software/MainBoard/rust/src/hal/v4_sensor.rs +++ b/Software/MainBoard/rust/src/hal/v4_sensor.rs @@ -10,12 +10,12 @@ use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET}; use canapi::SensorSlot; use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_time::{Duration, Timer, WithTimeout}; +use embassy_time::{Duration, Instant, Timer, WithTimeout}; use embedded_can::{Frame, Id}; use esp_hal::gpio::Output; use esp_hal::i2c::master::I2c; use esp_hal::pcnt::unit::Unit; -use esp_hal::twai::{EspTwaiFrame, StandardId, Twai, TwaiConfiguration}; +use esp_hal::twai::{EspTwaiError, EspTwaiFrame, StandardId, Twai, TwaiConfiguration}; use esp_hal::{Async, Blocking}; use log::{error, info, warn}; use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface}; @@ -129,10 +129,12 @@ impl SensorImpl { // Power on CAN transceiver and start controller can_power.set_high(); let config = twai_config.take().expect("twai config not set"); + info!("convert can"); let mut as_async = 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..crate::hal::PLANT_COUNT { for sensor in [Sensor::A, Sensor::B] { @@ -145,7 +147,7 @@ impl SensorImpl { 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).await; + 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:?}"); @@ -164,7 +166,7 @@ impl SensorImpl { let mut moistures = Moistures::default(); let _ = Self::wait_for_can_measurements(&mut as_async, &mut moistures) - .with_timeout(Duration::from_millis(5000)) + .with_timeout(Duration::from_millis(1000)) .await; let config = as_async.stop().into_blocking(); @@ -200,15 +202,18 @@ impl SensorImpl { let plant = msg.1 as usize; let sensor = msg.2; let data = can_frame.data(); - - match sensor { - SensorSlot::A => { - moistures.sensor_a_hz[plant] = data[0] as f32; - } - SensorSlot::B => { - moistures.sensor_b_hz[plant] = data[0] as f32; + if data.len() == 2 { + let frequency = u16::from_be_bytes([data[0], data[1]]); + match sensor { + SensorSlot::A => { + moistures.sensor_a_hz[plant] = frequency as f32; + } + SensorSlot::B => { + moistures.sensor_b_hz[plant] = frequency as f32; + } } } + } } } @@ -218,6 +223,13 @@ impl SensorImpl { } }, Err(err) => { + match err { + EspTwaiError::BusOff => { + bail!("Bus offline") + } + EspTwaiError::NonCompliantDlc(_) => {} + EspTwaiError::EmbeddedHAL(_) => {} + } error!("Error receiving CAN message: {err:?}"); } } diff --git a/Software/MainBoard/rust/src_webpack/package-lock.json b/Software/MainBoard/rust/src_webpack/package-lock.json index 46ef213..26340b5 100644 --- a/Software/MainBoard/rust/src_webpack/package-lock.json +++ b/Software/MainBoard/rust/src_webpack/package-lock.json @@ -707,7 +707,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -732,7 +731,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -959,7 +957,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -2277,7 +2274,6 @@ "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -3384,7 +3380,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4343,8 +4338,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/type-is": { "version": "1.6.18", @@ -4366,7 +4360,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4515,7 +4508,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -4565,7 +4557,6 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1",