Skip to content

Commit

Permalink
Merge pull request #19 from decaday/feat/usb
Browse files Browse the repository at this point in the history
USB Support
  • Loading branch information
decaday authored Dec 10, 2024
2 parents 2c3577b + 0f10d87 commit ecfec8f
Show file tree
Hide file tree
Showing 22 changed files with 1,499 additions and 24 deletions.
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ embassy-executor = { version = "0.6", features = [
"arch-cortex-m",
] }
embassy-embedded-hal = { version = "0.2.0", default-features = false }

embassy-usb-driver = {version = "0.1.0" }

[build-dependencies]
# py32-metapac = { path = "../py32-data/build/py32-metapac", default-features = false, features = [
Expand All @@ -74,17 +74,21 @@ default = ["rt", "memory-x", "defmt", "embassy", "time", "exti"]

rt = ["py32-metapac/rt"]

defmt = ["dep:defmt", "dep:defmt-rtt"]
defmt = ["dep:defmt", "dep:defmt-rtt", "embassy-usb-driver/defmt"]

memory-x = ["py32-metapac/memory-x"]

embassy = ["dep:embassy-sync", "dep:embassy-futures", "dep:embassy-time-driver"]


time = ["dep:embassy-time", "embassy-embedded-hal/time"]

exti = []

# PY32F07x: the IN and OUT buffers of the same endpoint being shared
# When this feature is enabled, the In and Out of an endpoint will not be used at the same time, except for ep0.
# PY32F403: IN and OUT do not share FIFO, this feature is invalid
allow-ep-shared-fifo = []

py32f030k28 = ["py32-metapac/py32f030k28"]
py32f030f16 = ["py32-metapac/py32f030f16"]
py32f072c1b = ["py32-metapac/py32f072c1b"]
Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ Supported chip flags: `py32f030f16`, `py32f030k28`, `py32f072c1b`, More is comin

Note: Currently the program behavior has nothing to do with chip packaging.



others should work if you are careful as most peripherals are similar enough.In fact, the IPs of peripherals in different PY32 series may be consistent. Moreover, some series use the same die, so it might not require much work.
Others should work if you are careful as most peripherals are similar enough.In fact, the IPs of peripherals in different PY32 series may be consistent. Moreover, some series use the same die, so it might not require much work.

