From ff63e6343d4eca53cbeab7052d5187371279b6b3 Mon Sep 17 00:00:00 2001 From: hodasemi Date: Sun, 10 Mar 2024 18:56:40 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 ++ Cargo.toml | 13 +++++ build.rs | 125 +++++++++++++++++++++++++++++++++++++++++++ renovate.json | 15 ++++++ serial.conf | 5 ++ src/lib.rs | 108 +++++++++++++++++++++++++++++++++++++ src/serial_config.rs | 19 +++++++ 7 files changed, 288 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 renovate.json create mode 100644 serial.conf create mode 100644 src/lib.rs create mode 100644 src/serial_config.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fba7613 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target + +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bccffb0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "embassy_serial" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +embassy-rp = { version = "0.1.0", features = ["unstable-pac", "time-driver", "critical-section-impl"] } +embassy-usb = { version = "0.1.0" } + +numtoa = "0.2.4" +futures = { version = "0.3.30", defaul-features = false, features = [] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..bb13f8c --- /dev/null +++ b/build.rs @@ -0,0 +1,125 @@ +use std::fmt; + +enum Num<'a> { + Hex(&'a str), + Dec(i32), +} + +impl<'a> fmt::Display for Num<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Num::Hex(h) => write!(f, "{h}"), + Num::Dec(d) => write!(f, "{d}"), + } + } +} + +enum Variable<'a> { + Int(&'a str, Num<'a>), + String(&'a str, &'a str), +} + +fn contains_string(variables: &[Variable]) -> bool { + variables.iter().any(|variable| match variable { + Variable::Int(_, _) => false, + Variable::String(_, _) => true, + }) +} + +fn create_config_struct(content: &str, path: &str, struct_name: &str) { + let variables: Vec> = content + .lines() + .map(|line| { + let mut split = line.split(':'); + + let name = split + .next() + .unwrap() + .trim() + .trim_start_matches('"') + .trim_end_matches('"'); + + let value = split.next().unwrap().trim(); + + if value.starts_with('"') { + let trimmed_string = value.trim_start_matches('"').trim_end_matches('"'); + + Variable::String(name, trimmed_string) + } else { + Variable::Int( + name, + if value.starts_with("0x") { + Num::Hex(value) + } else { + Num::Dec(str::parse(value).unwrap()) + }, + ) + } + }) + .collect(); + + let needs_lifetime = contains_string(&variables); + let mut file_string = String::new(); + + file_string += &format!("pub struct {struct_name}"); + + if needs_lifetime { + file_string += "<'a> "; + } + + file_string += "{\n"; + + for variable in variables.iter() { + match variable { + Variable::Int(name, _value) => { + file_string += &format!("\tpub {name}: i32,\n"); + } + Variable::String(name, _value) => { + file_string += &format!("\tpub {name}: &'a str,\n"); + } + } + } + + file_string += "}\n\n"; + + file_string += "impl"; + + if needs_lifetime { + file_string += "<'a> "; + } + + file_string += struct_name; + + if needs_lifetime { + file_string += "<'a> "; + } + + file_string += "{\n"; + file_string += "\tpub const fn new() -> Self {\n"; + file_string += "\t\tSelf {\n"; + + for variable in variables.iter() { + match variable { + Variable::Int(name, value) => { + file_string += &format!("\t\t\t{name}: {value},\n"); + } + Variable::String(name, value) => { + file_string += &format!("\t\t\t{name}: \"{value}\",\n"); + } + } + } + + file_string += "\t\t}\n"; + file_string += "\t}\n"; + file_string += "}\n"; + + std::fs::write(path, file_string).unwrap(); +} + +fn main() { + create_config_struct( + include_str!("serial.conf"), + "src/serial_config.rs", + "SerialConfig", + ); +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..c24cd7a --- /dev/null +++ b/renovate.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ], + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch" + ], + "automerge": true + } + ] +} \ No newline at end of file diff --git a/serial.conf b/serial.conf new file mode 100644 index 0000000..a3a870b --- /dev/null +++ b/serial.conf @@ -0,0 +1,5 @@ +vendor_id: 0x16c0 +product_id: 0x27dd +manufacturer: "MeMyselfAndI" +product: "Pico Serial" +serial_number: "13371337" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..001e82e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,108 @@ +mod serial_config; + +use numtoa::NumToA; + +use core::future::Future; +use futures::never::Never; + +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; + +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::{Builder, Config, UsbDevice}; + +pub use serial_config::SerialConfig; + +static mut DEVICE_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256]; +static mut CONFIG_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256]; +static mut BOS_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256]; +static mut MSOS_DESCRIPTOR_BUFFER: [u8; 256] = [0; 256]; +static mut CONTROL_BUFFER: [u8; 64] = [0; 64]; +static mut STATE: Option> = None; +pub static mut USB: Option>> = None; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +pub struct Serial<'a> { + class: CdcAcmClass<'a, Driver<'a, USB>>, +} + +impl Serial<'static> { + pub async fn new( + usb: USB, + serial_config: SerialConfig<'static>, + ) -> (Serial<'static>, impl Future) { + // Create the driver, from the HAL. + let driver = Driver::new(usb, Irqs); + + // Create embassy-usb Config + let mut config = Config::new( + serial_config.vendor_id as u16, + serial_config.product_id as u16, + ); + config.manufacturer = Some(serial_config.manufacturer); + config.product = Some(serial_config.product); + config.serial_number = Some(serial_config.serial_number); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + unsafe { + STATE = Some(State::new()); + } + + let mut builder = Builder::new( + driver, + config, + unsafe { &mut DEVICE_DESCRIPTOR_BUFFER }, + unsafe { &mut CONFIG_DESCRIPTOR_BUFFER }, + unsafe { &mut BOS_DESCRIPTOR_BUFFER }, + unsafe { &mut MSOS_DESCRIPTOR_BUFFER }, + unsafe { &mut CONTROL_BUFFER }, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, unsafe { STATE.as_mut().unwrap() }, 64); + + // Build the builder. + unsafe { + USB = Some(builder.build()); + }; + + (Self { class }, Self::run_usb()) + } + + fn run_usb() -> impl Future { + unsafe { + async { + loop { + USB.as_mut().unwrap().run().await + } + } + } + } + + pub async fn send_msg(&mut self, s: &str) { + self.class.write_packet(s.as_bytes()).await.unwrap_or(()); + } + + pub async fn send_number(&mut self, i: T, base: I) + where + T: NumToA, + { + let mut buf = [0; 256]; + + let s = i.numtoa_str(base, &mut buf); + + self.send_msg(s).await; + } +} diff --git a/src/serial_config.rs b/src/serial_config.rs new file mode 100644 index 0000000..7f7b34b --- /dev/null +++ b/src/serial_config.rs @@ -0,0 +1,19 @@ +pub struct SerialConfig<'a> { + pub vendor_id: i32, + pub product_id: i32, + pub manufacturer: &'a str, + pub product: &'a str, + pub serial_number: &'a str, +} + +impl<'a> SerialConfig<'a> { + pub const fn new() -> Self { + Self { + vendor_id: 0x16c0, + product_id: 0x27dd, + manufacturer: "MeMyselfAndI", + product: "Pico Serial", + serial_number: "13371337", + } + } +}