diff --git a/board/Body1.3mf b/board/Body1.3mf new file mode 100644 index 0000000..ddc330b Binary files /dev/null and b/board/Body1.3mf differ diff --git a/board/modules/MPPT/battery-charging.png b/board/modules/MPPT/battery-charging.png new file mode 100644 index 0000000..87d37df Binary files /dev/null and b/board/modules/MPPT/battery-charging.png differ diff --git a/board/modules/MPPT/battery-charging.svg b/board/modules/MPPT/battery-charging.svg new file mode 100644 index 0000000..4ae74d2 --- /dev/null +++ b/board/modules/MPPT/battery-charging.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/board/modules/Sensors_can/Sensors/sym-lib-table b/board/modules/Sensors_can/Sensors/sym-lib-table new file mode 100644 index 0000000..fb2abc0 --- /dev/null +++ b/board/modules/Sensors_can/Sensors/sym-lib-table @@ -0,0 +1,4 @@ +(sym_lib_table + (version 7) + (lib (name "Modules")(type "KiCad")(uri "/home/empire/workspace/PlantCtrl/board/modules/Modules.kicad_sym")(options "")(descr "")) +) diff --git a/board/modules/Sensors_can/ch32-sensor/.cargo/config.toml b/board/modules/Sensors_can/ch32-sensor/.cargo/config.toml new file mode 100644 index 0000000..c0b2201 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/.cargo/config.toml @@ -0,0 +1,14 @@ +[build] +target = "riscv32imc-unknown-none-elf" + +[target."riscv32imc-unknown-none-elf"] +rustflags = [ +# "-C", "link-arg=-Tlink.x", +] +# runner = "riscv64-unknown-elf-gdb -q -x openocd.gdb" +# runner = "riscv-none-embed-gdb -q -x openocd.gdb" +# runner = "gdb -q -x openocd.gdb" +# runner = "wlink -v flash" + +runner = "wlink -v flash --enable-sdi-print --watch-serial --erase" +# runner = "wlink -v flash" diff --git a/board/modules/Sensors_can/ch32-sensor/.gdbinit b/board/modules/Sensors_can/ch32-sensor/.gdbinit new file mode 100644 index 0000000..3263f19 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/.gdbinit @@ -0,0 +1,7 @@ +target extended-remote :3333 +set remotetimeout 2000 + +#symbol-file target/riscv32imc-unknown-none-elf/release/ch32v203-examples +file target/riscv32imc-unknown-none-elf/release/bms + +monitor reset halt diff --git a/board/modules/Sensors_can/ch32-sensor/.gitignore b/board/modules/Sensors_can/ch32-sensor/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/.gitignore @@ -0,0 +1 @@ +target diff --git a/board/modules/Sensors_can/ch32-sensor/Cargo.toml b/board/modules/Sensors_can/ch32-sensor/Cargo.toml new file mode 100644 index 0000000..70dc449 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "bms" +version = "0.1.0" +edition = "2021" + +[dependencies] +ch32-hal = { git = "https://github.com/ch32-rs/ch32-hal", features = [ + "ch32v203c8t6", + "memory-x", + "embassy", + "rt", + "time-driver-tim2", +], default-features = false } + +embassy-executor = { version = "0.7.0", features = [ + "arch-riscv32", + "executor-thread", +] } + +embassy-time = { version = "0.4.0" } +embassy-usb = { version = "0.3.0" } +embassy-futures = { version = "0.1.0" } + +# This is okay because we should automatically use whatever ch32-hal uses +qingke-rt = "*" +qingke = "*" + +panic-halt = "1.0" + +embedded-hal = "1.0.0" +heapless = "0.8.0" +micromath = { version = "2.1.0", features = ["num-traits"] } +embedded-can = "0.4.1" + +[profile.dev] +#lto = true +opt-level = 1 + +[profile.release] +strip = false # symbols are not flashed to the microcontroller, so don't strip them. +#lto = true +debug = true +opt-level = "z" # Optimize for size. diff --git a/board/modules/Sensors_can/ch32-sensor/README.md b/board/modules/Sensors_can/ch32-sensor/README.md new file mode 100644 index 0000000..5be4a9d --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/README.md @@ -0,0 +1,72 @@ + +# ch32v203-bms + +A simple battery management controller software. + +## CAN bus and hardware address + +This firmware exposes a CAN interface on the CH32V203 and uses 4 hardware address pins to allow up to 16 sensors on the same bus. + +- CAN pins (default mapping): + - CAN RX: PA11 + - CAN TX: PA12 +- Address select pins (with internal pull-ups): + - A0: PA0 + - A1: PA1 + - A2: PA2 + - A3: PA3 + +Wire each address pin to GND to set its corresponding bit to 1. The 4-bit address range is 0..15. The node’s CAN Standard ID is `0x100 | addr`, i.e. 0x100..0x10F. The CAN acceptance filter is configured to only accept frames with the node’s own ID. + +Adjust the pins above if your PCB routes CAN or address lines to different pads. + +## 555 timer (software) emulation mode + +To save the BOM cost of a classic NE555 in simple oscillator applications, this firmware implements a minimal 555-like Schmitt trigger using the MCU’s ADC and a GPIO, approximating the behavior when the capacitor is charged/discharged via Q through a resistor, and the combined Trigger/Threshold senses the capacitor node. + +- Pins used: + - Q output: PB2 + - Combined Trigger/Threshold (ADC input): PA0 +- Wiring: + - PB2 (Q) -> series resistor R -> capacitor node + - Capacitor node -> capacitor to GND + - Capacitor node -> PA0 (ADC input) +- Behavior: + - When ADC(PA0) <= ~1/3 Vref, PB2 is driven High. + - When ADC(PA0) >= ~2/3 Vref, PB2 is driven Low. + - Hysteresis avoids chatter; the actual charge/discharge dynamics follow your chosen R and C. +- Notes: + - Use an appropriate resistor from PB2 to the capacitor to set oscillation frequency. Start with 10k..100k and adjust with C. + - Ensure PA0 is routed to the capacitor node and left high impedance (no strong pull-ups/downs) so the ADC can sense the analog voltage. + - PB2 drives the on-board LED (if present), so the LED might blink at the oscillation frequency. + +This mode is implemented in `src/main.rs` using `hal::adc::Adc::convert(&mut pin, SampleTime::...)` to take periodic samples and a simple state machine to toggle the Q output based on ~1/3 and ~2/3 Vref thresholds. + +## Building + +``` sh +cargo build --release +``` + +## Flash + +``` sh +wchisp config reset +wchip wchisp flash target/riscv32imc-unknown-none-elf/release/bms +``` + +## Debugging + +For debugging purposes a container file is provided together with wrapper scripts to start the containerized `openocd` and `riscv-gdb` transparently. The wrapper scripts assume that `podman` is setup. + +Starting Debug server + +``` +./bin/openocd +``` + +Connecting with gdb for interactive debugging + +``` +./bin/gdb -f target/riscv32imc-unknown-none-elf/release/bms +``` diff --git a/board/modules/Sensors_can/ch32-sensor/bin/build-wch-tools-container.sh b/board/modules/Sensors_can/ch32-sensor/bin/build-wch-tools-container.sh new file mode 100755 index 0000000..83721f8 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/bin/build-wch-tools-container.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euo pipefail + +CONTAINER_NAME="localhost/wch-dev-tools:latest" +CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")" + +pushd "$CONTAINER_TOOLS_BASEDIR" +podman build -t "$CONTAINER_NAME" -f "../wch-tools.Containerfile" . +popd diff --git a/board/modules/Sensors_can/ch32-sensor/bin/gdb b/board/modules/Sensors_can/ch32-sensor/bin/gdb new file mode 100755 index 0000000..3677232 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/bin/gdb @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -euo pipefail + +CONTAINER_IMAGE="localhost/wch-dev-tools:latest" +CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")" + +function _fatal { + echo -e "\e[31mERROR\e[0m $(&2 + exit 1 +} + +declare -a PODMAN_ARGS=( + "--rm" "-i" "--log-driver=none" + "--network=host" + "--pid=host" + "-v" "$PWD:$PWD:rw" + "-w" "$PWD" +) + +[[ -t 1 ]] && PODMAN_ARGS+=("-t") + +if ! podman image exists "$CONTAINER_IMAGE"; then + #attempt to build container + "$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 || + _fatal "faild to build local image, cannot continue! … please ensure you have an internet connection" +fi + +podman run "${PODMAN_ARGS[@]}" --entrypoint riscv-none-elf-gdb-py3 "$CONTAINER_IMAGE" "$@" diff --git a/board/modules/Sensors_can/ch32-sensor/bin/openocd b/board/modules/Sensors_can/ch32-sensor/bin/openocd new file mode 100755 index 0000000..2ca3b44 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/bin/openocd @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +set -euo pipefail + +CONTAINER_IMAGE="localhost/wch-dev-tools:latest" +CONTAINER_TOOLS_BASEDIR="$(dirname "$(readlink -f "$0")")" + +function _fatal { + echo -e "\e[31mERROR\e[0m $(&2 + exit 1 +} + +declare -a PODMAN_ARGS=( + "--rm" "-i" "--log-driver=none" + "--network=host" + "-v" "$PWD:$PWD:rw" + "-w" "$PWD" +) + +for device in /dev/bus/usb/*/*; do + if udevadm info "$device" | grep -q "ID_VENDOR=wch.cn" && \ + udevadm info "$device" | grep -q "ID_MODEL=WCH-Link"; then + DEBUGGER_DEV_PATH="$device" + break + fi +done + +if [[ -z "${DEBUGGER_DEV_PATH:-}" ]]; then + echo "Could not find hardware debugger … Exiting!" 1>&2 + exit 1 +else + # add jlink to podman device + PODMAN_ARGS+=("--device=$DEBUGGER_DEV_PATH") +fi + +[[ -t 1 ]] && PODMAN_ARGS+=("-t") + +if ! podman image exists "$CONTAINER_IMAGE"; then + #attempt to build container + "$CONTAINER_TOOLS_BASEDIR/build-wch-tools-container.sh" 1>&2 || + _fatal "faild to build local image, cannot continue! … please ensure you have an internet connection" +fi + +podman run "${PODMAN_ARGS[@]}" --entrypoint openocd "$CONTAINER_IMAGE" "$@" diff --git a/board/modules/Sensors_can/ch32-sensor/build.rs b/board/modules/Sensors_can/ch32-sensor/build.rs new file mode 100644 index 0000000..77208e1 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/build.rs @@ -0,0 +1,11 @@ +fn main() { + // println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + // println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + let out_dir = std::env::var("OUT_DIR").unwrap(); + let out_dir = std::path::PathBuf::from(out_dir); + std::fs::write(out_dir.join("memory.x"), include_bytes!("memory.x")).unwrap(); + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/board/modules/Sensors_can/ch32-sensor/memory.x b/board/modules/Sensors_can/ch32-sensor/memory.x new file mode 100644 index 0000000..d6d76a7 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/memory.x @@ -0,0 +1,125 @@ +/* CH32V203c8t6 */ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 64K /* BANK_1 */ + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} +REGION_ALIAS("REGION_TEXT", FLASH); +REGION_ALIAS("REGION_RODATA", FLASH); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); + +/* fault handlers */ + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler);; +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +/* core interrupt handlers */ + +PROVIDE(NonMaskableInt = DefaultHandler); +PROVIDE(Software = DefaultHandler); + +/* external interrupt handlers */ + +PROVIDE(WWDG = DefaultHandler); +PROVIDE(PVD = DefaultHandler); +PROVIDE(TAMPER = DefaultHandler); +PROVIDE(RTC = DefaultHandler); +PROVIDE(FLASH = DefaultHandler); +PROVIDE(RCC = DefaultHandler); +PROVIDE(EXTI0 = DefaultHandler); +PROVIDE(EXTI1 = DefaultHandler); +PROVIDE(EXTI2 = DefaultHandler); +PROVIDE(EXTI3 = DefaultHandler); +PROVIDE(EXTI4 = DefaultHandler); +PROVIDE(DMA1_CHANNEL1 = DefaultHandler); +PROVIDE(DMA1_CHANNEL2 = DefaultHandler); +PROVIDE(DMA1_CHANNEL3 = DefaultHandler); +PROVIDE(DMA1_CHANNEL4 = DefaultHandler); +PROVIDE(DMA1_CHANNEL5 = DefaultHandler); +PROVIDE(DMA1_CHANNEL6 = DefaultHandler); +PROVIDE(DMA1_CHANNEL7 = DefaultHandler); +PROVIDE(ADC = DefaultHandler); +PROVIDE(USB_HP_CAN1_TX = DefaultHandler); +/*PROVIDE(USB_LP_CAN1_RX0 = DefaultHandler);*/ +PROVIDE(CAN1_RX1 = DefaultHandler); +PROVIDE(CAN1_SCE = DefaultHandler); +PROVIDE(EXTI9_5 = DefaultHandler); +PROVIDE(TIM1_BRK = DefaultHandler); +PROVIDE(TIM1_UP_ = DefaultHandler); +PROVIDE(TIM1_TRG_COM = DefaultHandler); +PROVIDE(TIM1_CC = DefaultHandler); +PROVIDE(TIM2 = DefaultHandler); +PROVIDE(TIM3 = DefaultHandler); +PROVIDE(TIM4 = DefaultHandler); +PROVIDE(I2C1_EV = DefaultHandler); +PROVIDE(I2C1_ER = DefaultHandler); +PROVIDE(I2C2_EV = DefaultHandler); +PROVIDE(I2C2_ER = DefaultHandler); +PROVIDE(SPI1 = DefaultHandler); +PROVIDE(SPI2 = DefaultHandler); +PROVIDE(USART1 = DefaultHandler); +PROVIDE(USART2 = DefaultHandler); +PROVIDE(USART3 = DefaultHandler); +PROVIDE(EXTI15_10 = DefaultHandler); +PROVIDE(RTCALARM = DefaultHandler); +PROVIDE(USBWAKE_UP = DefaultHandler); +PROVIDE(TIM8_BRK = DefaultHandler); +PROVIDE(TIM8_UP_ = DefaultHandler); +PROVIDE(TIM8_TRG_COM = DefaultHandler); +PROVIDE(TIM8_CC = DefaultHandler); +PROVIDE(RNG = DefaultHandler); +PROVIDE(FSMC = DefaultHandler); +PROVIDE(SDIO = DefaultHandler); +PROVIDE(TIM5 = DefaultHandler); +PROVIDE(SPI3 = DefaultHandler); +PROVIDE(UART4 = DefaultHandler); +PROVIDE(UART5 = DefaultHandler); +PROVIDE(TIM6 = DefaultHandler); +PROVIDE(TIM7 = DefaultHandler); +PROVIDE(DMA2_CHANNEL1 = DefaultHandler); +PROVIDE(DMA2_CHANNEL2 = DefaultHandler); +PROVIDE(DMA2_CHANNEL3 = DefaultHandler); +PROVIDE(DMA2_CHANNEL4 = DefaultHandler); +PROVIDE(DMA2_CHANNEL5 = DefaultHandler); +PROVIDE(ETH = DefaultHandler); +PROVIDE(ETH_WKUP = DefaultHandler); +PROVIDE(CAN2_TX = DefaultHandler); +PROVIDE(CAN2_RX0 = DefaultHandler); +PROVIDE(CAN2_RX1 = DefaultHandler); +PROVIDE(CAN2_SCE = DefaultHandler); +PROVIDE(OTG_FS = DefaultHandler); +PROVIDE(USBHSWAKEUP = DefaultHandler); +PROVIDE(USBHS = DefaultHandler); +PROVIDE(DVP = DefaultHandler); +PROVIDE(UART6 = DefaultHandler); +PROVIDE(UART7 = DefaultHandler); +PROVIDE(UART8 = DefaultHandler); +PROVIDE(TIM9_BRK = DefaultHandler); +PROVIDE(TIM9_UP_ = DefaultHandler); +PROVIDE(TIM9_TRG_COM = DefaultHandler); +PROVIDE(TIM9_CC = DefaultHandler); +PROVIDE(TIM10_BRK = DefaultHandler); +PROVIDE(TIM10_UP_ = DefaultHandler); +PROVIDE(TIM10_TRG_COM = DefaultHandler); +PROVIDE(TIM10_CC = DefaultHandler); +PROVIDE(DMA2_CHANNEL6 = DefaultHandler); +PROVIDE(DMA2_CHANNEL7 = DefaultHandler); +PROVIDE(DMA2_CHANNEL8 = DefaultHandler); +PROVIDE(DMA2_CHANNEL9 = DefaultHandler); +PROVIDE(DMA2_CHANNEL10 = DefaultHandler); +PROVIDE(DMA2_CHANNEL11 = DefaultHandler); diff --git a/board/modules/Sensors_can/ch32-sensor/openocd.cfg b/board/modules/Sensors_can/ch32-sensor/openocd.cfg new file mode 100644 index 0000000..fd3d973 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/openocd.cfg @@ -0,0 +1,17 @@ +set _CHIPNAME ch32v203 +set _TARGETNAME $_CHIPNAME.cpu + +#bindto 0.0.0.0 + +adapter driver wlinke +adapter speed 6000 +transport select sdi + +sdi newtap $_CHIPNAME cpu -irlen 5 --expected-id 0x00001 +target create $_TARGETNAME.0 wch_riscv -chain-position $_TARGETNAME +$_TARGETNAME.0 configure -work-area-phys 0x20000000 -work-area-size 10000 -work-area-backup 1 +set _FLASHNAME $_CHIPNAME.flash + +flash bank $_FLASHNAME wch_rsicv 0x00000000 0 0 0 $_TARGETNAME.0 + +init diff --git a/board/modules/Sensors_can/ch32-sensor/rust-toolchain.toml b/board/modules/Sensors_can/ch32-sensor/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/board/modules/Sensors_can/ch32-sensor/src/main.rs b/board/modules/Sensors_can/ch32-sensor/src/main.rs new file mode 100644 index 0000000..94fee12 --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/src/main.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] + +// Simple 555-like oscillator implemented in firmware. +// - Q output: PB2 (also drives the on-board LED if present) +// - Combined Trigger/Threshold analog input: PA0 (capacitor node) +// Wiring suggestion: +// Q (PB2) --[R]--+-- C -- GND +// | +// PA0 (ADC input) +// The firmware toggles Q high when PA0 <= 1/3 Vref and low when PA0 >= 2/3 Vref. + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::gpio::{Level, Output}; +use {ch32_hal as hal, panic_halt as _}; + +use hal::adc::{Adc, SampleTime}; + +#[embassy_executor::main(entry = "qingke_rt::entry")] +async fn main(_spawner: Spawner) -> ! { + let p = hal::init(Default::default()); + + // Q output on PB2 + let mut q = Output::new(p.PB2, Level::Low, Default::default()); + + // ADC on PA0 for combined Trigger/Threshold input + let mut adc = Adc::new(p.ADC1, Default::default()); + let mut trig_thres = p.PA0; // analog-capable pin used as ADC channel + + // ADC characteristics: assume 12-bit if HAL doesn't expose it. + // If the HAL provides a method to query resolution, prefer that. + let full_scale: u16 = 4095; // 12-bit default + let thr_low: u16 = (full_scale as u32 / 3) as u16; // ~1/3 Vref + let thr_high: u16 = ((full_scale as u32 * 2) / 3) as u16; // ~2/3 Vref + + // Start with Q low. State variable to avoid redundant toggles. + let mut q_high = false; + q.set_low(); + + loop { + // Read capacitor node voltage via ADC + let sample: u16 = adc.convert(&mut trig_thres, SampleTime::CYCLES239_5); + + // Implement Schmitt trigger behavior like NE555 using thresholds + if !q_high && sample <= thr_low { + // Trigger: voltage fell below 1/3 Vref -> set output high + q.set_high(); + q_high = true; + } else if q_high && sample >= thr_high { + // Threshold: voltage rose above 2/3 Vref -> set output low + q.set_low(); + q_high = false; + } + + // Small delay to reduce CPU usage; adjust for responsiveness/noise + Timer::after_micros(200).await; + } +} diff --git a/board/modules/Sensors_can/ch32-sensor/wch-tools.Containerfile b/board/modules/Sensors_can/ch32-sensor/wch-tools.Containerfile new file mode 100644 index 0000000..e48a92f --- /dev/null +++ b/board/modules/Sensors_can/ch32-sensor/wch-tools.Containerfile @@ -0,0 +1,27 @@ +FROM debian:bookworm + +RUN apt update -y && apt upgrade -y && apt install git libjaylink-dev libusb-1.0-0 unzip curl libhidapi-hidraw0 xz-utils -y + +RUN cd /root && \ + curl -L -o mrs-toolchain.tar.xz "https://github.com/ch32-riscv-ug/MounRiver_Studio_Community_miror/releases/download/1.92-toolchain/MRS_Toolchain_Linux_x64_V1.92.tar.xz" && \ + mkdir mrs-toolchain && \ + tar -xvf mrs-toolchain.tar.xz -C mrs-toolchain --strip-components=1 && \ + mv mrs-toolchain/OpenOCD/bin/openocd /usr/local/bin && \ + mv mrs-toolchain/OpenOCD/share/openocd /usr/local/share && \ + # mv mrs-toolchain/RISC-V_Embedded_GCC12/bin/riscv-none-elf-gdb /usr/local/bin && \ # both toolchains in MRS are to old to work with emacs dape + # mv mrs-toolchain/RISC-V_Embedded_GCC12/libexec /usr/local && \ # both toolchains in MRS are to old to work with emacs dape + rm -rf mrs-toolchain mrs-toolchain.tar.xz && \ + # Use up to date xpack toolchains for gdb + curl -L -o xpack-riscv-toolchain.tar.gz "https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v14.2.0-3/xpack-riscv-none-elf-gcc-14.2.0-3-linux-x64.tar.gz" && \ + mkdir xpack-toolchain && \ + tar -xvf xpack-riscv-toolchain.tar.gz -C xpack-toolchain --strip-components=1 && \ + mv xpack-toolchain/bin/* /usr/local/bin && \ + mv xpack-toolchain/lib/ /usr/local && \ + mv xpack-toolchain/lib64/ /usr/local && \ + mv xpack-toolchain/libexec /usr/local && \ + mv xpack-toolchain/riscv-none-elf /usr/local && \ + rm -rf xpack-toolchain xpack-riscv-toolchain.tar.gz + +RUN mkdir -p /root/.config/gdb && echo "set auto-load safe-path /" >> /root/.config/gdb/gdbinit + +ENTRYPOINT [ "/usr/bin/bash" ] diff --git a/board/modules/Sensors_simplified/Sensors/fp-lib-table b/board/modules/Sensors_simplified/Sensors/fp-lib-table new file mode 100644 index 0000000..5f0dd0d --- /dev/null +++ b/board/modules/Sensors_simplified/Sensors/fp-lib-table @@ -0,0 +1,4 @@ +(fp_lib_table + (version 7) + (lib (name "Sensor")(type "KiCad")(uri "${KIPRJMOD}/Sensor.pretty")(options "")(descr "")) +) diff --git a/rust/src/config.rs b/rust/src/config.rs index ef993d9..0c37085 100644 --- a/rust/src/config.rs +++ b/rust/src/config.rs @@ -58,7 +58,7 @@ pub struct TankConfig { pub tank_warn_percent: u8, pub tank_empty_percent: u8, pub tank_full_percent: u8, - pub ml_per_pulse: f32 + pub ml_per_pulse: f32, } impl Default for TankConfig { fn default() -> Self { @@ -111,6 +111,7 @@ pub struct PlantControllerConfig { pub struct PlantConfig { pub mode: PlantWateringMode, pub target_moisture: f32, + pub min_moisture: f32, pub pump_time_s: u16, pub pump_limit_ml: u16, pub pump_cooldown_min: u16, @@ -131,6 +132,7 @@ impl Default for PlantConfig { Self { mode: PlantWateringMode::OFF, target_moisture: 40., + min_moisture: 30., pump_time_s: 30, pump_limit_ml: 5000, pump_cooldown_min: 60, diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index d722e81..c31d216 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -22,6 +22,7 @@ use bq34z100::Bq34z100g1Driver; use ds323x::{DateTimeAccess, Ds323x}; use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; use embedded_hal_bus::i2c::MutexDevice; +use esp_idf_hal::pcnt::PCNT1; use esp_idf_hal::{ adc::ADC1, delay::Delay, @@ -47,7 +48,6 @@ use once_cell::sync::Lazy; use std::result::Result::Ok as OkStd; use std::sync::Mutex; use std::time::Duration; -use esp_idf_hal::pcnt::PCNT1; //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs index 312f1e9..d1d6c32 100644 --- a/rust/src/hal/v3_hal.rs +++ b/rust/src/hal/v3_hal.rs @@ -132,7 +132,7 @@ pub(crate) fn create_v3( peripherals.gpio5, tank_power_pin, flow_sensor_pin, - peripherals.pcnt1 + peripherals.pcnt1, )?; let mut signal_counter = PcntDriver::new( diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs index 18e8bc9..189d6af 100644 --- a/rust/src/hal/v4_hal.rs +++ b/rust/src/hal/v4_hal.rs @@ -160,7 +160,7 @@ pub(crate) fn create_v4( peripherals.gpio5, tank_power_pin, flow_sensor_pin, - peripherals.pcnt1 + peripherals.pcnt1, )?; let mut signal_counter = PcntDriver::new( diff --git a/rust/src/hal/water.rs b/rust/src/hal/water.rs index 2de5170..1e44e47 100644 --- a/rust/src/hal/water.rs +++ b/rust/src/hal/water.rs @@ -6,7 +6,9 @@ use esp_idf_hal::adc::oneshot::{AdcChannelDriver, AdcDriver}; use esp_idf_hal::adc::{attenuation, Resolution, ADC1}; use esp_idf_hal::delay::Delay; use esp_idf_hal::gpio::{AnyIOPin, AnyInputPin, Gpio5, InputOutput, PinDriver, Pull}; -use esp_idf_hal::pcnt::{PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1}; +use esp_idf_hal::pcnt::{ + PcntChannel, PcntChannelConfig, PcntControlMode, PcntCountMode, PcntDriver, PinIndex, PCNT1, +}; use esp_idf_sys::EspError; use one_wire_bus::OneWire; @@ -25,7 +27,7 @@ impl<'a> TankSensor<'a> { gpio5: Gpio5, tank_power_pin: AnyIOPin, flow_sensor_pin: AnyIOPin, - pcnt1: PCNT1 + pcnt1: PCNT1, ) -> anyhow::Result> { let mut one_wire_pin = PinDriver::input_output_od(one_wire_pin).expect("Failed to configure pin"); @@ -73,7 +75,6 @@ impl<'a> TankSensor<'a> { }, )?; - Ok(TankSensor { one_wire_bus, tank_channel, diff --git a/rust/src/main.rs b/rust/src/main.rs index 58f0b02..c21a625 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -591,16 +591,31 @@ pub fn do_secure_pump( let mut first_error = true; let mut pump_time_s = 0; if !dry_run { - board.board_hal.get_tank_sensor().unwrap().reset_flow_meter(); - board.board_hal.get_tank_sensor().unwrap().start_flow_meter(); + board + .board_hal + .get_tank_sensor() + .unwrap() + .reset_flow_meter(); + board + .board_hal + .get_tank_sensor() + .unwrap() + .start_flow_meter(); board.board_hal.pump(plant_id, true)?; Delay::new_default().delay_ms(10); for step in 0..plant_config.pump_time_s as usize { - let flow_value = board.board_hal.get_tank_sensor().unwrap().get_flow_meter_value(); + let flow_value = board + .board_hal + .get_tank_sensor() + .unwrap() + .get_flow_meter_value(); flow_collector[step] = flow_value; let flow_value_ml = flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; - println!("Flow value is {} ml, limit is {} ml raw sensor {}", flow_value_ml, plant_config.pump_limit_ml, flow_value); + println!( + "Flow value is {} ml, limit is {} ml raw sensor {}", + flow_value_ml, plant_config.pump_limit_ml, flow_value + ); if flow_value_ml > plant_config.pump_limit_ml as f32 { println!("Flow value is reached, stopping"); break; @@ -672,9 +687,16 @@ pub fn do_secure_pump( } } board.board_hal.get_tank_sensor().unwrap().stop_flow_meter(); - let final_flow_value = board.board_hal.get_tank_sensor().unwrap().get_flow_meter_value(); + let final_flow_value = board + .board_hal + .get_tank_sensor() + .unwrap() + .get_flow_meter_value(); let flow_value_ml = final_flow_value as f32 * board.board_hal.get_config().tank.ml_per_pulse; - println!("Final flow value is {} with {} ml", final_flow_value, flow_value_ml); + println!( + "Final flow value is {} with {} ml", + final_flow_value, flow_value_ml + ); current_collector.sort(); Ok(PumpResult { median_current_ma: current_collector[current_collector.len() / 2], diff --git a/rust/src/plant_state.rs b/rust/src/plant_state.rs index 38204cf..ae57373 100644 --- a/rust/src/plant_state.rs +++ b/rust/src/plant_state.rs @@ -78,6 +78,7 @@ impl PumpState { pub enum PlantWateringMode { OFF, TargetMoisture, + MinMoisture, TimerOnly, } @@ -235,6 +236,30 @@ impl PlantState { false } } + PlantWateringMode::MinMoisture => { + let (moisture_percent, _) = self.plant_moisture(); + if let Some(moisture_percent) = moisture_percent { + if self.pump_in_timeout(plant_conf, current_time) { + false + } else if !in_time_range( + current_time, + plant_conf.pump_hour_start, + plant_conf.pump_hour_end, + ) { + false + } else if (true) { + //if not cooldown min and below max + true + } else if (true) { + //if below min disable cooldown min + true + } else { + false + } + } else { + false + } + } PlantWateringMode::TimerOnly => !self.pump_in_timeout(plant_conf, current_time), } } @@ -268,7 +293,9 @@ impl PlantState { .map(|t| t.with_timezone(¤t_time.timezone())), next_pump: if matches!( plant_conf.mode, - PlantWateringMode::TimerOnly | PlantWateringMode::TargetMoisture + PlantWateringMode::TimerOnly + | PlantWateringMode::TargetMoisture + | PlantWateringMode::MinMoisture ) { self.pump.previous_pump.and_then(|last_pump| { last_pump diff --git a/rust/src_webpack/src/api.ts b/rust/src_webpack/src/api.ts index 7f46855..e02ed93 100644 --- a/rust/src_webpack/src/api.ts +++ b/rust/src_webpack/src/api.ts @@ -105,6 +105,7 @@ export interface PlantControllerConfig { export interface PlantConfig { mode: string, target_moisture: number, + min_moisture: number, pump_time_s: number, pump_cooldown_min: number, pump_hour_start: number, diff --git a/rust/src_webpack/src/plant.html b/rust/src_webpack/src/plant.html index 77b16f5..230838e 100644 --- a/rust/src_webpack/src/plant.html +++ b/rust/src_webpack/src/plant.html @@ -55,6 +55,7 @@ @@ -63,6 +64,10 @@
Target Moisture:
+
+
Minimum Moisture:
+ +
Pump Time (s):
diff --git a/rust/src_webpack/src/plant.ts b/rust/src_webpack/src/plant.ts index 6f29fc4..12e33b2 100644 --- a/rust/src_webpack/src/plant.ts +++ b/rust/src_webpack/src/plant.ts @@ -59,6 +59,7 @@ export class PlantView { private readonly header: HTMLElement; private readonly testButton: HTMLButtonElement; private readonly targetMoisture: HTMLInputElement; + private readonly minMoisture: HTMLInputElement; private readonly pumpTimeS: HTMLInputElement; private readonly pumpCooldown: HTMLInputElement; private readonly pumpHourStart: HTMLSelectElement; @@ -118,6 +119,11 @@ export class PlantView { controller.configChanged() } + this.minMoisture = document.getElementById("plant_" + plantId + "_min_moisture")! as HTMLInputElement; + this.minMoisture.onchange = function () { + controller.configChanged() + } + this.pumpTimeS = document.getElementById("plant_" + plantId + "_pump_time_s") as HTMLInputElement; this.pumpTimeS.onchange = function () { controller.configChanged() @@ -203,13 +209,15 @@ export class PlantView { let sensorOnly = document.getElementsByClassName("plantSensorEnabledOnly_"+ this.plantId) let pumpOnly = document.getElementsByClassName("plantPumpEnabledOnly_"+ this.plantId) let targetOnly = document.getElementsByClassName("plantTargetEnabledOnly_"+ this.plantId) + let minOnly = document.getElementsByClassName("plantMinEnabledOnly_"+ this.plantId) console.log("updateVisibility plantConfig: " + plantConfig.mode) let showSensor = plantConfig.sensor_a || plantConfig.sensor_b let showPump = plantConfig.mode !== "OFF" let showTarget = plantConfig.mode === "TargetMoisture" + let showMin = plantConfig.mode === "MinMoisture" - console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget) + console.log("updateVisibility showsensor: " + showSensor + " pump " + showPump + " target " +showTarget + " min " + showMin) for (const element of Array.from(sensorOnly)) { if (showSensor) { @@ -234,6 +242,14 @@ export class PlantView { element.classList.add("plantHidden_" + this.plantId) } } + + for (const element of Array.from(minOnly)) { + if (showMin) { + element.classList.remove("plantHidden_" + this.plantId) + } else { + element.classList.add("plantHidden_" + this.plantId) + } + } } setTestResult(result: PumpTestResult) { @@ -255,6 +271,7 @@ export class PlantView { setConfig(plantConfig: PlantConfig) { this.mode.value = plantConfig.mode; this.targetMoisture.value = plantConfig.target_moisture.toString(); + this.minMoisture.value = plantConfig.min_moisture?.toString() || ""; this.pumpTimeS.value = plantConfig.pump_time_s.toString(); this.pumpCooldown.value = plantConfig.pump_cooldown_min.toString(); this.pumpHourStart.value = plantConfig.pump_hour_start.toString(); @@ -280,6 +297,7 @@ export class PlantView { let conv: PlantConfig = { mode: this.mode.value, target_moisture: this.targetMoisture.valueAsNumber, + min_moisture: this.minMoisture.valueAsNumber, pump_time_s: this.pumpTimeS.valueAsNumber, pump_cooldown_min: this.pumpCooldown.valueAsNumber, pump_hour_start: +this.pumpHourStart.value,