For a full list of chip capabilities and peripherals, check the [py32-data](https://github.com/py32-rs/py32-data) repository.

Expand All @@ -57,14 +55,13 @@ For a full list of chip capabilities and peripherals, check the [py32-data](http
| ADC | | ✅+ || |
| RTC | | | | |
| Timer(PWM) | ||| |
| USB/OTG | N/A | N/A | | |
| USB | N/A | N/A | ✅+ | |

- ✅ : Expected to work
- ❌ : Not implemented
- ❓ : Not tested
- `+` : marks the async driver
- TODO: I haven't got a dev board yet, help-wanted
- N/A: Not available
- ✅ : Implemented
- Blank : Not implemented
- ❓ : Requires demo verification
- `+` : Async support
- N/A : Not available

## TODOs

Expand Down Expand Up @@ -92,6 +89,8 @@ Embassy requires that any TIM used as a time-driver has at least two channels, s

`time-driver-systick`: Although we do not recommend using it and there are some shortcomings, it does work. For details, please see [systick-demo](examples/systick-time-driver-f030/README.md)

For PY32F07x, F040, you can use TIM15, TIM3 or TIM1.

## Minimum supported Rust version(MSRV)

This project is developed with a recent **nightly** version of Rust compiler. And is expected to work with beta versions of Rust.
Expand All @@ -114,3 +113,7 @@ All kinds of contributions are welcome.
## License

This project is licensed under the MIT or Apache-2.0 license, at your option.



Some peripheral driver code has been modified from [embassy-stm32]([embassy/embassy-stm32 at main · embassy-rs/embassy](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32)). Big thanks to this project and its awesome contributors!
4 changes: 4 additions & 0 deletions examples/py32f030/.cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# probe-rs chip list | grep -i PY32
runner = 'probe-rs run --chip PY32F030x8'

# rustflags = [
# "-C", "linker=flip-link",
# ]

[build]
target = "thumbv6m-none-eabi"

Expand Down
2 changes: 0 additions & 2 deletions examples/py32f030/src/bin/button_exti.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ use py32_hal::exti::ExtiInput;
use py32_hal::gpio::Pull;
use {defmt_rtt as _, panic_probe as _};

use py32_hal::interrupt;

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = py32_hal::init(Default::default());
Expand Down
4 changes: 3 additions & 1 deletion examples/py32f030/src/bin/raw_rtt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
use cortex_m_rt::entry;
use defmt::*;
use defmt_rtt as _;
use hal::pac;
use panic_halt as _;

use py32_hal as hal;
#[allow(unused_imports)]
use hal::pac;

#[entry]
fn main() -> ! {
Expand Down
1 change: 0 additions & 1 deletion examples/py32f030/src/bin/usart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use embassy_executor::Spawner;
use defmt::*;
use py32_hal::usart::{Config, Uart};
use py32_hal::{bind_interrupts, peripherals, usart};
use {defmt_rtt as _, panic_probe as _};

#[embassy_executor::main]
Expand Down
1 change: 0 additions & 1 deletion examples/py32f030/src/bin/usart_buffered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use embassy_executor::Spawner;
use py32_hal::usart::{BufferedUart, Config};
use py32_hal::{bind_interrupts, peripherals, usart};
use py32_hal::time::Hertz;
use py32_hal::rcc::{Pll, PllSource, Sysclk};
use embedded_io_async::Read;
use embedded_io_async::Write;
use {defmt_rtt as _, panic_probe as _};
Expand Down
12 changes: 7 additions & 5 deletions examples/py32f072/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
[target.thumbv6m-none-eabi]
# probe-rs chip list | grep -i PY32
runner = 'probe-rs run --chip PY32F072xb'
runner = "probe-rs run --chip PY32F072xB"

# rustflags = [
# "-C", "linker=flip-link",
# ]

[build]
target = "thumbv6m-none-eabi"
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+

[env]
DEFMT_LOG = "trace"

# rustflags = ["-C", "link-arg=-Tlink.x"]
DEFMT_LOG = "info"
9 changes: 9 additions & 0 deletions examples/py32f072/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ py32-hal = { path = "../../", features = [ "time-driver-tim15", "py32f072c1b"]}

defmt = "0.3"
defmt-rtt = "0.4"
embassy-futures = "0.1.1"
embassy-usb = { version = "0.3.0", features = [ "defmt"]}
usbd-hid = "0.8.2"

# embassy-usb-logger = "0.2.0"
log = "0.4"

portable-atomic = { version = "1.5", features = ["critical-section"] }
static_cell = "2.1"

# cargo build/run
[profile.dev]
Expand Down
4 changes: 3 additions & 1 deletion examples/py32f072/src/bin/raw_rtt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
use cortex_m_rt::entry;
use defmt::*;
use defmt_rtt as _;
use hal::pac;
use panic_halt as _;

use py32_hal as hal;
#[allow(unused_imports)]
use hal::pac;

#[entry]
fn main() -> ! {
Expand Down
212 changes: 212 additions & 0 deletions examples/py32f072/src/bin/usb_hid_keyboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#![no_std]
#![no_main]
#![feature(impl_trait_in_assoc_type)]

use core::sync::atomic::{AtomicBool, Ordering};

use defmt::*;
use {defmt_rtt as _, panic_probe as _};
use embassy_executor::Spawner;
use embassy_futures::join::join;
use py32_hal::bind_interrupts;
use py32_hal::gpio::Pull;
use py32_hal::exti::ExtiInput;
use py32_hal::time::Hertz;
use py32_hal::rcc::{Pll, PllSource, Sysclk, PllMul};
use py32_hal::usb::{Driver, InterruptHandler};


use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State};
use embassy_usb::control::OutResponse;
use embassy_usb::{Builder, Handler};
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};


bind_interrupts!(struct Irqs {
USB => InterruptHandler<py32_hal::peripherals::USB>;
});

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut cfg: py32_hal::Config = Default::default();

// PY32 USB uses PLL as the clock source and can only run at 48Mhz.
cfg.rcc.hsi = Some(Hertz::mhz(16));
cfg.rcc.pll = Some(Pll {
src: PllSource::HSI,
mul: PllMul::MUL3,
});
cfg.rcc.sys = Sysclk::PLL;
let p = py32_hal::init(cfg);

// Create the driver, from the HAL.

let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);

// Create embassy-usb Config
let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("HID keyboard example");
config.serial_number = Some("12345678");
config.max_power = 100;
config.max_packet_size_0 = 64;

// Required for windows compatibility.
// 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;

// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
// You can also add a Microsoft OS descriptor.
let mut msos_descriptor = [0; 256];
let mut control_buf = [0; 64];

let mut request_handler = MyRequestHandler {};
let mut device_handler = MyDeviceHandler::new();

let mut state = State::new();

let mut builder = Builder::new(
driver,
config,
&mut config_descriptor,
&mut bos_descriptor,
&mut msos_descriptor,
&mut control_buf,
);

builder.handler(&mut device_handler);

// Create classes on the builder.
let config = embassy_usb::class::hid::Config {
report_descriptor: KeyboardReport::desc(),
request_handler: None,
poll_ms: 60,
max_packet_size: 8,
};

let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config);

