sensor sweep tester
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
|
use crate::hal::Sensor;
|
||||||
use bincode::{Decode, Encode};
|
use bincode::{Decode, Encode};
|
||||||
|
|
||||||
pub(crate) const SENSOR_BASE_ADDRESS: u16 = 1000;
|
pub(crate) const SENSOR_BASE_ADDRESS: u16 = 1000;
|
||||||
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
||||||
pub(crate) struct RequestMoisture {
|
pub(crate) struct AutoDetectRequest {}
|
||||||
pub(crate) sensor: Sensor,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
#[derive(Debug, Clone, Copy, Encode, Decode)]
|
||||||
pub(crate) struct ResponseMoisture {
|
pub(crate) struct ResponseMoisture {
|
||||||
@@ -12,9 +11,3 @@ pub(crate) struct ResponseMoisture {
|
|||||||
pub sensor: Sensor,
|
pub sensor: Sensor,
|
||||||
pub hz: u32,
|
pub hz: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq, Eq)]
|
|
||||||
pub(crate) enum Sensor {
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
}
|
|
||||||
|
@@ -56,6 +56,7 @@ use alloc::boxed::Box;
|
|||||||
use alloc::format;
|
use alloc::format;
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use bincode::{Decode, Encode};
|
||||||
use bq34z100::Bq34z100g1Driver;
|
use bq34z100::Bq34z100g1Driver;
|
||||||
use chrono::{DateTime, FixedOffset, Utc};
|
use chrono::{DateTime, FixedOffset, Utc};
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
@@ -117,7 +118,7 @@ pub static I2C_DRIVER: OnceLock<
|
|||||||
embassy_sync::blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<I2c<Blocking>>>,
|
embassy_sync::blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<I2c<Blocking>>>,
|
||||||
> = OnceLock::new();
|
> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Clone, Copy, Encode, Decode)]
|
||||||
pub enum Sensor {
|
pub enum Sensor {
|
||||||
A,
|
A,
|
||||||
B,
|
B,
|
||||||
@@ -152,6 +153,11 @@ pub trait BoardInteraction<'a> {
|
|||||||
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError>;
|
async fn get_mptt_voltage(&mut self) -> Result<Voltage, FatError>;
|
||||||
async fn get_mptt_current(&mut self) -> Result<Current, FatError>;
|
async fn get_mptt_current(&mut self) -> Result<Current, FatError>;
|
||||||
|
|
||||||
|
// Return JSON string with autodetected sensors per plant. Default: not supported.
|
||||||
|
async fn detect_sensors(&mut self) -> Result<alloc::string::String, FatError> {
|
||||||
|
bail!("Autodetection is only available on v4 HAL with CAN bus");
|
||||||
|
}
|
||||||
|
|
||||||
async fn progress(&mut self, counter: u32) {
|
async fn progress(&mut self, counter: u32) {
|
||||||
// Indicate progress is active to suppress default wait_infinity blinking
|
// Indicate progress is active to suppress default wait_infinity blinking
|
||||||
crate::hal::PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed);
|
crate::hal::PROGRESS_ACTIVE.store(true, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
@@ -201,8 +201,8 @@ pub(crate) async fn create_v4(
|
|||||||
log::info!("Can bus mode ");
|
log::info!("Can bus mode ");
|
||||||
let twai_config = Some(twai::TwaiConfiguration::new(
|
let twai_config = Some(twai::TwaiConfiguration::new(
|
||||||
peripherals.twai,
|
peripherals.twai,
|
||||||
peripherals.gpio0,
|
|
||||||
peripherals.gpio2,
|
peripherals.gpio2,
|
||||||
|
peripherals.gpio0,
|
||||||
TWAI_BAUDRATE,
|
TWAI_BAUDRATE,
|
||||||
TwaiMode::Normal,
|
TwaiMode::Normal,
|
||||||
));
|
));
|
||||||
@@ -458,4 +458,24 @@ impl<'a> BoardInteraction<'a> for V4<'a> {
|
|||||||
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
|
async fn get_mptt_current(&mut self) -> Result<Current, FatError> {
|
||||||
self.charger.get_mppt_current()
|
self.charger.get_mppt_current()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn detect_sensors(&mut self) -> Result<alloc::string::String, FatError> {
|
||||||
|
// Delegate to sensor autodetect and build JSON
|
||||||
|
use alloc::string::ToString;
|
||||||
|
let detected = self.sensor.autodetect().await?;
|
||||||
|
// Build JSON manually to avoid exposing internal types
|
||||||
|
let mut s = alloc::string::String::from("{\"plants\":[");
|
||||||
|
for (i, (a, b)) in detected.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
s.push(',');
|
||||||
|
}
|
||||||
|
s.push_str("{\"a\":");
|
||||||
|
s.push_str(if *a { "true" } else { "false" });
|
||||||
|
s.push_str(",\"b\":");
|
||||||
|
s.push_str(if *b { "true" } else { "false" });
|
||||||
|
s.push('}');
|
||||||
|
}
|
||||||
|
s.push_str("]}");
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,15 +9,17 @@ use alloc::string::ToString;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bincode::config;
|
use bincode::config;
|
||||||
use bincode::error::DecodeError;
|
use bincode::error::DecodeError;
|
||||||
use can_api::RequestMoisture;
|
use core::mem;
|
||||||
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_time::{Instant, Timer};
|
use embassy_sync::mutex::Mutex;
|
||||||
|
use embassy_time::{Instant, Timer, WithTimeout};
|
||||||
|
use embedded_can::nb::Can;
|
||||||
use embedded_can::Frame;
|
use embedded_can::Frame;
|
||||||
use esp_hal::gpio::Output;
|
use esp_hal::gpio::Output;
|
||||||
use esp_hal::i2c::master::I2c;
|
use esp_hal::i2c::master::I2c;
|
||||||
use esp_hal::pcnt::unit::Unit;
|
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::Blocking;
|
use esp_hal::Blocking;
|
||||||
use log::info;
|
use log::info;
|
||||||
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
use pca9535::{GPIOBank, Pca9535Immediate, StandardExpanderInterface};
|
||||||
@@ -140,9 +142,22 @@ impl SensorInteraction for SensorImpl {
|
|||||||
let config = twai_config.take().expect("twai config not set");
|
let config = twai_config.take().expect("twai config not set");
|
||||||
let mut twai = config.start();
|
let mut twai = config.start();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let rec = twai.receive();
|
||||||
|
match rec {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
info!("Error receiving CAN message: {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer::after_millis(10).await;
|
Timer::after_millis(10).await;
|
||||||
let can = Self::inner_can(plant, sensor, &mut twai).await;
|
let can = Self::inner_can(plant, sensor, &mut twai).await;
|
||||||
|
|
||||||
can_power.set_low();
|
can_power.set_low();
|
||||||
|
|
||||||
let config = twai.stop();
|
let config = twai.stop();
|
||||||
twai_config.replace(config);
|
twai_config.replace(config);
|
||||||
|
|
||||||
@@ -154,21 +169,104 @@ impl SensorInteraction for SensorImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SensorImpl {
|
impl SensorImpl {
|
||||||
|
pub async fn autodetect(&mut self) -> FatResult<[(bool, bool); crate::hal::PLANT_COUNT]> {
|
||||||
|
match self {
|
||||||
|
SensorImpl::PulseCounter { .. } => {
|
||||||
|
bail!("Only CAN bus implementation supports autodetection")
|
||||||
|
}
|
||||||
|
SensorImpl::CanBus {
|
||||||
|
twai_config,
|
||||||
|
can_power,
|
||||||
|
} => {
|
||||||
|
// Power on CAN transceiver and start controller
|
||||||
|
can_power.set_high();
|
||||||
|
let config = twai_config.take().expect("twai config not set");
|
||||||
|
let mut twai = config.start();
|
||||||
|
|
||||||
|
// Give CAN some time to stabilize
|
||||||
|
Timer::after_millis(10).await;
|
||||||
|
|
||||||
|
// Send a few test messages per potential sensor node
|
||||||
|
for plant in 0..crate::hal::PLANT_COUNT {
|
||||||
|
for sensor in [Sensor::A, Sensor::B] {
|
||||||
|
// Reuse CAN addressing scheme from moisture request
|
||||||
|
let can_buffer = [0_u8; 8];
|
||||||
|
let cfg = config::standard();
|
||||||
|
if let Some(address) =
|
||||||
|
StandardId::new(can_api::SENSOR_BASE_ADDRESS + plant as u16)
|
||||||
|
{
|
||||||
|
if let Some(frame) = EspTwaiFrame::new(address, &can_buffer) {
|
||||||
|
// Try a few times; we intentionally ignore rx here and rely on stub logic
|
||||||
|
let resu = twai.transmit(&frame);
|
||||||
|
match resu {
|
||||||
|
Ok(_) => {
|
||||||
|
info!(
|
||||||
|
"Sent test message to plant {} sensor {:?}",
|
||||||
|
plant, sensor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
info!("Error sending test message to plant {} sensor {:?}: {:?}", plant, sensor, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Error building CAN frame");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Error creating address for sensor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let rec = twai
|
||||||
|
.receive()
|
||||||
|
.with_timeout(embassy_time::Duration::from_millis(100))
|
||||||
|
.await;
|
||||||
|
match rec {
|
||||||
|
Ok(msg) => match msg {
|
||||||
|
Ok(or) => {
|
||||||
|
info!("Received CAN message: {:?}", or);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
info!("Error receiving CAN message: {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
info!("Error receiving CAN message: {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for acknowledgements on the bus (stub: just wait 5 seconds)
|
||||||
|
Timer::after_millis(5_000).await;
|
||||||
|
// Stop CAN and power down
|
||||||
|
can_power.set_low();
|
||||||
|
twai_config.replace(config);
|
||||||
|
|
||||||
|
// Stub: return no detections yet
|
||||||
|
let mut result = [(false, false); crate::hal::PLANT_COUNT];
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn inner_can(
|
async fn inner_can(
|
||||||
plant: usize,
|
plant: usize,
|
||||||
sensor: Sensor,
|
sensor: Sensor,
|
||||||
twai: &mut Twai<'static, Blocking>,
|
twai: &mut Twai<'static, Blocking>,
|
||||||
) -> FatResult<f32> {
|
) -> FatResult<f32> {
|
||||||
let can_sensor: can_api::Sensor = sensor.into();
|
let can_sensor: Sensor = sensor.into();
|
||||||
let request = RequestMoisture { sensor: can_sensor };
|
//let request = RequestMoisture { sensor: can_sensor };
|
||||||
let mut can_buffer = [0_u8; 8];
|
let can_buffer = [0_u8; 8];
|
||||||
let config = config::standard();
|
let config = config::standard();
|
||||||
let encoded = bincode::encode_into_slice(&request, &mut can_buffer, config)?;
|
//let encoded = bincode::encode_into_slice(&request, &mut can_buffer, config)?;
|
||||||
|
|
||||||
let address = StandardId::new(can_api::SENSOR_BASE_ADDRESS + plant as u16)
|
let address = StandardId::new(can_api::SENSOR_BASE_ADDRESS + plant as u16)
|
||||||
.context(">> Could not create address for sensor! (plant: {}) <<")?;
|
.context(">> Could not create address for sensor! (plant: {}) <<")?;
|
||||||
let request = EspTwaiFrame::new(address, &can_buffer[0..encoded])
|
let request =
|
||||||
.context("Error building CAN frame")?;
|
EspTwaiFrame::new(address, &can_buffer[0..8]).context("Error building CAN frame")?;
|
||||||
twai.transmit(&request)?;
|
twai.transmit(&request)?;
|
||||||
|
|
||||||
let timeout = Instant::now()
|
let timeout = Instant::now()
|
||||||
@@ -214,12 +312,3 @@ impl SensorImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Sensor> for can_api::Sensor {
|
|
||||||
fn from(value: Sensor) -> Self {
|
|
||||||
match value {
|
|
||||||
Sensor::A => can_api::Sensor::A,
|
|
||||||
Sensor::B => can_api::Sensor::B,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -19,7 +19,7 @@ use crate::webserver::get_log::get_log;
|
|||||||
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};
|
use crate::webserver::get_static::{serve_bundle, serve_favicon, serve_index};
|
||||||
use crate::webserver::ota::ota_operations;
|
use crate::webserver::ota::ota_operations;
|
||||||
use crate::webserver::post_json::{
|
use crate::webserver::post_json::{
|
||||||
board_test, night_lamp_test, pump_test, set_config, wifi_scan, write_time,
|
board_test, night_lamp_test, pump_test, set_config, wifi_scan, write_time, detect_sensors,
|
||||||
};
|
};
|
||||||
use crate::{bail, BOARD_ACCESS};
|
use crate::{bail, BOARD_ACCESS};
|
||||||
use alloc::borrow::ToOwned;
|
use alloc::borrow::ToOwned;
|
||||||
@@ -151,6 +151,7 @@ impl Handler for HTTPRequestRouter {
|
|||||||
"/pumptest" => Some(pump_test(conn).await),
|
"/pumptest" => Some(pump_test(conn).await),
|
||||||
"/lamptest" => Some(night_lamp_test(conn).await),
|
"/lamptest" => Some(night_lamp_test(conn).await),
|
||||||
"/boardtest" => Some(board_test().await),
|
"/boardtest" => Some(board_test().await),
|
||||||
|
"/detect_sensors" => Some(detect_sensors().await),
|
||||||
"/reboot" => {
|
"/reboot" => {
|
||||||
let mut board = BOARD_ACCESS.get().await.lock().await;
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
board.board_hal.get_esp().set_restart_to_conf(true);
|
board.board_hal.get_esp().set_restart_to_conf(true);
|
||||||
|
@@ -50,6 +50,12 @@ pub(crate) async fn board_test() -> FatResult<Option<String>> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn detect_sensors() -> FatResult<Option<String>> {
|
||||||
|
let mut board = BOARD_ACCESS.get().await.lock().await;
|
||||||
|
let json = board.board_hal.detect_sensors().await?;
|
||||||
|
Ok(Some(json))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn pump_test<T, const N: usize>(
|
pub(crate) async fn pump_test<T, const N: usize>(
|
||||||
request: &mut Connection<'_, T, N>,
|
request: &mut Connection<'_, T, N>,
|
||||||
) -> FatResult<Option<String>>
|
) -> FatResult<Option<String>>
|
||||||
|
@@ -173,6 +173,15 @@ export interface BatteryState {
|
|||||||
state_of_health: string
|
state_of_health: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DetectionPlant {
|
||||||
|
a: boolean,
|
||||||
|
b: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DetectionResult {
|
||||||
|
plants: DetectionPlant[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface TankInfo {
|
export interface TankInfo {
|
||||||
/// is there enough water in the tank
|
/// is there enough water in the tank
|
||||||
enough_water: boolean,
|
enough_water: boolean,
|
||||||
|
@@ -163,6 +163,7 @@
|
|||||||
|
|
||||||
<h3>Plants:</h3>
|
<h3>Plants:</h3>
|
||||||
<button id="measure_moisture">Measure Moisture</button>
|
<button id="measure_moisture">Measure Moisture</button>
|
||||||
|
<button id="detect_sensors" style="display:none">Detect/Test Sensors</button>
|
||||||
<div id="plants" class="plantlist"></div>
|
<div id="plants" class="plantlist"></div>
|
||||||
|
|
||||||
<div class="flexcontainer-rev">
|
<div class="flexcontainer-rev">
|
||||||
|
@@ -358,6 +358,36 @@ export class Controller {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async detectSensors() {
|
||||||
|
let counter = 0
|
||||||
|
let limit = 5
|
||||||
|
controller.progressview.addProgress("detect_sensors", counter / limit * 100, "Detecting sensors " + (limit - counter) + "s")
|
||||||
|
|
||||||
|
let timerId: string | number | NodeJS.Timeout | undefined
|
||||||
|
|
||||||
|
function updateProgress() {
|
||||||
|
counter++;
|
||||||
|
controller.progressview.addProgress("detect_sensors", counter / limit * 100, "Detecting sensors " + (limit - counter) + "s")
|
||||||
|
timerId = setTimeout(updateProgress, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
timerId = setTimeout(updateProgress, 1000);
|
||||||
|
|
||||||
|
fetch(PUBLIC_URL + "/detect_sensors", { method: "POST" })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
clearTimeout(timerId);
|
||||||
|
controller.progressview.removeProgress("detect_sensors");
|
||||||
|
const pretty = JSON.stringify(json);
|
||||||
|
toast.info("Detection result: " + pretty);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
clearTimeout(timerId);
|
||||||
|
controller.progressview.removeProgress("detect_sensors");
|
||||||
|
toast.error("Autodetect failed: " + error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getConfig(): PlantControllerConfig {
|
getConfig(): PlantControllerConfig {
|
||||||
return {
|
return {
|
||||||
hardware: controller.hardwareView.getConfig(),
|
hardware: controller.hardwareView.getConfig(),
|
||||||
@@ -405,6 +435,12 @@ export class Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setConfig(current: PlantControllerConfig) {
|
setConfig(current: PlantControllerConfig) {
|
||||||
|
// Show Detect/Test button only for V4 HAL
|
||||||
|
if (current.hardware && (current.hardware as any).board === "V4") {
|
||||||
|
this.detectBtn.style.display = "inline-block";
|
||||||
|
} else {
|
||||||
|
this.detectBtn.style.display = "none";
|
||||||
|
}
|
||||||
this.tankView.setConfig(current.tank);
|
this.tankView.setConfig(current.tank);
|
||||||
this.networkView.setConfig(current.network);
|
this.networkView.setConfig(current.network);
|
||||||
this.nightLampView.setConfig(current.night_lamp);
|
this.nightLampView.setConfig(current.night_lamp);
|
||||||
@@ -500,6 +536,7 @@ export class Controller {
|
|||||||
readonly solarView: SolarView;
|
readonly solarView: SolarView;
|
||||||
readonly fileview: FileView;
|
readonly fileview: FileView;
|
||||||
readonly logView: LogView
|
readonly logView: LogView
|
||||||
|
readonly detectBtn: HTMLButtonElement
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.timeView = new TimeView(this)
|
this.timeView = new TimeView(this)
|
||||||
@@ -515,6 +552,8 @@ export class Controller {
|
|||||||
this.fileview = new FileView(this)
|
this.fileview = new FileView(this)
|
||||||
this.logView = new LogView(this)
|
this.logView = new LogView(this)
|
||||||
this.hardwareView = new HardwareConfigView(this)
|
this.hardwareView = new HardwareConfigView(this)
|
||||||
|
this.detectBtn = document.getElementById("detect_sensors") as HTMLButtonElement
|
||||||
|
this.detectBtn.onclick = () => { controller.detectSensors(); }
|
||||||
this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
|
this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
|
||||||
this.rebootBtn.onclick = () => {
|
this.rebootBtn.onclick = () => {
|
||||||
controller.reboot();
|
controller.reboot();
|
||||||
|
@@ -26,6 +26,7 @@ embassy-usb = { version = "0.3.0" }
|
|||||||
embassy-futures = { version = "0.1.0" }
|
embassy-futures = { version = "0.1.0" }
|
||||||
embassy-sync = { version = "0.6.0" }
|
embassy-sync = { version = "0.6.0" }
|
||||||
embedded-can = "0.4.1"
|
embedded-can = "0.4.1"
|
||||||
|
embedded-alloc = { version = "0.6.0", default-features = false, features = ["llff"] }
|
||||||
|
|
||||||
# This is okay because we should automatically use whatever ch32-hal uses
|
# This is okay because we should automatically use whatever ch32-hal uses
|
||||||
qingke-rt = "*"
|
qingke-rt = "*"
|
||||||
|
@@ -28,12 +28,6 @@ If you need to map a label to code, use the same letter+number as in the silkscr
|
|||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
## USB CDC Console (optional)
|
|
||||||
|
|
||||||
This project includes an optional software USB CDC-ACM device stack using embassy-usb. It runs on the CH32V203’s USB device peripheral but implements the protocol fully in software (no built-in USB class firmware is required).
|
|
||||||
|
|
||||||
How to enable:
|
|
||||||
- Build with the `usb-cdc` feature: `cargo build --release --features usb-cdc`
|
|
||||||
- Wire the MCU’s USB pins to a USB connector:
|
- Wire the MCU’s USB pins to a USB connector:
|
||||||
- D+ (PA12)
|
- D+ (PA12)
|
||||||
- D− (PA11)
|
- D− (PA11)
|
||||||
@@ -46,12 +40,6 @@ Example:
|
|||||||
- macOS: `screen /dev/tty.usbmodemXXXX 115200`
|
- macOS: `screen /dev/tty.usbmodemXXXX 115200`
|
||||||
- Windows: Use PuTTY on the shown COM port.
|
- Windows: Use PuTTY on the shown COM port.
|
||||||
|
|
||||||
Notes:
|
|
||||||
- The firmware currently implements an echo console: bytes you type are echoed back. You can extend it to print logs or interact with your application.
|
|
||||||
- If you don’t see a device, ensure D+ (PA12) and D− (PA11) are connected and the cable supports data.
|
|
||||||
|
|
||||||
## Flash
|
|
||||||
|
|
||||||
You can flash the built ELF using wchisp (WCH ISP tool):
|
You can flash the built ELF using wchisp (WCH ISP tool):
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
use crate::hal::peripherals::CAN1;
|
use crate::hal::peripherals::CAN1;
|
||||||
use core::fmt::Write as _;
|
use core::fmt::Write as _;
|
||||||
@@ -8,7 +8,7 @@ use ch32_hal::gpio::{Level, Output, Speed};
|
|||||||
use ch32_hal::adc::{Adc, SampleTime, ADC_MAX};
|
use ch32_hal::adc::{Adc, SampleTime, ADC_MAX};
|
||||||
use ch32_hal::can;
|
use ch32_hal::can;
|
||||||
use ch32_hal::can::{Can, CanFifo, CanFilter, CanFrame, CanMode};
|
use ch32_hal::can::{Can, CanFifo, CanFilter, CanFrame, CanMode};
|
||||||
use ch32_hal::mode::{Blocking, Mode};
|
use ch32_hal::mode::{Blocking};
|
||||||
use ch32_hal::peripherals::USBD;
|
use ch32_hal::peripherals::USBD;
|
||||||
// use ch32_hal::delay::Delay;
|
// use ch32_hal::delay::Delay;
|
||||||
use embassy_executor::{Spawner, task};
|
use embassy_executor::{Spawner, task};
|
||||||
@@ -18,10 +18,10 @@ use embassy_futures::yield_now;
|
|||||||
use hal::usbd::{Driver};
|
use hal::usbd::{Driver};
|
||||||
use hal::{bind_interrupts};
|
use hal::{bind_interrupts};
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::channel::{Channel, TrySendError};
|
use embassy_sync::channel::{Channel};
|
||||||
use embassy_time::{Timer, Instant, Duration, Ticker};
|
use embassy_time::{Instant, Duration};
|
||||||
|
use embedded_can::blocking::Can as bcan;
|
||||||
use embedded_can::StandardId;
|
use embedded_can::StandardId;
|
||||||
use heapless::String;
|
|
||||||
use {ch32_hal as hal, panic_halt as _};
|
use {ch32_hal as hal, panic_halt as _};
|
||||||
|
|
||||||
macro_rules! mk_static {
|
macro_rules! mk_static {
|
||||||
@@ -37,10 +37,22 @@ bind_interrupts!(struct Irqs {
|
|||||||
USB_LP_CAN1_RX0 => hal::usbd::InterruptHandler<hal::peripherals::USBD>;
|
USB_LP_CAN1_RX0 => hal::usbd::InterruptHandler<hal::peripherals::USBD>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
use embedded_alloc::LlffHeap as Heap;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static HEAP: Heap = Heap::empty();
|
||||||
|
|
||||||
|
|
||||||
static LOG_CH: Channel<CriticalSectionRawMutex, heapless::String<128>, 8> = Channel::new();
|
static LOG_CH: Channel<CriticalSectionRawMutex, heapless::String<128>, 8> = Channel::new();
|
||||||
|
|
||||||
#[embassy_executor::main(entry = "qingke_rt::entry")]
|
#[embassy_executor::main(entry = "qingke_rt::entry")]
|
||||||
async fn main(spawner: Spawner) {
|
async fn main(spawner: Spawner) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
let p = hal::init(hal::Config {
|
let p = hal::init(hal::Config {
|
||||||
rcc: hal::rcc::Config::SYSCLK_FREQ_144MHZ_HSI,
|
rcc: hal::rcc::Config::SYSCLK_FREQ_144MHZ_HSI,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -105,13 +117,11 @@ async fn main(spawner: Spawner) {
|
|||||||
can.add_filter(CanFilter::accept_all());
|
can.add_filter(CanFilter::accept_all());
|
||||||
|
|
||||||
|
|
||||||
// Spawn independent tasks using 'static references
|
spawner.spawn(usb_task(usb)).unwrap();
|
||||||
unsafe {
|
spawner.spawn(usb_writer(class)).unwrap();
|
||||||
spawner.spawn(usb_task(usb)).unwrap();
|
// move Q output, LED, ADC and analog input into worker task
|
||||||
spawner.spawn(usb_writer(class)).unwrap();
|
spawner.spawn(worker(q_out, led, adc, ain, can)).unwrap();
|
||||||
// move Q output, LED, ADC and analog input into worker task
|
|
||||||
spawner.spawn(worker(q_out, led, adc, ain, can)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Prevent main from exiting
|
// Prevent main from exiting
|
||||||
@@ -182,6 +192,31 @@ async fn worker(
|
|||||||
pulses, freq_hz
|
pulses, freq_hz
|
||||||
);
|
);
|
||||||
log(msg);
|
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) {
|
||||||
|
Ok(..) => {
|
||||||
|
let mut msg: heapless::String<128> = heapless::String::new();
|
||||||
|
let _ = write!(
|
||||||
|
&mut msg,
|
||||||
|
"Send to canbus"
|
||||||
|
);
|
||||||
|
log(msg);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let mut msg: heapless::String<128> = heapless::String::new();
|
||||||
|
let _ = write!(
|
||||||
|
&mut msg,
|
||||||
|
"err {}"
|
||||||
|
,err
|
||||||
|
);
|
||||||
|
log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user