add USB CDC support, and implement self-check diagnostic
This commit is contained in:
@@ -10,5 +10,7 @@ rustflags = [
|
||||
# runner = "gdb -q -x openocd.gdb"
|
||||
# runner = "wlink -v flash"
|
||||
|
||||
runner = "wlink -v flash --enable-sdi-print --watch-serial --erase"
|
||||
|
||||
runner = "wchisp flash"
|
||||
#runner = "wlink -v flash --enable-sdi-print --watch-serial --erase"
|
||||
# runner = "wlink -v flash"
|
||||
|
||||
+10
-4
@@ -14,14 +14,20 @@ ch32-hal = { git = "https://github.com/ju6ge/ch32-hal", branch = "feature/i2c-sl
|
||||
], default-features = false }
|
||||
|
||||
embassy-executor = { version = "0.7.0", features = [
|
||||
"arch-riscv32",
|
||||
# arch-riscv32's WFI-based sleep races with the USB interrupt wake signal,
|
||||
# so tasks awaiting USB (e.g. wait_connection/write_packet) can hang
|
||||
# forever even though the device enumerates fine. arch-spin busy-polls
|
||||
# instead of sleeping, sidestepping the race. See CAN_Sensor/Cargo.toml
|
||||
# for the same fix on the same ch32-hal/embassy-executor combo.
|
||||
"arch-spin",
|
||||
"executor-thread",
|
||||
] }
|
||||
|
||||
embassy-time = { version = "0.4.0" }
|
||||
embassy-usb = { version = "0.3.0" }
|
||||
embassy-time = { version = "0.5.0", features = ["generic-queue-8"] }
|
||||
embassy-usb = { version = "0.5.1" }
|
||||
embassy-futures = { version = "0.1.0" }
|
||||
embassy-sync = { version = "0.7.2" }
|
||||
static_cell = "2.1.1"
|
||||
|
||||
# This is okay because we should automatically use whatever ch32-hal uses
|
||||
qingke-rt = "*"
|
||||
@@ -30,7 +36,7 @@ qingke = "*"
|
||||
panic-halt = "1.0"
|
||||
|
||||
embedded-hal = "1.0.0"
|
||||
heapless = "0.8.0"
|
||||
heapless = { version = "0.8.0", features = ["portable-atomic-critical-section"] }
|
||||
micromath = { version = "2.1.0", features = ["num-traits"] }
|
||||
ch32-metapac = { version = "0.0.6", features = ["ch32v203c8t6"] }
|
||||
ina219 = "0.2.0"
|
||||
|
||||
+159
-29
@@ -4,24 +4,53 @@
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
|
||||
mod measurement;
|
||||
mod selfcheck;
|
||||
|
||||
use ch32_hal::i2c::mode::OperatingMode;
|
||||
use ch32_hal::mode::Mode;
|
||||
use ch32_hal::println;
|
||||
use ch32_hal::time::Hertz;
|
||||
use ch32_hal::peripherals::USBD;
|
||||
use core::fmt::Write as _;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
|
||||
use embassy_sync::channel::Channel;
|
||||
use embassy_sync::lazy_lock::LazyLock;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embassy_sync::rwlock::RwLock;
|
||||
//use embassy_time::{Duration, Timer};
|
||||
use embassy_time::Timer;
|
||||
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
|
||||
use embassy_usb::{Builder, UsbDevice};
|
||||
use hal::bind_interrupts;
|
||||
use hal::i2c::{Config, I2c, SlaveAddress, SlaveConfig};
|
||||
use hal::gpio::{Flex, Input, Level, Output, Pull};
|
||||
use hal::i2c::I2c;
|
||||
use hal::spi::Spi;
|
||||
use hal::usbd::Driver;
|
||||
use {ch32_hal as hal, panic_halt as _};
|
||||
|
||||
macro_rules! mk_static {
|
||||
($t:ty,$val:expr) => {{
|
||||
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||||
#[deny(unused_attributes)]
|
||||
let x = STATIC_CELL.uninit().write(($val));
|
||||
x
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) static LOG_CH: Channel<CriticalSectionRawMutex, heapless::String<128>, 8> = Channel::new();
|
||||
|
||||
/// Formats like `std::println!`, but sends the line over USB CDC instead of stdout.
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
($($arg:tt)*) => {{
|
||||
let mut s: heapless::String<128> = heapless::String::new();
|
||||
let _ = core::write!(&mut s, $($arg)*);
|
||||
let _ = $crate::LOG_CH.try_send(s);
|
||||
}};
|
||||
}
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
I2C1_EV => ch32_hal::i2c::EventInterruptHandler<ch32_hal::peripherals::I2C1>;
|
||||
I2C1_ER => ch32_hal::i2c::ErrorInterruptHandler<ch32_hal::peripherals::I2C1>;
|
||||
USB_LP_CAN1_RX0 => hal::usbd::InterruptHandler<hal::peripherals::USBD>;
|
||||
});
|
||||
|
||||
const MEASUREMENT_SAMPLES_CAPACITY: usize = 1024;
|
||||
@@ -38,36 +67,137 @@ async fn measurement_task<'m, M: Mode, O: OperatingMode>(
|
||||
}
|
||||
|
||||
#[embassy_executor::main(entry = "qingke_rt::entry")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let p = hal::init(Default::default());
|
||||
async fn main(spawner: Spawner) {
|
||||
let p = hal::init(hal::Config {
|
||||
rcc: hal::rcc::Config::SYSCLK_FREQ_144MHZ_HSI,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let config = Config::default();
|
||||
let mut i2c_slave = I2c::new_blocking(p.I2C1, p.PB6, p.PB7, Hertz::khz(100), config)
|
||||
.into_slave(SlaveConfig {
|
||||
address: SlaveAddress::SevenBit(0x55),
|
||||
general_call: false,
|
||||
});
|
||||
// D7 startup blink so we can visually confirm the board booted before it
|
||||
// goes quiet waiting on the I2C bus.
|
||||
let mut led = Output::new(p.PA8, Level::Low, Default::default());
|
||||
for _ in 0..5 {
|
||||
led.toggle();
|
||||
Timer::after_millis(200).await;
|
||||
}
|
||||
led.set_low();
|
||||
|
||||
// USB CDC-ACM console: lets us `println!` self-test output over the USB
|
||||
// cable instead of needing a WCH-Link SWD probe attached.
|
||||
let driver = Driver::new(p.USBD, Irqs, p.PA12, p.PA11);
|
||||
|
||||
let mut usb_config = embassy_usb::Config::new(0xC0DE, 0xCAFE);
|
||||
usb_config.manufacturer = Some("ch32-bms");
|
||||
usb_config.product = Some("BMS debug console");
|
||||
usb_config.serial_number = Some("12345678");
|
||||
usb_config.max_power = 100;
|
||||
usb_config.max_packet_size_0 = 64;
|
||||
// Windows compatibility requires these; CDC-ACM
|
||||
usb_config.device_class = 0x02;
|
||||
usb_config.device_sub_class = 0x02;
|
||||
usb_config.device_protocol = 0x00;
|
||||
usb_config.composite_with_iads = false;
|
||||
|
||||
let mut builder = Builder::new(
|
||||
driver,
|
||||
usb_config,
|
||||
mk_static!([u8; 256], [0; 256]),
|
||||
mk_static!([u8; 256], [0; 256]),
|
||||
&mut [], // no msos descriptors
|
||||
mk_static!([u8; 64], [0; 64]),
|
||||
);
|
||||
let class = mk_static!(
|
||||
CdcAcmClass<'static, Driver<'static, USBD>>,
|
||||
CdcAcmClass::new(&mut builder, mk_static!(State, State::new()), 64)
|
||||
);
|
||||
let usb = mk_static!(UsbDevice<Driver<USBD>>, builder.build());
|
||||
|
||||
spawner.spawn(usb_task(usb)).unwrap();
|
||||
spawner.spawn(usb_writer(class)).unwrap();
|
||||
|
||||
println!("BMS booted, D7 blink done, USB console up");
|
||||
|
||||
// Power-on self-check: exercises the other LEDs, the spare input pin,
|
||||
// the INA219 current sensor, and the SPI flash, reporting over USB.
|
||||
let mut led_d3 = Output::new(p.PB12, Level::Low, Default::default());
|
||||
let mut led_d4 = Output::new(p.PB13, Level::Low, Default::default());
|
||||
let mut led_d5 = Output::new(p.PB14, Level::Low, Default::default());
|
||||
let mut led_d6 = Output::new(p.PB15, Level::Low, Default::default());
|
||||
let input_pb2 = Input::new(p.PB2, Pull::None);
|
||||
let mut spi = Spi::new_blocking::<0>(p.SPI1, p.PA5, p.PA7, p.PA6, Default::default());
|
||||
let mut flash_cs = Output::new(p.PA4, Level::High, Default::default());
|
||||
let mut i2c_scl = Flex::new(p.PB10);
|
||||
let mut i2c_sda = Flex::new(p.PB9);
|
||||
|
||||
loop {
|
||||
match i2c_slave.listen_blocking() {
|
||||
Ok(command) => {
|
||||
match command {
|
||||
ch32_hal::i2c::SlaveCommand::GeneralCall => { /* will not be triggered because we disabled it */
|
||||
}
|
||||
ch32_hal::i2c::SlaveCommand::ReadCommand => {
|
||||
// send empty response
|
||||
let _ = i2c_slave.blocking_write_timeout(&[0x05, 0x01]);
|
||||
}
|
||||
ch32_hal::i2c::SlaveCommand::WriteCommand => {
|
||||
let mut buf: [u8; 3] = [0x0, 0x0, 0x0];
|
||||
let _ = i2c_slave.blocking_read_timeout(&mut buf);
|
||||
println!("received byte 0x{buf:x?}");
|
||||
}
|
||||
selfcheck::run(
|
||||
&mut led,
|
||||
&mut led_d3,
|
||||
&mut led_d4,
|
||||
&mut led_d5,
|
||||
&mut led_d6,
|
||||
&input_pb2,
|
||||
&mut spi,
|
||||
&mut flash_cs,
|
||||
&mut i2c_scl,
|
||||
&mut i2c_sda,
|
||||
)
|
||||
.await;
|
||||
Timer::after_secs(5).await;
|
||||
}
|
||||
|
||||
// I2C slave listen loop disabled for now: it busy-waits with no `.await`,
|
||||
// which would starve the USB tasks above on this single-threaded executor.
|
||||
// let config = hal::i2c::Config::default();
|
||||
// let mut i2c_slave = I2c::new_blocking(p.I2C1, p.PB6, p.PB7, hal::time::Hertz::khz(100), config)
|
||||
// .into_slave(hal::i2c::SlaveConfig {
|
||||
// address: hal::i2c::SlaveAddress::SevenBit(0x55),
|
||||
// general_call: false,
|
||||
// });
|
||||
//
|
||||
// loop {
|
||||
// match i2c_slave.listen_blocking() {
|
||||
// Ok(command) => {
|
||||
// match command {
|
||||
// ch32_hal::i2c::SlaveCommand::GeneralCall => { /* will not be triggered because we disabled it */
|
||||
// }
|
||||
// ch32_hal::i2c::SlaveCommand::ReadCommand => {
|
||||
// // send empty response
|
||||
// let _ = i2c_slave.blocking_write_timeout(&[0x05, 0x01]);
|
||||
// }
|
||||
// ch32_hal::i2c::SlaveCommand::WriteCommand => {
|
||||
// let mut buf: [u8; 3] = [0x0, 0x0, 0x0];
|
||||
// let _ = i2c_slave.blocking_read_timeout(&mut buf);
|
||||
// println!("received byte 0x{buf:x?}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Err(err) => {
|
||||
// println!("{:?}", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn usb_task(usb: &'static mut UsbDevice<'static, Driver<'static, USBD>>) {
|
||||
usb.run().await;
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn usb_writer(class: &'static mut CdcAcmClass<'static, Driver<'static, USBD>>) {
|
||||
loop {
|
||||
class.wait_connection().await;
|
||||
'connected: loop {
|
||||
let msg = LOG_CH.receive().await;
|
||||
// write_packet() fails outright for any single write over the
|
||||
// endpoint's max packet size (64 bytes here), so chunk it.
|
||||
for chunk in msg.as_bytes().chunks(64) {
|
||||
if class.write_packet(chunk).await.is_err() {
|
||||
// Disconnected or endpoint disabled; wait for reconnection.
|
||||
break 'connected;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("{:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
use embassy_usb::msos::ConfigurationSubsetHeader;
|
||||
use lib_bms_protocol::BatteryState;
|
||||
|
||||
use ina219::{address::Address, calibration::{IntCalibration, MicroAmpere, MicroWatt, UnCalibrated}, configuration::Configuration, errors::{ConfigurationReadError, InitializationError}, measurements::{CurrentRegister, Measurements, PowerRegister}, SyncIna219};
|
||||
|
||||
Reference in New Issue
Block a user