// Build the builder.
let mut usb = builder.build();

// Run the USB device.
let usb_fut = usb.run();

let (reader, mut writer) = hid.split();

let mut button = ExtiInput::new(p.PB0, p.EXTI0, Pull::Down);

// Do stuff with the class!
let in_fut = async {
loop {
button.wait_for_rising_edge().await;
// signal_pin.wait_for_high().await;
info!("Button pressed!");
// Create a report with the A key pressed. (no shift modifier)
let report = KeyboardReport {
keycodes: [4, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
// Send the report.
match writer.write_serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};

button.wait_for_falling_edge().await;
// signal_pin.wait_for_low().await;
info!("Button released!");
let report = KeyboardReport {
keycodes: [0, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
match writer.write_serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
}
};

let out_fut = async {
reader.run(false, &mut request_handler).await;
};

// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, join(in_fut, out_fut)).await;
}

struct MyRequestHandler {}

impl RequestHandler for MyRequestHandler {
fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}

fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse {
info!("Set report for {:?}: {=[u8]}", id, data);
OutResponse::Accepted
}

fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}

fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
info!("Get idle rate for {:?}", id);
None
}
}

struct MyDeviceHandler {
configured: AtomicBool,
}

impl MyDeviceHandler {
fn new() -> Self {
MyDeviceHandler {
configured: AtomicBool::new(false),
}
}
}

impl Handler for MyDeviceHandler {
fn enabled(&mut self, enabled: bool) {
self.configured.store(false, Ordering::Relaxed);
if enabled {
info!("Device enabled");
} else {
info!("Device disabled");
}
}

fn reset(&mut self) {
self.configured.store(false, Ordering::Relaxed);
info!("Bus reset, the Vbus current limit is 100mA");
}

fn addressed(&mut self, addr: u8) {
self.configured.store(false, Ordering::Relaxed);
info!("USB address set to: {}", addr);
}

fn configured(&mut self, configured: bool) {
self.configured.store(configured, Ordering::Relaxed);
if configured {
info!("Device configured, it may now draw up to the configured current limit from Vbus.")
} else {
info!("Device is no longer configured, the Vbus current limit is 100mA.");
}
}
}
Loading

0 comments on commit ecfec8f

Please sign in to comment.