rotor-control-stm32/src/usb.rs

237 lines
8.6 KiB
Rust
Raw Permalink Normal View History

2022-08-20 19:55:14 +02:00
use defmt::Format;
use embassy_stm32::interrupt;
2022-08-20 18:27:10 +02:00
use embassy_stm32::peripherals;
use embassy_stm32::usb::Driver;
use embassy_usb::Builder;
use embassy_usb_serial::{CdcAcmClass, State};
use embassy_util::blocking_mutex::raw::ThreadModeRawMutex;
2022-08-21 22:19:34 +02:00
use embassy_util::channel::mpmc::{Receiver, Sender};
use embassy_util::{select, Either};
2022-08-24 20:08:51 +02:00
use futures_util::future::join;
2022-08-24 17:45:38 +02:00
2022-08-20 19:55:14 +02:00
use heapless::String;
2022-08-24 17:45:38 +02:00
use ufmt::uwrite;
2022-08-20 18:27:10 +02:00
use crate::AzElPair;
2022-08-20 18:27:10 +02:00
#[embassy_executor::task]
pub async fn usb_task(
usb: peripherals::USB,
dp_pin: peripherals::PA12,
dm_pin: peripherals::PA11,
cmd_sender: Sender<'static, ThreadModeRawMutex, Gs232Cmd, 1>,
2022-08-21 22:19:34 +02:00
pos_receiver: Receiver<'static, ThreadModeRawMutex, AzElPair, 1>,
) {
2022-08-20 18:27:10 +02:00
let irq = interrupt::take!(USB_LP_CAN1_RX0);
let driver = Driver::new(usb, irq, dp_pin, dm_pin);
// Create embassy-usb Config
let config = embassy_usb::Config::new(0xc0de, 0xcafe);
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut device_descriptor = [0; 256];
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 7];
2022-08-28 00:03:06 +02:00
let mut usb_state = State::new();
2022-08-20 18:27:10 +02:00
let mut builder = Builder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
None,
);
// Create classes on the builder.
2022-08-28 00:03:06 +02:00
let mut class = CdcAcmClass::new(&mut builder, &mut usb_state, 64);
2022-08-20 18:27:10 +02:00
// Build the builder.
let mut usb = builder.build();
2022-08-28 00:03:06 +02:00
// Create a future to handle incomming usb packets
2022-08-20 18:27:10 +02:00
let usb_handler_fut = async {
2022-08-28 00:03:06 +02:00
// Initialize the current position in case we get a B or C command,
// before we get the first the update via pos_receiver
2022-08-21 22:19:34 +02:00
let mut current_pos = AzElPair { az: 0, el: 0 };
2022-08-20 18:27:10 +02:00
loop {
2022-08-28 00:03:06 +02:00
// No much used doing anything until we have a usb connection
2022-08-20 18:27:10 +02:00
class.wait_connection().await;
defmt::info!("USB connected");
2022-08-28 00:03:06 +02:00
// Allocate a space for incomming usb data packets
2022-08-20 18:27:10 +02:00
let mut packet = [0; 64];
2022-08-28 00:03:06 +02:00
// Allocate a string to act as buffer to pares the packets linewise
2022-08-20 19:55:14 +02:00
let mut buffer: String<64> = String::new();
2022-08-20 18:27:10 +02:00
loop {
2022-08-21 22:19:34 +02:00
let n = match select(class.read_packet(&mut packet), pos_receiver.recv()).await {
2022-08-28 00:03:06 +02:00
// The read_packet furture returned either usb data or an error.
2022-08-21 22:19:34 +02:00
Either::First(res) => match res {
2022-08-28 00:03:06 +02:00
// In case of an error break the loop and treat it like an usb disconnect
2022-08-21 22:19:34 +02:00
Ok(n) => n,
2022-08-28 00:03:06 +02:00
// In case of an error break the loop and treat it like an usb disconnect
2022-08-21 22:19:34 +02:00
Err(err) => {
defmt::error!("Unable to read packet: {}", err);
break;
}
},
2022-08-28 00:03:06 +02:00
// The pos_receiver future returned a position update from moment task.
// Just update position and restart loop.
2022-08-21 22:19:34 +02:00
Either::Second(pair) => {
current_pos = pair;
continue;
2022-08-20 18:27:10 +02:00
}
};
2022-08-28 00:03:06 +02:00
// Append the data in the packet buffer to the buffer string
2022-08-20 19:55:14 +02:00
for byte in &packet[..n] {
if buffer.len() == 64 {
buffer.clear();
2022-08-20 18:27:10 +02:00
}
2022-08-20 19:55:14 +02:00
buffer.push(*byte as char).unwrap();
2022-08-20 18:27:10 +02:00
}
2022-08-28 00:03:06 +02:00
// Check if the buffer string contains a '\r'
2022-08-20 19:55:14 +02:00
let line_end = match buffer.rfind('\r') {
2022-08-28 00:03:06 +02:00
// Carriage return found, keep the index
2022-08-20 19:55:14 +02:00
Some(n) => n,
2022-08-28 00:03:06 +02:00
// No carriage return, wait for the next package
2022-08-20 19:55:14 +02:00
_ => continue,
};
2022-08-28 00:03:06 +02:00
// The is a non-zero amount of characters before the carriage return
2022-08-20 18:27:10 +02:00
if line_end > 0 {
2022-08-28 00:03:06 +02:00
// Try the parse the slice leading up to linend as a GS323 command
2022-08-20 19:55:14 +02:00
let cmd = parse_command(&buffer.as_str()[..line_end]);
defmt::info!("Command: {}", cmd);
2022-08-28 00:03:06 +02:00
// Reverse some space for a respose to the command
2022-08-20 19:55:14 +02:00
let mut resp: String<16> = String::new();
match cmd {
2022-08-28 00:03:06 +02:00
// Get Azimuth command. Respond with last known azimuth
Gs232Cmd::GetAz => {
2022-08-24 17:45:38 +02:00
uwrite!(&mut resp, "AZ={}\r", current_pos.az).unwrap();
2022-08-20 19:55:14 +02:00
}
2022-08-28 00:03:06 +02:00
// Get Elevation comman. Respond with last known elevation
Gs232Cmd::GetEl => {
2022-08-24 17:45:38 +02:00
uwrite!(&mut resp, "EL={}\r", current_pos.el).unwrap();
2022-08-20 19:55:14 +02:00
}
2022-09-05 19:21:32 +02:00
// Get Azimuth and Elevation. Respond with last known pair
2022-08-28 00:03:06 +02:00
Gs232Cmd::GetAzEl => {
2022-08-24 17:45:38 +02:00
uwrite!(&mut resp, "AZ={} EL={}\r", current_pos.az, current_pos.el)
2022-08-21 22:19:34 +02:00
.unwrap();
2022-08-20 19:55:14 +02:00
}
2022-08-28 00:03:06 +02:00
// Move to command. Send to movement task. Respond with empty line.
Gs232Cmd::MoveTo(_) => {
cmd_sender.send(cmd).await;
2022-08-20 19:55:14 +02:00
resp.push_str("\r").unwrap();
}
2022-08-28 00:03:06 +02:00
// Stop command. Send to movement task. Respond with empty line.
2022-08-20 19:55:14 +02:00
Gs232Cmd::Stop => {
cmd_sender.send(cmd).await;
2022-08-20 19:55:14 +02:00
resp.push_str("\r").unwrap();
}
2022-08-28 00:03:06 +02:00
// Unknown command or parser error. Complain and do nothing.
2022-08-20 19:55:14 +02:00
_ => {
defmt::error!("Uknown command: {}", &buffer.as_str()[..line_end]);
resp.push_str("Unkown command!\r").unwrap();
}
2022-08-20 18:27:10 +02:00
}
2022-09-05 19:21:32 +02:00
2022-08-28 00:03:06 +02:00
// Write the response back via USB
2022-08-20 19:55:14 +02:00
match class.write_packet(resp.as_bytes()).await {
Ok(_) => {}
2022-08-28 00:03:06 +02:00
// Error treat like broken usb connection
2022-08-20 19:55:14 +02:00
Err(err) => {
defmt::error!("Unable to write packet: {}", err);
break;
}
};
2022-08-20 18:27:10 +02:00
}
2022-08-28 00:03:06 +02:00
// Drop the processed line from the buffer
2022-08-20 19:55:14 +02:00
buffer = String::from(&buffer.as_str()[line_end + 1..]);
2022-08-20 18:27:10 +02:00
}
defmt::info!("USB disconnected");
2022-08-28 00:13:22 +02:00
// USB connection is broken, so better stop the rotor.
cmd_sender.send(Gs232Cmd::Stop);
2022-08-20 18:27:10 +02:00
}
};
2022-08-28 00:03:06 +02:00
// Run the ubs and handler future both to completion.
// None of the ever completes, but they will still be polled continously.
2022-08-20 18:27:10 +02:00
join(usb.run(), usb_handler_fut).await;
}
2022-08-28 00:03:06 +02:00
// Enum for the GS232 commands
2022-08-20 19:55:14 +02:00
#[derive(Format, PartialEq)]
pub enum Gs232Cmd {
2022-08-20 18:27:10 +02:00
Unkown,
2022-08-28 00:03:06 +02:00
GetAz,
GetEl,
GetAzEl,
MoveTo(AzElPair),
2022-08-20 18:27:10 +02:00
Stop,
}
2022-08-28 00:03:06 +02:00
// Parse a GS232 commmand from a string slice
2022-08-20 19:55:14 +02:00
fn parse_command(data: &str) -> Gs232Cmd {
match data.chars().nth(0).unwrap() {
2022-08-20 18:27:10 +02:00
'B' => {
2022-08-28 00:03:06 +02:00
// Get Az command. Format 'B\r'
2022-08-20 18:27:10 +02:00
if data.len() == 1 {
2022-08-28 00:03:06 +02:00
Gs232Cmd::GetAz
2022-08-20 18:27:10 +02:00
} else {
Gs232Cmd::Unkown
}
}
'C' => {
2022-08-28 00:03:06 +02:00
// Get AZ and EL. Format 'C2\r'
2022-08-20 19:55:14 +02:00
if data.len() == 2 && data.chars().nth(1).unwrap() as char == '2' {
2022-08-28 00:03:06 +02:00
Gs232Cmd::GetAzEl
// Get EL only 'C\r'
2022-08-20 18:27:10 +02:00
} else if data.len() == 1 {
2022-08-28 00:03:06 +02:00
Gs232Cmd::GetEl
2022-08-20 18:27:10 +02:00
} else {
Gs232Cmd::Unkown
}
}
'W' => {
2022-08-28 00:03:06 +02:00
// Set position 'Waaa eee\r' with azimuth aaa and elevation eee.
// Fortunately rotcld will prepend zeros, so there will always be 3 digits per number.
2022-08-20 18:27:10 +02:00
if data.len() == 8 {
2022-08-20 19:55:14 +02:00
if let Ok(az) = data[1..4].parse::<u16>() {
if let Ok(el) = data[5..].parse::<u16>() {
Gs232Cmd::MoveTo(AzElPair { az, el })
2022-08-20 19:55:14 +02:00
} else {
Gs232Cmd::Unkown
}
} else {
Gs232Cmd::Unkown
}
2022-08-20 18:27:10 +02:00
} else {
Gs232Cmd::Unkown
}
}
'S' => {
2022-08-28 00:03:06 +02:00
// Stop command. Format 'S\r'
2022-08-20 18:27:10 +02:00
if data.len() == 1 {
Gs232Cmd::Stop
} else {
Gs232Cmd::Unkown
}
}
_ => Gs232Cmd::Unkown,
}
}