sensor and esp and canable call all communicate with each other.

This commit is contained in:
2025-10-18 00:58:12 +02:00
parent ea608dfa6a
commit a446f3ace2
232 changed files with 9387 additions and 3067870 deletions

View File

@@ -20,6 +20,25 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"bincode_derive",
"unty",
]
[[package]]
name = "bincode_derive"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
dependencies = [
"virtue",
]
[[package]]
name = "bit_field"
version = "0.10.3"
@@ -36,6 +55,7 @@ checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
name = "bms"
version = "0.1.0"
dependencies = [
"canapi",
"ch32-hal",
"embassy-executor",
"embassy-futures",
@@ -45,6 +65,7 @@ dependencies = [
"embedded-alloc",
"embedded-can",
"heapless",
"log",
"panic-halt",
"qingke",
"qingke-rt",
@@ -57,6 +78,13 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "canapi"
version = "0.1.0"
dependencies = [
"bincode",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
@@ -778,6 +806,12 @@ version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "usb-device"
version = "0.3.2"
@@ -837,6 +871,12 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "virtue"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
[[package]]
name = "void"
version = "1.0.2"

View File

@@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
# Shared CAN API
canapi = { path = "../Shared/canapi" }
ch32-hal = { git = "https://github.com/empirephoenix/ch32-hal.git", features = [
"ch32v203c8t6",
"memory-x",
@@ -38,6 +40,7 @@ panic-halt = "1.0"
heapless = { version = "0.8.0", features = ["portable-atomic-critical-section"] }
embassy-time = { version = "0.4.0" }
static_cell = "2.1.1"
log = "0.4.28"
[profile.dev]
#lto = true

View File

@@ -4,13 +4,14 @@ extern crate alloc;
use crate::hal::peripherals::CAN1;
use core::fmt::Write as _;
use canapi::id::{classify, plant_id, MessageKind, IDENTIFY_CMD_OFFSET, MOISTURE_DATA_OFFSET};
use canapi::SensorSlot;
use ch32_hal::gpio::{Level, Output, Speed};
use ch32_hal::adc::{Adc, SampleTime, ADC_MAX};
use ch32_hal::can;
use ch32_hal::can::{Can, CanFifo, CanFilter, CanFrame, CanMode};
use ch32_hal::mode::{Blocking};
use ch32_hal::mode::{NonBlocking};
use ch32_hal::peripherals::USBD;
// use ch32_hal::delay::Delay;
use embassy_executor::{Spawner, task};
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
use embassy_usb::{Builder, UsbDevice};
@@ -19,9 +20,8 @@ use hal::usbd::{Driver};
use hal::{bind_interrupts};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::{Channel};
use embassy_time::{Instant, Duration};
use embedded_can::blocking::Can as bcan;
use embedded_can::StandardId;
use embassy_time::{Instant, Duration, Delay, Timer};
use embedded_can::{Id, StandardId};
use {ch32_hal as hal, panic_halt as _};
macro_rules! mk_static {
@@ -33,12 +33,15 @@ macro_rules! mk_static {
}};
}
bind_interrupts!(struct Irqs {
USB_LP_CAN1_RX0 => hal::usbd::InterruptHandler<hal::peripherals::USBD>;
});
bind_interrupts!(struct Irqs {
USB_LP_CAN1_RX0 => hal::usbd::InterruptHandler<hal::peripherals::USBD>;
});
use embedded_alloc::LlffHeap as Heap;
use embedded_alloc::LlffHeap as Heap;
use embedded_can::nb::Can as nb_can;
use qingke::riscv::asm::delay;
use log::log;
#[global_allocator]
static HEAP: Heap = Heap::empty();
@@ -49,7 +52,7 @@ static LOG_CH: Channel<CriticalSectionRawMutex, heapless::String<128>, 8> = Chan
#[embassy_executor::main(entry = "qingke_rt::entry")]
async fn main(spawner: Spawner) {
ch32_hal::pac::AFIO.pcfr1().write(|w| w.set_can1_rm(2));
//
unsafe {
static mut HEAP_SPACE: [u8; 4096] = [0; 4096]; // 4 KiB heap, adjust as needed
HEAP.init(HEAP_SPACE.as_ptr() as usize, HEAP_SPACE.len());
@@ -103,20 +106,14 @@ async fn main(spawner: Spawner) {
// Built-in LED on PB2 mirrors Q state
let led = Output::new(p.PB2, Level::Low, Speed::Low);
let info = Output::new(p.PA3, Level::Low, Speed::Low);
// 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<'static, CAN1 , Blocking> = Can::new_blocking(p.CAN1, p.PB8, p.PB9, CanFifo::Fifo1, CanMode::Normal, 125_000, config).expect("Valid");
let mut filter = CanFilter::new_id_list();
filter
.get(0)
.unwrap()
.set(StandardId::new(0x580 | 0x42).unwrap().into(), Default::default());
can.add_filter(CanFilter::accept_all());
let can: Can<CAN1, NonBlocking> = 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();
@@ -135,13 +132,28 @@ async fn worker(
mut led: Output<'static>,
mut adc: Adc<'static, hal::peripherals::ADC1>,
mut ain: hal::peripherals::PA1,
mut can: Can<'static, CAN1, Blocking>,
mut can: Can<'static, CAN1, NonBlocking>,
) {
// 555 emulation state: Q initially Low
let mut q_high = false;
let low_th: u16 = (ADC_MAX as u16) / 3; // ~1/3 Vref
let high_th: u16 = ((ADC_MAX as u32 * 2) / 3) as u16; // ~2/3 Vref
let moisture_address = StandardId::new(plant_id(MOISTURE_DATA_OFFSET, SensorSlot::A, 0)).unwrap();
let identity_address = StandardId::new(plant_id(IDENTIFY_CMD_OFFSET, SensorSlot::A, 0)).unwrap();
let mut filter = CanFilter::new_id_list();
filter
.get(0)
.unwrap()
.set(identity_address.into(), Default::default());
can.add_filter(filter);
//can.add_filter(CanFilter::accept_all());
loop {
// Count rising edges of Q in a 100 ms window
let start = Instant::now();
@@ -167,10 +179,8 @@ async fn worker(
// Drive output pin accordingly
if q_high {
q.set_high();
led.set_high();
} else {
q.set_low();
led.set_low();
}
// Count rising edges
@@ -194,9 +204,9 @@ async fn worker(
);
log(msg);
let address = StandardId::new(0x580 | 0x42).unwrap();
let moisture = CanFrame::new(address, &[freq_hz as u8]).unwrap();
match bcan::transmit(&mut can, &moisture) {
let mut moisture = CanFrame::new(moisture_address, &[freq_hz as u8]).unwrap();
match can.transmit(&mut moisture){
Ok(..) => {
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(
@@ -206,18 +216,48 @@ async fn worker(
log(msg);
}
Err(err) => {
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(
&mut msg,
"err {}"
"err {:?}"
,err
);
log(msg);
}
}
loop {
yield_now().await;
match can.receive() {
Ok(frame) => {
match frame.id() {
Id::Standard(s_frame) => {
let mut msg: heapless::String<128> = heapless::String::new();
let _ = write!(
&mut msg,
"Received from canbus: {:?} ident is {:?} \r\n",
s_frame.as_raw(),
identity_address.as_raw()
);
log(msg);
if s_frame.as_raw() == identity_address.as_raw() {
for _ in 0..10 {
Timer::after_millis(250).await;
led.toggle();
}
led.set_low();
}
}
Id::Extended(_) => {}
}
}
_ => {
break;
}
}
}
}
}

View File

@@ -38,7 +38,7 @@ partition_table = "partitions.csv"
[dependencies]
# Shared CAN API
canapi = { path = "canapi" }
canapi = { path = "../../Shared/canapi" }
#ESP stuff
esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32c6"] }
esp-hal = { version = "=1.0.0-rc.0", features = [

View File

@@ -1,13 +0,0 @@
use crate::hal::Sensor;
use bincode::{Decode, Encode};
pub(crate) const SENSOR_BASE_ADDRESS: u16 = 1000;
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub(crate) struct AutoDetectRequest {}
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub(crate) struct ResponseMoisture {
pub plant: u8,
pub sensor: Sensor,
pub hz: u32,
}

View File

@@ -11,13 +11,13 @@ use async_trait::async_trait;
use bincode::config;
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Instant, 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::{Blocking};
use esp_hal::{Async, Blocking};
use log::{error, info, warn};
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
@@ -148,52 +148,8 @@ impl SensorImpl {
}
let mut result = DetectionResult::default();
loop {
match as_async.receive_async().with_deadline(Instant::from_millis(100)).await {
Ok(or) => {
match or {
Ok(can_frame) => {
match can_frame.id() {
Id::Standard(id) => {
let rawid = id.as_raw();
match classify(rawid) {
None => {}
Some(msg) => {
if msg.0 == MessageKind::MoistureData {
let plant = msg.1 as usize;
let sensor = msg.2;
match sensor {
SensorSlot::A => {
result.plant[plant].sensor_a = true;
}
SensorSlot::B => {
result.plant[plant].sensor_b = true;
}
}
}
}
}
}
Id::Extended(ext) => {
warn!("Received extended ID: {:?}", ext);
}
}
}
Err(err ) => {
error!("Error receiving CAN message: {:?}", err);
break;
}
}
info!("Received CAN message: {:?}", or);
}
Err(err) => {
error!("Timeout receiving CAN message: {:?}", err);
break;
}
}
}
// Wait for messages to arrive
let _ = Self::wait_for_can_measurements(&mut as_async, &mut result).with_timeout(Duration::from_millis(5000)).await;
let config = as_async.stop().into_blocking();
can_power.set_low();
@@ -205,6 +161,46 @@ impl SensorImpl {
}
}
async fn wait_for_can_measurements(as_async: &mut Twai<'_, Async>, result: &mut DetectionResult) {
loop {
match as_async.receive_async().await {
Ok(can_frame) => {
match can_frame.id() {
Id::Standard(id) => {
info!("Received CAN message: {:?}", id);
let rawid = id.as_raw();
match classify(rawid) {
None => {}
Some(msg) => {
info!("received message of kind {:?} (plant: {}, sensor: {:?})", msg.0, msg.1, msg.2);
if msg.0 == MessageKind::MoistureData {
let plant = msg.1 as usize;
let sensor = msg.2;
match sensor {
SensorSlot::A => {
result.plant[plant].sensor_a = true;
}
SensorSlot::B => {
result.plant[plant].sensor_b = true;
}
}
}
}
}
}
Id::Extended(ext) => {
warn!("Received extended ID: {:?}", ext);
}
}
}
Err(err) => {
error!("Error receiving CAN message: {:?}", err);
break;
}
}
}
}
pub async fn inner_pulse(plant: usize, sensor: Sensor, signal_counter: &mut Unit<'_, 0>, sensor_expander: &mut Pca9535Immediate<I2cDevice<'static, CriticalSectionRawMutex, I2c<'static, Blocking>>>) -> FatResult<f32> {
let mut results = [0_f32; REPEAT_MOIST_MEASURE];

View File

@@ -38,52 +38,6 @@ use embassy_net::Stack;
use embassy_time::Instant;
use embedded_io_async::{Read, Write};
use log::{error, info};
// fn ota(
// request: &mut Request<&mut EspHttpConnection>,
// ) -> Result<Option<std::string::String>, anyhow::Error> {
// let mut board = BOARD_ACCESS.lock().unwrap();
// let mut ota = OtaUpdate::begin()?;
// log::info!("start ota");
//
// //having a larger buffer is not really faster, requires more stack and prevents the progress bar from working ;)
// const BUFFER_SIZE: usize = 512;
// let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
// let mut total_read: usize = 0;
// let mut lastiter = 0;
// loop {
// let read = request.read(&mut buffer)?;
// total_read += read;
// let to_write = &buffer[0..read];
// //delay for watchdog and wifi stuff
// board.board_hal.get_esp().delay.delay_ms(1);
//
// let iter = (total_read / 1024) % 8;
// if iter != lastiter {
// board.board_hal.general_fault(iter % 5 == 0);
// for i in 0..PLANT_COUNT {
// let _ = board.board_hal.fault(i, iter == i);
// }
// lastiter = iter;
// }
//
// ota.write(to_write)?;
// if read == 0 {
// break;
// }
// }
// log::info!("wrote bytes ota {total_read}");
// log::info!("finish ota");
// let partition = ota.raw_partition();
// log::info!("finalizing and changing boot partition to {partition:?}");
//
// let mut finalizer = ota.finalize()?;
// log::info!("changing boot partition");
// board.board_hal.get_esp().set_restart_to_conf(true);
// drop(board);
// finalizer.set_as_boot_partition()?;
// anyhow::Ok(None)
// }
//
struct HTTPRequestRouter {
reboot_now: Arc<AtomicBool>,

View File

@@ -707,6 +707,7 @@
"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"
},
@@ -731,6 +732,7 @@
"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",
@@ -957,6 +959,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
@@ -2274,6 +2277,7 @@
"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",
@@ -3380,6 +3384,7 @@
"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",
@@ -4338,7 +4343,8 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
"license": "0BSD",
"peer": true
},
"node_modules/type-is": {
"version": "1.6.18",
@@ -4360,6 +4366,7 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4508,6 +4515,7 @@
"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",

View File

@@ -44,9 +44,6 @@ pub mod id {
pub const MOISTURE_DATA_OFFSET: u16 = 0; // periodic data from sensor (sensor -> controller)
pub const IDENTIFY_CMD_OFFSET: u16 = 32; // identify LED command (controller -> sensor)
// Convenience constants for per-slot base offsets
pub const IDENTIFY_CMD_OFFSET_A: u16 = IDENTIFY_CMD_OFFSET + 0;
pub const IDENTIFY_CMD_OFFSET_B: u16 = IDENTIFY_CMD_OFFSET + B_OFFSET;
#[inline]
pub const fn plant_id(message_type_offset: u16, sensor: SensorSlot, plant: u16) -> u16 {