add: implement CAN communication channels, create can_task for RX/TX handling, enhance error detection, and reorganize configuration validation logic
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
use crate::hal::peripherals::CAN1;
|
use crate::hal::peripherals::CAN1;
|
||||||
use canapi::id::{plant_id, MessageKind, IDENTIFY_CMD_OFFSET, MOISTURE_DATA_OFFSET};
|
use canapi::id::{plant_id, IDENTIFY_CMD_OFFSET, MOISTURE_DATA_OFFSET};
|
||||||
use canapi::SensorSlot;
|
use canapi::SensorSlot;
|
||||||
use ch32_hal::adc::{Adc, SampleTime, ADC_MAX};
|
use ch32_hal::adc::{Adc, SampleTime, ADC_MAX};
|
||||||
use ch32_hal::can;
|
use ch32_hal::can;
|
||||||
@@ -16,7 +16,7 @@ use embassy_executor::{task, Spawner};
|
|||||||
use embassy_futures::yield_now;
|
use embassy_futures::yield_now;
|
||||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||||
use embassy_sync::channel::Channel;
|
use embassy_sync::channel::Channel;
|
||||||
use embassy_time::{Delay, Duration, Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
|
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
|
||||||
use embassy_usb::{Builder, UsbDevice};
|
use embassy_usb::{Builder, UsbDevice};
|
||||||
use embedded_alloc::LlffHeap as Heap;
|
use embedded_alloc::LlffHeap as Heap;
|
||||||
@@ -42,6 +42,8 @@ bind_interrupts!(struct Irqs {
|
|||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static HEAP: Heap = Heap::empty();
|
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();
|
||||||
|
static CAN_RX_CH: Channel<CriticalSectionRawMutex, CanFrame, 4> = Channel::new();
|
||||||
|
static CAN_TX_CH: Channel<CriticalSectionRawMutex, CanFrame, 4> = 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) {
|
||||||
@@ -66,7 +68,7 @@ async fn main(spawner: Spawner) {
|
|||||||
// Create GPIO for 555 Q output (PB0)
|
// Create GPIO for 555 Q output (PB0)
|
||||||
let q_out = Output::new(p.PA0, 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 info = Output::new(p.PA10, Level::Low, Speed::Low);
|
||||||
let mut warn = mk_static!(Output, Output::new(p.PA9, Level::Low, Speed::Low));
|
let warn = mk_static!(Output, Output::new(p.PA9, Level::Low, Speed::Low));
|
||||||
|
|
||||||
// Read configuration switches on PB3..PB7 at startup with floating detection
|
// Read configuration switches on PB3..PB7 at startup with floating detection
|
||||||
// PB3: Sensor A/B selector (Low=A, High=B)
|
// PB3: Sensor A/B selector (Low=A, High=B)
|
||||||
@@ -105,12 +107,16 @@ async fn main(spawner: Spawner) {
|
|||||||
}
|
}
|
||||||
let moisture_id = plant_id(MOISTURE_DATA_OFFSET, slot, addr as u16);
|
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 identify_id = plant_id(IDENTIFY_CMD_OFFSET, slot, addr as u16);
|
||||||
|
let standard_identify_id = StandardId::new(identify_id).unwrap();
|
||||||
|
|
||||||
|
//is any floating, or invalid addr (only 1-8 are valid)
|
||||||
let invalid_config = sensor_ab_config.is_none()
|
let invalid_config = sensor_ab_config.is_none()
|
||||||
|| sensor_address_bit_1_config.is_none()
|
|| sensor_address_bit_1_config.is_none()
|
||||||
|| sensor_address_bit_2_config.is_none()
|
|| sensor_address_bit_2_config.is_none()
|
||||||
|| sensor_address_bit_3_config.is_none()
|
|| sensor_address_bit_3_config.is_none()
|
||||||
|| sensor_address_bit_4_config.is_none();
|
|| sensor_address_bit_4_config.is_none()
|
||||||
|
|| addr == 0
|
||||||
|
|| addr > 8;
|
||||||
|
|
||||||
let mut config = embassy_usb::Config::new(0xC0DE, 0xCAFE);
|
let mut config = embassy_usb::Config::new(0xC0DE, 0xCAFE);
|
||||||
config.manufacturer = Some("Can Sensor v0.2");
|
config.manufacturer = Some("Can Sensor v0.2");
|
||||||
@@ -170,16 +176,28 @@ async fn main(spawner: Spawner) {
|
|||||||
3
|
3
|
||||||
} else if sensor_address_bit_4_config.is_none() {
|
} else if sensor_address_bit_4_config.is_none() {
|
||||||
4
|
4
|
||||||
} else {
|
} else if sensor_ab_config.is_none() {
|
||||||
5
|
5
|
||||||
}; // PB3 -> 5
|
} else {
|
||||||
let which = match code {
|
6 // Invalid address (0 or > 8)
|
||||||
1 => "PB4",
|
|
||||||
2 => "PB5",
|
|
||||||
3 => "PB6",
|
|
||||||
4 => "PB7",
|
|
||||||
_ => "PB3 (A/B)",
|
|
||||||
};
|
};
|
||||||
|
let which = match code {
|
||||||
|
1 => "PB4 (bit 1)",
|
||||||
|
2 => "PB5 (bit 2)",
|
||||||
|
3 => "PB6 (bit 3)",
|
||||||
|
4 => "PB7 (bit 4)",
|
||||||
|
5 => "PB3 (A/B)",
|
||||||
|
_ => "Address (0 or > 8)",
|
||||||
|
};
|
||||||
|
if code == 6 {
|
||||||
|
let _ = core::fmt::Write::write_fmt(
|
||||||
|
&mut msg,
|
||||||
|
format_args!(
|
||||||
|
"Invalid address {} (only 1-8 allowed) -> blinking code {}. Fix jumpers.\r\n",
|
||||||
|
addr, code
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
let _ = core::fmt::Write::write_fmt(
|
let _ = core::fmt::Write::write_fmt(
|
||||||
&mut msg,
|
&mut msg,
|
||||||
format_args!(
|
format_args!(
|
||||||
@@ -187,6 +205,7 @@ async fn main(spawner: Spawner) {
|
|||||||
which, code
|
which, code
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
log(msg);
|
log(msg);
|
||||||
spawner.spawn(blink_error(warn, code)).unwrap();
|
spawner.spawn(blink_error(warn, code)).unwrap();
|
||||||
} else {
|
} else {
|
||||||
@@ -224,18 +243,22 @@ async fn main(spawner: Spawner) {
|
|||||||
.expect("Valid");
|
.expect("Valid");
|
||||||
ch32_hal::pac::AFIO.pcfr1().write(|w| w.set_can1_rm(2));
|
ch32_hal::pac::AFIO.pcfr1().write(|w| w.set_can1_rm(2));
|
||||||
|
|
||||||
|
can.add_filter(CanFilter::accept_all());
|
||||||
|
// let mut filter = CanFilter::new_id_list();
|
||||||
|
// filter.get(0).unwrap().set(Id::Standard(standard_identify_id), Default::default());
|
||||||
|
// can.add_filter(filter);
|
||||||
|
spawner.spawn(can_task(can, warn, standard_identify_id)).unwrap();
|
||||||
|
|
||||||
// move Q output, LED, ADC and analog input into worker task
|
// move Q output, LED, ADC and analog input into worker task
|
||||||
spawner
|
spawner
|
||||||
.spawn(worker(
|
.spawn(worker(
|
||||||
probe_gnd,
|
probe_gnd,
|
||||||
q_out,
|
q_out,
|
||||||
info,
|
info,
|
||||||
warn,
|
|
||||||
adc,
|
adc,
|
||||||
ain,
|
ain,
|
||||||
can,
|
|
||||||
StandardId::new(moisture_id).unwrap(),
|
StandardId::new(moisture_id).unwrap(),
|
||||||
StandardId::new(identify_id).unwrap(),
|
standard_identify_id,
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -280,15 +303,87 @@ async fn blink_error(info_led: &'static mut Output<'static>, code: u8) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[task]
|
||||||
|
async fn can_task(
|
||||||
|
mut can: Can<'static, CAN1, NonBlocking>,
|
||||||
|
warn: &'static mut Output<'static>,
|
||||||
|
identify_id: StandardId,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
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(),
|
||||||
|
identify_id.as_raw()
|
||||||
|
);
|
||||||
|
log(msg);
|
||||||
|
if s_frame.as_raw() == identify_id.as_raw() {
|
||||||
|
CAN_RX_CH.send(frame).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Id::Extended(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(nb::Error::WouldBlock) => {
|
||||||
|
// No frame available
|
||||||
|
}
|
||||||
|
Err(nb::Error::Other(err)) => {
|
||||||
|
for _ in 0..3 {
|
||||||
|
warn.set_high();
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
warn.set_low();
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
}
|
||||||
|
let mut msg: heapless::String<128> = heapless::String::new();
|
||||||
|
let _ = write!(&mut msg, "rx err {:?}", err);
|
||||||
|
log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Ok(mut frame) = CAN_TX_CH.try_receive() {
|
||||||
|
match can.transmit(&mut frame) {
|
||||||
|
Ok(..) => {
|
||||||
|
}
|
||||||
|
Err(nb::Error::WouldBlock) => {
|
||||||
|
for _ in 0..2 {
|
||||||
|
warn.set_high();
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
warn.set_low();
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
}
|
||||||
|
let mut msg: heapless::String<128> = heapless::String::new();
|
||||||
|
let _ = write!(&mut msg, "canbus out buffer full");
|
||||||
|
log(msg);
|
||||||
|
}
|
||||||
|
Err(nb::Error::Other(err)) => {
|
||||||
|
for _ in 0..3 {
|
||||||
|
warn.set_high();
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
warn.set_low();
|
||||||
|
Timer::after_millis(100).await;
|
||||||
|
}
|
||||||
|
let mut msg: heapless::String<128> = heapless::String::new();
|
||||||
|
let _ = write!(&mut msg, "tx err {:?}", err);
|
||||||
|
log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield_now().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[task]
|
#[task]
|
||||||
async fn worker(
|
async fn worker(
|
||||||
mut probe_gnd: Flex<'static>,
|
mut probe_gnd: Flex<'static>,
|
||||||
mut q: Output<'static>,
|
mut q: Output<'static>,
|
||||||
mut info: Output<'static>,
|
mut info: Output<'static>,
|
||||||
mut warn: &'static mut Output<'static>,
|
|
||||||
mut adc: Adc<'static, hal::peripherals::ADC1>,
|
mut adc: Adc<'static, hal::peripherals::ADC1>,
|
||||||
mut ain: hal::peripherals::PA1,
|
mut ain: hal::peripherals::PA1,
|
||||||
mut can: Can<'static, CAN1, NonBlocking>,
|
|
||||||
moisture_id: StandardId,
|
moisture_id: StandardId,
|
||||||
identify_id: StandardId,
|
identify_id: StandardId,
|
||||||
) {
|
) {
|
||||||
@@ -297,16 +392,6 @@ async fn worker(
|
|||||||
let low_th: u16 = (ADC_MAX as u16) / 3; // ~1/3 Vref
|
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 high_th: u16 = ((ADC_MAX as u32 * 2) / 3) as u16; // ~2/3 Vref
|
||||||
|
|
||||||
let mut filter = CanFilter::new_id_list();
|
|
||||||
|
|
||||||
filter
|
|
||||||
.get(0)
|
|
||||||
.unwrap()
|
|
||||||
.set(identify_id.into(), Default::default());
|
|
||||||
|
|
||||||
//can.add_filter(filter);
|
|
||||||
can.add_filter(CanFilter::accept_all());
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Count rising edges of Q in a 100 ms window
|
// Count rising edges of Q in a 100 ms window
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@@ -352,60 +437,23 @@ async fn worker(
|
|||||||
}
|
}
|
||||||
probe_gnd.set_as_input(Pull::None);
|
probe_gnd.set_as_input(Pull::None);
|
||||||
|
|
||||||
let freq_hz: u32 = pulses * (1000/probe_duration.as_millis()).into(); // pulses per 0.1s => Hz
|
let freq_hz: u32 = pulses * (1000 / probe_duration.as_millis()) as u32; // pulses per 0.1s => Hz
|
||||||
|
|
||||||
let mut msg: heapless::String<128> = heapless::String::new();
|
let mut msg: heapless::String<128> = heapless::String::new();
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
&mut msg,
|
&mut msg,
|
||||||
"555 window={}ms pulses={} freq={} Hz (A1->Q on PB0)\r\n",
|
"555 window={}ms pulses={} freq={} Hz (A1->Q on PB0) id={:?}\r\n",
|
||||||
probe_duration.as_millis(),
|
probe_duration.as_millis(),
|
||||||
pulses,
|
pulses,
|
||||||
freq_hz
|
freq_hz,
|
||||||
);
|
|
||||||
log(msg);
|
|
||||||
|
|
||||||
let mut moisture = CanFrame::new(moisture_id, &(freq_hz as u32).to_be_bytes()).unwrap();
|
|
||||||
match can.transmit(&mut moisture) {
|
|
||||||
Ok(..) => {
|
|
||||||
let mut msg: heapless::String<128> = heapless::String::new();
|
|
||||||
let _ = write!(&mut msg, "Send to canbus");
|
|
||||||
log(msg);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
for _ in 0..3 {
|
|
||||||
warn.set_high();
|
|
||||||
Timer::after_millis(100).await;
|
|
||||||
warn.set_low();
|
|
||||||
Timer::after_millis(100).await;
|
|
||||||
}
|
|
||||||
let mut msg: heapless::String<128> = heapless::String::new();
|
|
||||||
let _ = write!(&mut msg, "err {:?}", err);
|
|
||||||
log(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
identify_id.as_raw()
|
||||||
);
|
);
|
||||||
log(msg);
|
log(msg);
|
||||||
|
|
||||||
yield_now().await;
|
let moisture = CanFrame::new(moisture_id, &(freq_hz as u32).to_be_bytes()).unwrap();
|
||||||
match can.receive() {
|
CAN_TX_CH.send(moisture).await;
|
||||||
Ok(frame) => match frame.id() {
|
|
||||||
Id::Standard(s_frame) => {
|
while let Ok(_frame) = CAN_RX_CH.try_receive() {
|
||||||
let mut msg: heapless::String<128> = heapless::String::new();
|
|
||||||
let _ = write!(
|
|
||||||
&mut msg,
|
|
||||||
"Received from canbus: {:?} ident is {:?} \r\n",
|
|
||||||
s_frame.as_raw(),
|
|
||||||
identify_id.as_raw()
|
|
||||||
);
|
|
||||||
log(msg);
|
|
||||||
if s_frame.as_raw() == identify_id.as_raw() {
|
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
Timer::after_millis(250).await;
|
Timer::after_millis(250).await;
|
||||||
info.toggle();
|
info.toggle();
|
||||||
@@ -413,15 +461,6 @@ async fn worker(
|
|||||||
info.set_low();
|
info.set_low();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Id::Extended(_) => {}
|
|
||||||
},
|
|
||||||
|
|
||||||
Err(err) => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log(message: heapless::String<128>) {
|
fn log(message: heapless::String<128>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user