mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2024-10-31 22:28:49 +00:00
info! -> println!
This commit is contained in:
parent
b975a07698
commit
ca262675e8
38 changed files with 931 additions and 853 deletions
|
@ -23,7 +23,7 @@ const APP: () = {
|
||||||
w.usbdetected().set_bit()
|
w.usbdetected().set_bit()
|
||||||
});
|
});
|
||||||
|
|
||||||
defmt::info!("USBDETECTED interrupt enabled");
|
defmt::println!("USBDETECTED interrupt enabled");
|
||||||
|
|
||||||
// read the whole 32-bit usb supply register
|
// read the whole 32-bit usb supply register
|
||||||
// the `read()` method returns a reader which can then be used to access the register content
|
// the `read()` method returns a reader which can then be used to access the register content
|
||||||
|
@ -31,19 +31,19 @@ const APP: () = {
|
||||||
// (the layout of the USBREGSTATUS register can be found in section 5.3.7.13 of the PS)
|
// (the layout of the USBREGSTATUS register can be found in section 5.3.7.13 of the PS)
|
||||||
let regstatus: u32 = power.usbregstatus.read().bits();
|
let regstatus: u32 = power.usbregstatus.read().bits();
|
||||||
// ^^^^ complete register content
|
// ^^^^ complete register content
|
||||||
defmt::info!("USBREGSTATUS: {:b}", regstatus);
|
defmt::println!("USBREGSTATUS: {:b}", regstatus);
|
||||||
|
|
||||||
// read the 1-bit VBUSDETECT field that is part of the USBREGSTATUS register content
|
// read the 1-bit VBUSDETECT field that is part of the USBREGSTATUS register content
|
||||||
// to show that its contents reflect our usb connection status
|
// to show that its contents reflect our usb connection status
|
||||||
// (the USBDETECTED event that will trigger `on_power_event()` is derived from this information)
|
// (the USBDETECTED event that will trigger `on_power_event()` is derived from this information)
|
||||||
let vbusdetect: bool = power.usbregstatus.read().vbusdetect().bits();
|
let vbusdetect: bool = power.usbregstatus.read().vbusdetect().bits();
|
||||||
// ^^^^^^^^^^ bitfield name
|
// ^^^^^^^^^^ bitfield name
|
||||||
defmt::info!("USBREGSTATUS.VBUSDETECT: {}", vbusdetect);
|
defmt::println!("USBREGSTATUS.VBUSDETECT: {}", vbusdetect);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle]
|
#[idle]
|
||||||
fn main(_cx: main::Context) -> ! {
|
fn main(_cx: main::Context) -> ! {
|
||||||
defmt::info!("idle: going to sleep");
|
defmt::println!("idle: going to sleep");
|
||||||
|
|
||||||
// sleep in the background
|
// sleep in the background
|
||||||
loop {
|
loop {
|
||||||
|
@ -53,7 +53,7 @@ const APP: () = {
|
||||||
|
|
||||||
#[task(binds = POWER_CLOCK)]
|
#[task(binds = POWER_CLOCK)]
|
||||||
fn on_power_event(_cx: on_power_event::Context) {
|
fn on_power_event(_cx: on_power_event::Context) {
|
||||||
defmt::info!("POWER event occurred");
|
defmt::println!("POWER event occurred");
|
||||||
dk::exit()
|
asm::bkpt();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ fn main() -> ! {
|
||||||
// board initialization
|
// board initialization
|
||||||
dk::init().unwrap();
|
dk::init().unwrap();
|
||||||
|
|
||||||
defmt::info!("Hello, world!");
|
defmt::println!("Hello, world!");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
asm::bkpt();
|
asm::bkpt();
|
||||||
|
|
|
@ -21,7 +21,7 @@ const APP: () = {
|
||||||
|
|
||||||
power.intenset.write(|w| w.usbdetected().set_bit());
|
power.intenset.write(|w| w.usbdetected().set_bit());
|
||||||
|
|
||||||
defmt::info!("USBDETECTED interrupt enabled");
|
defmt::println!("USBDETECTED interrupt enabled");
|
||||||
|
|
||||||
init::LateResources {
|
init::LateResources {
|
||||||
power,
|
power,
|
||||||
|
@ -32,9 +32,9 @@ const APP: () = {
|
||||||
#[idle]
|
#[idle]
|
||||||
fn main(_cx: main::Context) -> ! {
|
fn main(_cx: main::Context) -> ! {
|
||||||
loop {
|
loop {
|
||||||
defmt::info!("idle: going to sleep");
|
defmt::println!("idle: going to sleep");
|
||||||
asm::wfi();
|
asm::wfi();
|
||||||
defmt::info!("idle: woke up");
|
defmt::println!("idle: woke up");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ const APP: () = {
|
||||||
|
|
||||||
*counter += 1;
|
*counter += 1;
|
||||||
let n = *counter;
|
let n = *counter;
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"on_power_event: cable connected {} time{}",
|
"on_power_event: cable connected {} time{}",
|
||||||
n,
|
n,
|
||||||
if n != 1 { "s" } else { "" }
|
if n != 1 { "s" } else { "" }
|
||||||
|
|
|
@ -20,7 +20,7 @@ const APP: () = {
|
||||||
|
|
||||||
power.intenset.write(|w| w.usbdetected().set_bit());
|
power.intenset.write(|w| w.usbdetected().set_bit());
|
||||||
|
|
||||||
defmt::info!("USBDETECTED interrupt enabled");
|
defmt::println!("USBDETECTED interrupt enabled");
|
||||||
|
|
||||||
init::LateResources {
|
init::LateResources {
|
||||||
power, // <- resource initialization
|
power, // <- resource initialization
|
||||||
|
@ -30,16 +30,16 @@ const APP: () = {
|
||||||
#[idle]
|
#[idle]
|
||||||
fn main(_cx: main::Context) -> ! {
|
fn main(_cx: main::Context) -> ! {
|
||||||
loop {
|
loop {
|
||||||
defmt::info!("idle: going to sleep");
|
defmt::println!("idle: going to sleep");
|
||||||
asm::wfi();
|
asm::wfi();
|
||||||
defmt::info!("idle: woke up");
|
defmt::println!("idle: woke up");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = POWER_CLOCK, resources = [power])]
|
#[task(binds = POWER_CLOCK, resources = [power])]
|
||||||
// ^^^^^^^ resource access list
|
// ^^^^^^^ resource access list
|
||||||
fn on_power_event(cx: on_power_event::Context) {
|
fn on_power_event(cx: on_power_event::Context) {
|
||||||
defmt::info!("POWER event occurred");
|
defmt::println!("POWER event occurred");
|
||||||
|
|
||||||
// resources available to this task
|
// resources available to this task
|
||||||
let resources = cx.resources;
|
let resources = cx.resources;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
|
|
||||||
#[rtic::app(device = dk, peripherals = true)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
mod app {
|
mod app {
|
||||||
use cortex_m::asm;
|
use cortex_m::asm;
|
||||||
use dk::Peripherals;
|
use dk::Peripherals;
|
||||||
|
@ -23,13 +23,13 @@ mod app {
|
||||||
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
dk::init().unwrap();
|
dk::init().unwrap();
|
||||||
|
|
||||||
defmt::info!("Hello");
|
defmt::println!("Hello");
|
||||||
(MySharedResources {}, MyLocalResources {}, init::Monotonics())
|
(MySharedResources {}, MyLocalResources {}, init::Monotonics())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[idle]
|
#[idle]
|
||||||
fn idle(_cx: idle::Context) -> ! {
|
fn idle(_cx: idle::Context) -> ! {
|
||||||
defmt::info!("world!");
|
defmt::println!("world!");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
asm::bkpt();
|
asm::bkpt();
|
||||||
|
|
|
@ -22,7 +22,7 @@ fn main() -> ! {
|
||||||
fn fib(n: u32) -> u32 {
|
fn fib(n: u32) -> u32 {
|
||||||
// allocate and initialize one kilobyte of stack memory to provoke stack overflow
|
// allocate and initialize one kilobyte of stack memory to provoke stack overflow
|
||||||
let use_stack = [0xAA; 1024];
|
let use_stack = [0xAA; 1024];
|
||||||
defmt::info!("allocating [{}; 1024]; round #{}", use_stack[1023], n);
|
defmt::println!("allocating [{}; 1024]; round #{}", use_stack[1023], n);
|
||||||
|
|
||||||
if n < 2 {
|
if n < 2 {
|
||||||
1
|
1
|
||||||
|
|
|
@ -1,52 +1,67 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[init]
|
#[shared]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
struct MySharedResources {
|
||||||
let board = dk::init().unwrap();
|
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
|
||||||
|
|
||||||
init::LateResources { usbd: board.usbd }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd])]
|
#[init]
|
||||||
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
|
// initialize the USBD peripheral
|
||||||
|
// NOTE this will block if the USB cable is not connected to port J3
|
||||||
|
dk::usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
|
defmt::println!("USBD initialized");
|
||||||
|
|
||||||
|
(MySharedResources {}, MyLocalResources {usbd: board.usbd }, init::Monotonics())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(binds = USBD, local = [usbd])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, event)
|
on_event(usbd, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fn on_event(_usbd: &USBD, event: Event) {
|
fn on_event(_usbd: &USBD, event: Event) {
|
||||||
defmt::info!("USB: {:?}", event);
|
defmt::println("USB: {:?}", event);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::UsbReset => {
|
Event::UsbReset => {
|
||||||
// going from the Default state to the Default state is a no-operation
|
// going from the Default state to the Default state is a no-operation
|
||||||
defmt::info!("returning to the Default state");
|
defmt::println("returning to the Default state");
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UsbEp0DataDone => todo!(),
|
Event::UsbEp0DataDone => todo!(),
|
||||||
|
|
||||||
Event::UsbEp0Setup => {
|
Event::UsbEp0Setup => {
|
||||||
defmt::info!("goal reached; move to the next section");
|
defmt::println("goal reached; move to the next section");
|
||||||
dk::exit()
|
dk::exit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,46 +8,60 @@ use dk::{
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
// initialize the USBD peripheral
|
// initialize the USBD peripheral
|
||||||
// NOTE this will block if the USB cable is not connected to port J3
|
// NOTE this will block if the USB cable is not connected to port J3
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
defmt::info!("USBD initialized");
|
defmt::println!("USBD initialized");
|
||||||
|
|
||||||
init::LateResources { usbd: board.usbd }
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd }, init::Monotonics())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd])]
|
#[task(binds = USBD, local = [usbd])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, event)
|
on_event(usbd, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fn on_event(_usbd: &USBD, event: Event) {
|
fn on_event(_usbd: &USBD, event: Event) {
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::UsbReset => todo!(),
|
Event::UsbReset => todo!(),
|
||||||
|
|
||||||
Event::UsbEp0DataDone => todo!(),
|
Event::UsbEp0DataDone => todo!(),
|
||||||
// leave this at it is for now.
|
// leave this at it is for now.
|
||||||
Event::UsbEp0Setup => {
|
Event::UsbEp0Setup => {
|
||||||
defmt::info!("goal reached; move to the next section");
|
defmt::println!("goal reached; move to the next section");
|
||||||
dk::exit()
|
dk::exit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,102 +1,112 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
use usb::{Descriptor, Request};
|
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
|
||||||
|
use usb::{Descriptor, Request};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
init::LateResources { usbd: board.usbd }
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd }, init::Monotonics())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd])]
|
#[task(binds = USBD, local = [usbd])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, event)
|
on_event(usbd, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
fn on_event(usbd: &USBD, event: Event) {
|
||||||
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
fn on_event(usbd: &USBD, event: Event) {
|
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
match event {
|
||||||
|
Event::UsbReset => {
|
||||||
match event {
|
// nothing to do here at the moment
|
||||||
Event::UsbReset => {
|
}
|
||||||
// nothing to do here at the moment
|
|
||||||
}
|
Event::UsbEp0DataDone => todo!(),
|
||||||
|
|
||||||
Event::UsbEp0DataDone => todo!(),
|
Event::UsbEp0Setup => {
|
||||||
|
// the BMREQUESTTYPE register contains information about data recipient, transfer type and direction
|
||||||
Event::UsbEp0Setup => {
|
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
||||||
// the BMREQUESTTYPE register contains information about data recipient, transfer type and direction
|
// the BREQUEST register stores the type of the current request (e.g. SET_ADDRESS, GET_DESCRIPTOR, ...)
|
||||||
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
let brequest = usbd.brequest.read().brequest().bits();
|
||||||
// the BREQUEST register stores the type of the current request (e.g. SET_ADDRESS, GET_DESCRIPTOR, ...)
|
// wLength denotes the number of bytes to transfer (if any)
|
||||||
let brequest = usbd.brequest.read().brequest().bits();
|
// composed of a high register (WLENGTHH) and a low register (WLENGTHL)
|
||||||
// wLength denotes the number of bytes to transfer (if any)
|
let wlength = (u16::from(usbd.wlengthh.read().wlengthh().bits()) << 8)
|
||||||
// composed of a high register (WLENGTHH) and a low register (WLENGTHL)
|
| u16::from(usbd.wlengthl.read().wlengthl().bits());
|
||||||
let wlength = (u16::from(usbd.wlengthh.read().wlengthh().bits()) << 8)
|
// wIndex is a generic index field whose meaning depends on the request type
|
||||||
| u16::from(usbd.wlengthl.read().wlengthl().bits());
|
// composed of a high register (WINDEXH) and a low register (WINDEXL)
|
||||||
// wIndex is a generic index field whose meaning depends on the request type
|
let windex = (u16::from(usbd.windexh.read().windexh().bits()) << 8)
|
||||||
// composed of a high register (WINDEXH) and a low register (WINDEXL)
|
| u16::from(usbd.windexl.read().windexl().bits());
|
||||||
let windex = (u16::from(usbd.windexh.read().windexh().bits()) << 8)
|
// wValue is a generic paremeter field meaning depends on the request type (e.g. contains the device
|
||||||
| u16::from(usbd.windexl.read().windexl().bits());
|
// address in SET_ADRESS requests)
|
||||||
// wValue is a generic paremeter field meaning depends on the request type (e.g. contains the device
|
// composed of a high register (WVALUEH) and a low register (WVALUEL)
|
||||||
// address in SET_ADRESS requests)
|
let wvalue = (u16::from(usbd.wvalueh.read().wvalueh().bits()) << 8)
|
||||||
// composed of a high register (WVALUEH) and a low register (WVALUEL)
|
| u16::from(usbd.wvaluel.read().wvaluel().bits());
|
||||||
let wvalue = (u16::from(usbd.wvalueh.read().wvalueh().bits()) << 8)
|
|
||||||
| u16::from(usbd.wvaluel.read().wvaluel().bits());
|
// NOTE the `dk` crate contains helper functions for the above operations
|
||||||
|
// let bmrequesttype = usbd::bmrequesttype(usbd);
|
||||||
// NOTE the `dk` crate contains helper functions for the above operations
|
// let brequest = usbd::brequest(usbd);
|
||||||
// let bmrequesttype = usbd::bmrequesttype(usbd);
|
// let wlength = usbd::wlength(usbd);
|
||||||
// let brequest = usbd::brequest(usbd);
|
// let windex = usbd::windex(usbd);
|
||||||
// let wlength = usbd::wlength(usbd);
|
// let wvalue = usbd::wvalue(usbd);
|
||||||
// let windex = usbd::windex(usbd);
|
|
||||||
// let wvalue = usbd::wvalue(usbd);
|
defmt::println!(
|
||||||
|
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
||||||
defmt::info!(
|
bmrequesttype,
|
||||||
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
brequest,
|
||||||
bmrequesttype,
|
wlength,
|
||||||
brequest,
|
windex,
|
||||||
wlength,
|
wvalue
|
||||||
windex,
|
);
|
||||||
wvalue
|
|
||||||
);
|
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
||||||
|
.expect("Error parsing request");
|
||||||
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
match request {
|
||||||
.expect("Error parsing request");
|
Request::GetDescriptor { descriptor, length }
|
||||||
match request {
|
if descriptor == Descriptor::Device =>
|
||||||
Request::GetDescriptor { descriptor, length }
|
{
|
||||||
if descriptor == Descriptor::Device =>
|
defmt::println!("GET_DESCRIPTOR Device [length={}]", length);
|
||||||
{
|
|
||||||
defmt::info!("GET_DESCRIPTOR Device [length={}]", length);
|
defmt::println!("Goal reached; move to the next section");
|
||||||
|
dk::exit()
|
||||||
defmt::info!("Goal reached; move to the next section");
|
}
|
||||||
dk::exit()
|
Request::SetAddress { .. } => {
|
||||||
|
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
||||||
|
// need to catch it here. We'll properly handle this request later
|
||||||
|
// but for now it's OK to do nothing.
|
||||||
|
}
|
||||||
|
_ => unreachable!(), // we don't handle any other Requests
|
||||||
}
|
}
|
||||||
Request::SetAddress { .. } => {
|
|
||||||
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
|
||||||
// need to catch it here. We'll properly handle this request later
|
|
||||||
// but for now it's OK to do nothing.
|
|
||||||
}
|
|
||||||
_ => unreachable!(), // we don't handle any other Requests
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,98 +1,108 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
|
|
||||||
use usb::{Descriptor, Request};
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
|
mod app {
|
||||||
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
use usb::{Descriptor, Request};
|
||||||
const APP: () = {
|
|
||||||
struct Resources {
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
init::LateResources { usbd: board.usbd }
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd }, init::Monotonics())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd])]
|
#[task(binds = USBD, local = [usbd])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, event)
|
on_event(usbd, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fn on_event(_usbd: &USBD, event: Event) {
|
fn on_event(_usbd: &USBD, event: Event) {
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::UsbReset => {
|
Event::UsbReset => {
|
||||||
// nothing to do here at the moment
|
// nothing to do here at the moment
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UsbEp0DataDone => todo!(),
|
Event::UsbEp0DataDone => todo!(),
|
||||||
|
|
||||||
Event::UsbEp0Setup => {
|
Event::UsbEp0Setup => {
|
||||||
// TODO read USBD registers
|
// TODO read USBD registers
|
||||||
|
|
||||||
// the BMREQUESTTYPE register contains information about data recipient, transfer type and direction
|
// the BMREQUESTTYPE register contains information about data recipient, transfer type and direction
|
||||||
let bmrequesttype: u8 = 0;
|
let bmrequesttype: u8 = 0;
|
||||||
// the BREQUEST register stores the type of the current request (e.g. SET_ADDRESS, GET_DESCRIPTOR, ...)
|
// the BREQUEST register stores the type of the current request (e.g. SET_ADDRESS, GET_DESCRIPTOR, ...)
|
||||||
let brequest: u8 = 0;
|
let brequest: u8 = 0;
|
||||||
// wLength denotes the number of bytes to transfer (if any)
|
// wLength denotes the number of bytes to transfer (if any)
|
||||||
// composed of a high register (WLENGTHH) and a low register (WLENGTHL)
|
// composed of a high register (WLENGTHH) and a low register (WLENGTHL)
|
||||||
let wlength: u16 = 0;
|
let wlength: u16 = 0;
|
||||||
// wIndex is a generic index field whose meaning depends on the request type
|
// wIndex is a generic index field whose meaning depends on the request type
|
||||||
// composed of a high register (WINDEXH) and a low register (WINDEXL)
|
// composed of a high register (WINDEXH) and a low register (WINDEXL)
|
||||||
let windex: u16 = 0;
|
let windex: u16 = 0;
|
||||||
// wValue is a generic paremeter field meaning depends on the request type (e.g. contains the device
|
// wValue is a generic paremeter field meaning depends on the request type (e.g. contains the device
|
||||||
// address in SET_ADRESS requests)
|
// address in SET_ADRESS requests)
|
||||||
// composed of a high register (WVALUEH) and a low register (WVALUEL)
|
// composed of a high register (WVALUEH) and a low register (WVALUEL)
|
||||||
let wvalue: u16 = 0;
|
let wvalue: u16 = 0;
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
||||||
bmrequesttype,
|
bmrequesttype,
|
||||||
brequest,
|
brequest,
|
||||||
wlength,
|
wlength,
|
||||||
windex,
|
windex,
|
||||||
wvalue
|
wvalue
|
||||||
);
|
);
|
||||||
|
|
||||||
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
||||||
.expect("Error parsing request");
|
.expect("Error parsing request");
|
||||||
match request {
|
match request {
|
||||||
Request::GetDescriptor { descriptor, length }
|
Request::GetDescriptor { descriptor, length }
|
||||||
if descriptor == Descriptor::Device =>
|
if descriptor == Descriptor::Device =>
|
||||||
{
|
{
|
||||||
// TODO modify `Request::parse()` in `advanced/common/usb/lib.rs`
|
// TODO modify `Request::parse()` in `advanced/common/usb/lib.rs`
|
||||||
// so that this branch is reached
|
// so that this branch is reached
|
||||||
|
|
||||||
defmt::info!("GET_DESCRIPTOR Device [length={}]", length);
|
defmt::println!("GET_DESCRIPTOR Device [length={}]", length);
|
||||||
|
|
||||||
defmt::info!("Goal reached; move to the next section");
|
defmt::println!("Goal reached; move to the next section");
|
||||||
dk::exit()
|
dk::exit()
|
||||||
|
}
|
||||||
|
Request::SetAddress { .. } => {
|
||||||
|
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
||||||
|
// need to catch it here. We'll properly handle this request later
|
||||||
|
// but for now it's OK to do nothing.
|
||||||
|
}
|
||||||
|
_ => unreachable!(), // we don't handle any other Requests
|
||||||
}
|
}
|
||||||
Request::SetAddress { .. } => {
|
|
||||||
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
|
||||||
// need to catch it here. We'll properly handle this request later
|
|
||||||
// but for now it's OK to do nothing.
|
|
||||||
}
|
|
||||||
_ => unreachable!(), // we don't handle any other Requests
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,108 +1,115 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Ep0In, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
use usb::{Descriptor, Request};
|
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Ep0In, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
use usb::{Descriptor, Request};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
ep0in: Ep0In,
|
ep0in: Ep0In,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
init::LateResources {
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd, ep0in: board.ep0in, }, init::Monotonics())
|
||||||
ep0in: board.ep0in,
|
|
||||||
usbd: board.usbd,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd, ep0in])]
|
#[task(binds = USBD, local = [usbd, ep0in])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
let ep0in = cx.resources.ep0in;
|
let ep0in = cx.local.ep0in;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, ep0in, event)
|
on_event(usbd, ep0in, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, event: Event) {
|
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, event: Event) {
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::UsbReset => {
|
Event::UsbReset => {
|
||||||
// nothing to do here at the moment
|
// nothing to do here at the moment
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UsbEp0DataDone => ep0in.end(usbd),
|
Event::UsbEp0DataDone => ep0in.end(usbd),
|
||||||
|
|
||||||
Event::UsbEp0Setup => {
|
Event::UsbEp0Setup => {
|
||||||
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
||||||
let brequest = usbd.brequest.read().brequest().bits();
|
let brequest = usbd.brequest.read().brequest().bits();
|
||||||
let wlength = usbd::wlength(usbd);
|
let wlength = usbd::wlength(usbd);
|
||||||
let windex = usbd::windex(usbd);
|
let windex = usbd::windex(usbd);
|
||||||
let wvalue = usbd::wvalue(usbd);
|
let wvalue = usbd::wvalue(usbd);
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
||||||
bmrequesttype,
|
bmrequesttype,
|
||||||
brequest,
|
brequest,
|
||||||
wlength,
|
wlength,
|
||||||
windex,
|
windex,
|
||||||
wvalue
|
wvalue
|
||||||
);
|
);
|
||||||
|
|
||||||
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength).expect(
|
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength).expect(
|
||||||
"Error parsing request (goal achieved if GET_DESCRIPTOR Device was handled before)",
|
"Error parsing request (goal achieved if GET_DESCRIPTOR Device was handled before)",
|
||||||
);
|
);
|
||||||
match request {
|
match request {
|
||||||
Request::GetDescriptor { descriptor, length }
|
Request::GetDescriptor { descriptor, length }
|
||||||
if descriptor == Descriptor::Device =>
|
if descriptor == Descriptor::Device =>
|
||||||
{
|
{
|
||||||
defmt::info!("GET_DESCRIPTOR Device [length={}]", length);
|
defmt::println!("GET_DESCRIPTOR Device [length={}]", length);
|
||||||
|
|
||||||
let desc = usb2::device::Descriptor {
|
let desc = usb2::device::Descriptor {
|
||||||
bDeviceClass: 0,
|
bDeviceClass: 0,
|
||||||
bDeviceProtocol: 0,
|
bDeviceProtocol: 0,
|
||||||
bDeviceSubClass: 0,
|
bDeviceSubClass: 0,
|
||||||
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
||||||
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
||||||
bcdDevice: 0x01_00, // 1.00
|
bcdDevice: 0x01_00, // 1.00
|
||||||
iManufacturer: None,
|
iManufacturer: None,
|
||||||
iProduct: None,
|
iProduct: None,
|
||||||
iSerialNumber: None,
|
iSerialNumber: None,
|
||||||
idProduct: consts::PID,
|
idProduct: consts::PID,
|
||||||
idVendor: consts::VID,
|
idVendor: consts::VID,
|
||||||
};
|
};
|
||||||
let desc_bytes = desc.bytes();
|
let desc_bytes = desc.bytes();
|
||||||
let resp = &desc_bytes[..core::cmp::min(desc_bytes.len(), usize::from(length))];
|
let resp = &desc_bytes[..core::cmp::min(desc_bytes.len(), usize::from(length))];
|
||||||
ep0in.start(&resp, usbd);
|
ep0in.start(&resp, usbd);
|
||||||
}
|
}
|
||||||
Request::SetAddress { .. } => {
|
Request::SetAddress { .. } => {
|
||||||
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
||||||
// need to catch it here. We'll properly handle this request later
|
// need to catch it here. We'll properly handle this request later
|
||||||
// but for now it's OK to do nothing.
|
// but for now it's OK to do nothing.
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
defmt::error!(
|
defmt::error!(
|
||||||
"unknown request (goal achieved if GET_DESCRIPTOR Device was handled before)"
|
"unknown request (goal achieved if GET_DESCRIPTOR Device was handled before)"
|
||||||
);
|
);
|
||||||
dk::exit()
|
dk::exit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,99 +1,102 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Ep0In, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
use usb::{Descriptor, Request};
|
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Ep0In, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
use usb::{Descriptor, Request};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
ep0in: Ep0In,
|
ep0in: Ep0In,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
|
||||||
|
}
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
init::LateResources {
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd, ep0in: board.ep0in, }, init::Monotonics())
|
||||||
ep0in: board.ep0in,
|
|
||||||
usbd: board.usbd,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd, ep0in])]
|
#[task(binds = USBD, local = [usbd, ep0in])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
let ep0in = cx.resources.ep0in;
|
let ep0in = cx.local.ep0in;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, ep0in, event)
|
on_event(usbd, ep0in, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, event: Event) {
|
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, event: Event) {
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::UsbReset => {
|
Event::UsbReset => {
|
||||||
// nothing to do here at the moment
|
// nothing to do here at the moment
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UsbEp0DataDone => todo!(), // <- TODO
|
Event::UsbEp0DataDone => todo!(), // <- TODO
|
||||||
|
|
||||||
Event::UsbEp0Setup => {
|
Event::UsbEp0Setup => {
|
||||||
let bmrequesttype = usbd::bmrequesttype(usbd);
|
let bmrequesttype = usbd::bmrequesttype(usbd);
|
||||||
let brequest = usbd::brequest(usbd);
|
let brequest = usbd::brequest(usbd);
|
||||||
let wlength = usbd::wlength(usbd);
|
let wlength = usbd::wlength(usbd);
|
||||||
let windex = usbd::windex(usbd);
|
let windex = usbd::windex(usbd);
|
||||||
let wvalue = usbd::wvalue(usbd);
|
let wvalue = usbd::wvalue(usbd);
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
"SETUP: bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
||||||
bmrequesttype,
|
bmrequesttype,
|
||||||
brequest,
|
brequest,
|
||||||
wlength,
|
wlength,
|
||||||
windex,
|
windex,
|
||||||
wvalue
|
wvalue
|
||||||
);
|
);
|
||||||
|
|
||||||
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength).expect(
|
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength).expect(
|
||||||
"Error parsing request (goal achieved if GET_DESCRIPTOR Device was handled before)",
|
"Error parsing request (goal achieved if GET_DESCRIPTOR Device was handled before)",
|
||||||
);
|
);
|
||||||
match request {
|
match request {
|
||||||
Request::GetDescriptor { descriptor, length }
|
Request::GetDescriptor { descriptor, length }
|
||||||
if descriptor == Descriptor::Device =>
|
if descriptor == Descriptor::Device =>
|
||||||
{
|
{
|
||||||
defmt::info!("GET_DESCRIPTOR Device [length={}]", length);
|
defmt::println!("GET_DESCRIPTOR Device [length={}]", length);
|
||||||
|
|
||||||
// TODO send back a valid device descriptor, truncated to `length` bytes
|
// TODO send back a valid device descriptor, truncated to `length` bytes
|
||||||
// let desc = usb2::device::Descriptor { .. };
|
// let desc = usb2::device::Descriptor { .. };
|
||||||
let resp = [];
|
let resp = [];
|
||||||
|
ep0in.start(&resp, usbd);
|
||||||
// ensure we're not overstepping boundaries
|
}
|
||||||
assert!(resp.len() <= length as usize);
|
Request::SetAddress { .. } => {
|
||||||
ep0in.start(&resp, usbd);
|
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
||||||
}
|
// need to catch it here. We'll properly handle this request later
|
||||||
Request::SetAddress { .. } => {
|
// but for now it's OK to do nothing.
|
||||||
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
}
|
||||||
// need to catch it here. We'll properly handle this request later
|
_ => {
|
||||||
// but for now it's OK to do nothing.
|
defmt::error!(
|
||||||
}
|
"unknown request (goal achieved if GET_DESCRIPTOR Device was handled before)"
|
||||||
_ => {
|
);
|
||||||
defmt::error!(
|
dk::exit()
|
||||||
"unknown request (goal achieved if GET_DESCRIPTOR Device was handled before)"
|
}
|
||||||
);
|
|
||||||
dk::exit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,187 +1,192 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use core::num::NonZeroU8;
|
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Ep0In, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request, State};
|
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Ep0In, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request, State};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
ep0in: Ep0In,
|
ep0in: Ep0In,
|
||||||
state: State,
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
|
||||||
|
}
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
init::LateResources {
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd, ep0in: board.ep0in, state: State::Default, }, init::Monotonics())
|
||||||
usbd: board.usbd,
|
|
||||||
state: State::Default,
|
|
||||||
ep0in: board.ep0in,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd, ep0in, state])]
|
#[task(binds = USBD, local = [usbd, ep0in, state])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
let ep0in = cx.resources.ep0in;
|
let ep0in = cx.local.ep0in;
|
||||||
let state = cx.resources.state;
|
let state = cx.local.state;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, ep0in, state, event)
|
on_event(usbd, ep0in, state, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State, event: Event) {
|
||||||
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State, event: Event) {
|
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
match event {
|
||||||
|
Event::UsbReset => {
|
||||||
match event {
|
defmt::println!("USB reset condition detected");
|
||||||
Event::UsbReset => {
|
*state = State::Default;
|
||||||
defmt::info!("USB reset condition detected");
|
}
|
||||||
*state = State::Default;
|
|
||||||
}
|
Event::UsbEp0DataDone => {
|
||||||
|
defmt::println!("EP0IN: transfer complete");
|
||||||
Event::UsbEp0DataDone => {
|
ep0in.end(usbd);
|
||||||
defmt::info!("EP0IN: transfer complete");
|
}
|
||||||
ep0in.end(usbd);
|
|
||||||
}
|
Event::UsbEp0Setup => {
|
||||||
|
if ep0setup(usbd, ep0in, state).is_err() {
|
||||||
Event::UsbEp0Setup => {
|
defmt::warn!("EP0IN: unexpected request; stalling the endpoint");
|
||||||
if ep0setup(usbd, ep0in, state).is_err() {
|
usbd::ep0stall(usbd);
|
||||||
defmt::warn!("EP0IN: unexpected request; stalling the endpoint");
|
}
|
||||||
usbd::ep0stall(usbd);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// The `bConfigurationValue` of the only supported configuration
|
||||||
/// The `bConfigurationValue` of the only supported configuration
|
const CONFIG_VAL: u8 = 42;
|
||||||
const CONFIG_VAL: u8 = 42;
|
|
||||||
|
fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()> {
|
||||||
fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()> {
|
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
||||||
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
let brequest = usbd.brequest.read().brequest().bits();
|
||||||
let brequest = usbd.brequest.read().brequest().bits();
|
let wlength = usbd::wlength(usbd);
|
||||||
let wlength = usbd::wlength(usbd);
|
let windex = usbd::windex(usbd);
|
||||||
let windex = usbd::windex(usbd);
|
let wvalue = usbd::wvalue(usbd);
|
||||||
let wvalue = usbd::wvalue(usbd);
|
|
||||||
|
defmt::println!(
|
||||||
defmt::info!(
|
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
||||||
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
bmrequesttype,
|
||||||
bmrequesttype,
|
brequest,
|
||||||
brequest,
|
wlength,
|
||||||
wlength,
|
windex,
|
||||||
windex,
|
wvalue
|
||||||
wvalue
|
);
|
||||||
);
|
|
||||||
|
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
||||||
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
.expect("Error parsing request");
|
||||||
.expect("Error parsing request");
|
defmt::println!("EP0: {:?}", defmt::Debug2Format(&request));
|
||||||
defmt::info!("EP0: {:?}", defmt::Debug2Format(&request));
|
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
|
||||||
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
|
// `StandardRequest` with `defmt`
|
||||||
// `StandardRequest` with `defmt`
|
match request {
|
||||||
match request {
|
// section 9.4.3
|
||||||
// section 9.4.3
|
// this request is valid in any state
|
||||||
// this request is valid in any state
|
Request::GetDescriptor { descriptor, length } => match descriptor {
|
||||||
Request::GetDescriptor { descriptor, length } => match descriptor {
|
Descriptor::Device => {
|
||||||
Descriptor::Device => {
|
let desc = usb2::device::Descriptor {
|
||||||
let desc = usb2::device::Descriptor {
|
bDeviceClass: 0,
|
||||||
bDeviceClass: 0,
|
bDeviceProtocol: 0,
|
||||||
bDeviceProtocol: 0,
|
bDeviceSubClass: 0,
|
||||||
bDeviceSubClass: 0,
|
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
||||||
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
||||||
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
bcdDevice: 0x01_00, // 1.00
|
||||||
bcdDevice: 0x01_00, // 1.00
|
iManufacturer: None,
|
||||||
iManufacturer: None,
|
iProduct: None,
|
||||||
iProduct: None,
|
iSerialNumber: None,
|
||||||
iSerialNumber: None,
|
idProduct: consts::PID,
|
||||||
idProduct: consts::PID,
|
idVendor: consts::VID,
|
||||||
idVendor: consts::VID,
|
|
||||||
};
|
|
||||||
let bytes = desc.bytes();
|
|
||||||
let _ = ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd);
|
|
||||||
}
|
|
||||||
|
|
||||||
Descriptor::Configuration { index } => {
|
|
||||||
if index == 0 {
|
|
||||||
let mut resp = heapless::Vec::<u8, 64>::new();
|
|
||||||
|
|
||||||
let conf_desc = usb2::configuration::Descriptor {
|
|
||||||
wTotalLength: (usb2::configuration::Descriptor::SIZE
|
|
||||||
+ usb2::interface::Descriptor::SIZE)
|
|
||||||
.into(),
|
|
||||||
bNumInterfaces: NonZeroU8::new(1).unwrap(),
|
|
||||||
bConfigurationValue: core::num::NonZeroU8::new(CONFIG_VAL).unwrap(),
|
|
||||||
iConfiguration: None,
|
|
||||||
bmAttributes: usb2::configuration::bmAttributes {
|
|
||||||
self_powered: true,
|
|
||||||
remote_wakeup: false,
|
|
||||||
},
|
|
||||||
bMaxPower: 250, // 500 mA
|
|
||||||
};
|
};
|
||||||
|
let bytes = desc.bytes();
|
||||||
let iface_desc = usb2::interface::Descriptor {
|
let _ = ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd);
|
||||||
bInterfaceNumber: 0,
|
|
||||||
bAlternativeSetting: 0,
|
|
||||||
bNumEndpoints: 0,
|
|
||||||
bInterfaceClass: 0,
|
|
||||||
bInterfaceSubClass: 0,
|
|
||||||
bInterfaceProtocol: 0,
|
|
||||||
iInterface: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
resp.extend_from_slice(&conf_desc.bytes()).unwrap();
|
|
||||||
resp.extend_from_slice(&iface_desc.bytes()).unwrap();
|
|
||||||
ep0in.start(&resp[..core::cmp::min(resp.len(), length.into())], usbd);
|
|
||||||
} else {
|
|
||||||
// out of bounds access: stall the endpoint
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Descriptor::Configuration { index } => {
|
||||||
|
if index == 0 {
|
||||||
|
let mut resp = heapless::Vec::<u8, 64>::new();
|
||||||
|
|
||||||
|
let conf_desc = usb2::configuration::Descriptor {
|
||||||
|
wTotalLength: (usb2::configuration::Descriptor::SIZE
|
||||||
|
+ usb2::interface::Descriptor::SIZE)
|
||||||
|
.into(),
|
||||||
|
bNumInterfaces: NonZeroU8::new(1).unwrap(),
|
||||||
|
bConfigurationValue: core::num::NonZeroU8::new(CONFIG_VAL).unwrap(),
|
||||||
|
iConfiguration: None,
|
||||||
|
bmAttributes: usb2::configuration::bmAttributes {
|
||||||
|
self_powered: true,
|
||||||
|
remote_wakeup: false,
|
||||||
|
},
|
||||||
|
bMaxPower: 250, // 500 mA
|
||||||
|
};
|
||||||
|
|
||||||
|
let iface_desc = usb2::interface::Descriptor {
|
||||||
|
bInterfaceNumber: 0,
|
||||||
|
bAlternativeSetting: 0,
|
||||||
|
bNumEndpoints: 0,
|
||||||
|
bInterfaceClass: 0,
|
||||||
|
bInterfaceSubClass: 0,
|
||||||
|
bInterfaceProtocol: 0,
|
||||||
|
iInterface: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
resp.extend_from_slice(&conf_desc.bytes()).unwrap();
|
||||||
|
resp.extend_from_slice(&iface_desc.bytes()).unwrap();
|
||||||
|
ep0in.start(&resp[..core::cmp::min(resp.len(), length.into())], usbd);
|
||||||
|
} else {
|
||||||
|
// out of bounds access: stall the endpoint
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return Err(()),
|
||||||
|
},
|
||||||
|
|
||||||
|
Request::SetAddress { address } => {
|
||||||
|
match state {
|
||||||
|
State::Default => {
|
||||||
|
if let Some(address) = address {
|
||||||
|
*state = State::Address(address);
|
||||||
|
} else {
|
||||||
|
// stay in the default state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
State::Address(..) => {
|
||||||
|
if let Some(address) = address {
|
||||||
|
// use the new address
|
||||||
|
*state = State::Address(address);
|
||||||
|
} else {
|
||||||
|
*state = State::Default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unspecified behavior
|
||||||
|
State::Configured { .. } => return Err(()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// the response to this request is handled in hardware
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stall any other request
|
||||||
_ => return Err(()),
|
_ => return Err(()),
|
||||||
},
|
|
||||||
|
|
||||||
Request::SetAddress { address } => {
|
|
||||||
match state {
|
|
||||||
State::Default => {
|
|
||||||
if let Some(address) = address {
|
|
||||||
*state = State::Address(address);
|
|
||||||
} else {
|
|
||||||
// stay in the default state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
State::Address(..) => {
|
|
||||||
if let Some(address) = address {
|
|
||||||
// use the new address
|
|
||||||
*state = State::Address(address);
|
|
||||||
} else {
|
|
||||||
*state = State::Default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unspecified behavior
|
|
||||||
State::Configured { .. } => return Err(()),
|
|
||||||
}
|
|
||||||
|
|
||||||
// the response to this request is handled in hardware
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stall any other request
|
Ok(())
|
||||||
_ => return Err(()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,135 +1,143 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Ep0In, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
|
|
||||||
use usb2::State;
|
|
||||||
// HEADS UP to use *your* USB packet parser uncomment line 12 and remove line 13
|
|
||||||
// use usb::{Request, Descriptor};
|
|
||||||
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request};
|
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Ep0In, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
use usb2::State;
|
||||||
|
// HEADS UP to use *your* USB packet parser uncomment line 12 and remove line 13
|
||||||
|
// use usb::{Request, Descriptor};
|
||||||
|
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
ep0in: Ep0In,
|
ep0in: Ep0In,
|
||||||
state: State,
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
}
|
||||||
|
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
init::LateResources {
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd, ep0in: board.ep0in, state: State::Default, }, init::Monotonics())
|
||||||
usbd: board.usbd,
|
|
||||||
state: State::Default,
|
|
||||||
ep0in: board.ep0in,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd, ep0in, state])]
|
#[task(binds = USBD, local = [usbd, ep0in, state])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
let ep0in = cx.resources.ep0in;
|
let ep0in = cx.local.ep0in;
|
||||||
let state = cx.resources.state;
|
let state = cx.local.state;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, ep0in, state, event)
|
on_event(usbd, ep0in, state, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State, event: Event) {
|
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State, event: Event) {
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
// TODO change `state` as specified in chapter 9.1 USB Device States, of the USB specification
|
// TODO change `state` as specified in chapter 9.1 USB Device States, of the USB specification
|
||||||
Event::UsbReset => {
|
Event::UsbReset => {
|
||||||
defmt::info!("USB reset condition detected");
|
defmt::println!("USB reset condition detected");
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UsbEp0DataDone => {
|
Event::UsbEp0DataDone => {
|
||||||
defmt::info!("EP0IN: transfer complete");
|
defmt::println!("EP0IN: transfer complete");
|
||||||
ep0in.end(usbd);
|
ep0in.end(usbd);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UsbEp0Setup => {
|
Event::UsbEp0Setup => {
|
||||||
if ep0setup(usbd, ep0in, state).is_err() {
|
if ep0setup(usbd, ep0in, state).is_err() {
|
||||||
// unsupported or invalid request:
|
// unsupported or invalid request:
|
||||||
// TODO: add code to stall the endpoint
|
// TODO: add code to stall the endpoint
|
||||||
defmt::warn!("EP0IN: unexpected request; stalling the endpoint");
|
defmt::warn!("EP0IN: unexpected request; stalling the endpoint");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, _state: &mut State) -> Result<(), ()> {
|
||||||
fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, _state: &mut State) -> Result<(), ()> {
|
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
||||||
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
let brequest = usbd.brequest.read().brequest().bits();
|
||||||
let brequest = usbd.brequest.read().brequest().bits();
|
let wlength = usbd::wlength(usbd);
|
||||||
let wlength = usbd::wlength(usbd);
|
let windex = usbd::windex(usbd);
|
||||||
let windex = usbd::windex(usbd);
|
let wvalue = usbd::wvalue(usbd);
|
||||||
let wvalue = usbd::wvalue(usbd);
|
|
||||||
|
defmt::println!(
|
||||||
defmt::info!(
|
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
||||||
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
bmrequesttype,
|
||||||
bmrequesttype,
|
brequest,
|
||||||
brequest,
|
wlength,
|
||||||
wlength,
|
windex,
|
||||||
windex,
|
wvalue
|
||||||
wvalue
|
);
|
||||||
);
|
|
||||||
|
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
||||||
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
.expect("Error parsing request");
|
||||||
.expect("Error parsing request");
|
defmt::println!("EP0: {:?}", defmt::Debug2Format(&request));
|
||||||
defmt::info!("EP0: {:?}", defmt::Debug2Format(&request));
|
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
|
||||||
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
|
// `StandardRequest` with `defmt`
|
||||||
// `StandardRequest` with `defmt`
|
|
||||||
|
match request {
|
||||||
match request {
|
Request::GetDescriptor { descriptor, length } => match descriptor {
|
||||||
Request::GetDescriptor { descriptor, length } => match descriptor {
|
Descriptor::Device => {
|
||||||
Descriptor::Device => {
|
let desc = usb2::device::Descriptor {
|
||||||
let desc = usb2::device::Descriptor {
|
bDeviceClass: 0,
|
||||||
bDeviceClass: 0,
|
bDeviceProtocol: 0,
|
||||||
bDeviceProtocol: 0,
|
bDeviceSubClass: 0,
|
||||||
bDeviceSubClass: 0,
|
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
||||||
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
||||||
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
bcdDevice: 0x01_00, // 1.00
|
||||||
bcdDevice: 0x01_00, // 1.00
|
iManufacturer: None,
|
||||||
iManufacturer: None,
|
iProduct: None,
|
||||||
iProduct: None,
|
iSerialNumber: None,
|
||||||
iSerialNumber: None,
|
idProduct: consts::PID,
|
||||||
idProduct: consts::PID,
|
idVendor: consts::VID,
|
||||||
idVendor: consts::VID,
|
};
|
||||||
};
|
let bytes = desc.bytes();
|
||||||
let bytes = desc.bytes();
|
let _ = ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd);
|
||||||
let _ = ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd);
|
}
|
||||||
|
|
||||||
|
// TODO implement Configuration descriptor
|
||||||
|
// Descriptor::Configuration { .. } => todo!(),
|
||||||
|
|
||||||
|
// stall any other request
|
||||||
|
_ => return Err(()),
|
||||||
|
},
|
||||||
|
Request::SetAddress { .. } => {
|
||||||
|
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
||||||
|
// need to catch it here.
|
||||||
|
|
||||||
|
// TODO: handle this request properly now.
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO implement Configuration descriptor
|
|
||||||
// Descriptor::Configuration { .. } => todo!(),
|
|
||||||
|
|
||||||
// stall any other request
|
// stall any other request
|
||||||
_ => return Err(()),
|
_ => return Err(()),
|
||||||
},
|
|
||||||
Request::SetAddress { .. } => {
|
|
||||||
// On Mac OS you'll get this request before the GET_DESCRIPTOR request so we
|
|
||||||
// need to catch it here.
|
|
||||||
|
|
||||||
// TODO: handle this request properly now.
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stall any other request
|
Ok(())
|
||||||
_ => return Err(()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,235 +1,240 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use core::num::NonZeroU8;
|
|
||||||
|
|
||||||
use dk::{
|
|
||||||
peripheral::USBD,
|
|
||||||
usbd::{self, Ep0In, Event},
|
|
||||||
};
|
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use firmware as _;
|
use firmware as _;
|
||||||
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request, State};
|
|
||||||
|
|
||||||
#[rtic::app(device = dk)]
|
#[rtic::app(device = dk, peripherals = false)]
|
||||||
const APP: () = {
|
mod app {
|
||||||
struct Resources {
|
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
|
use dk::{
|
||||||
|
peripheral::USBD,
|
||||||
|
usbd::{self, Ep0In, Event},
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request, State};
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct MyLocalResources {
|
||||||
usbd: USBD,
|
usbd: USBD,
|
||||||
ep0in: Ep0In,
|
ep0in: Ep0In,
|
||||||
state: State,
|
state: State,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct MySharedResources {
|
||||||
|
|
||||||
|
}
|
||||||
#[init]
|
#[init]
|
||||||
fn init(_cx: init::Context) -> init::LateResources {
|
fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||||
let board = dk::init().unwrap();
|
let board = dk::init().unwrap();
|
||||||
|
|
||||||
usbd::init(board.power, &board.usbd);
|
usbd::init(board.power, &board.usbd);
|
||||||
|
|
||||||
init::LateResources {
|
(MySharedResources {}, MyLocalResources { usbd: board.usbd, ep0in: board.ep0in, state: State::Default, }, init::Monotonics())
|
||||||
usbd: board.usbd,
|
|
||||||
state: State::Default,
|
|
||||||
ep0in: board.ep0in,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USBD, resources = [usbd, ep0in, state])]
|
#[task(binds = USBD, local = [usbd, ep0in, state])]
|
||||||
fn main(cx: main::Context) {
|
fn main(cx: main::Context) {
|
||||||
let usbd = cx.resources.usbd;
|
let usbd = cx.local.usbd;
|
||||||
let ep0in = cx.resources.ep0in;
|
let ep0in = cx.local.ep0in;
|
||||||
let state = cx.resources.state;
|
let state = cx.local.state;
|
||||||
|
|
||||||
while let Some(event) = usbd::next_event(usbd) {
|
while let Some(event) = usbd::next_event(usbd) {
|
||||||
on_event(usbd, ep0in, state, event)
|
on_event(usbd, ep0in, state, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State, event: Event) {
|
||||||
|
defmt::println!("USB: {:?} @ {:?}", event, dk::uptime());
|
||||||
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State, event: Event) {
|
|
||||||
defmt::info!("USB: {:?} @ {:?}", event, dk::uptime());
|
match event {
|
||||||
|
Event::UsbReset => {
|
||||||
match event {
|
defmt::println!("USB reset condition detected");
|
||||||
Event::UsbReset => {
|
*state = State::Default;
|
||||||
defmt::info!("USB reset condition detected");
|
}
|
||||||
*state = State::Default;
|
|
||||||
}
|
Event::UsbEp0DataDone => {
|
||||||
|
defmt::println!("EP0IN: transfer complete");
|
||||||
Event::UsbEp0DataDone => {
|
ep0in.end(usbd);
|
||||||
defmt::info!("EP0IN: transfer complete");
|
}
|
||||||
ep0in.end(usbd);
|
|
||||||
}
|
Event::UsbEp0Setup => {
|
||||||
|
if ep0setup(usbd, ep0in, state).is_err() {
|
||||||
Event::UsbEp0Setup => {
|
defmt::warn!("EP0IN: unexpected request; stalling the endpoint");
|
||||||
if ep0setup(usbd, ep0in, state).is_err() {
|
usbd::ep0stall(usbd);
|
||||||
defmt::warn!("EP0IN: unexpected request; stalling the endpoint");
|
}
|
||||||
usbd::ep0stall(usbd);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// The `bConfigurationValue` of the only supported configuration
|
||||||
/// The `bConfigurationValue` of the only supported configuration
|
const CONFIG_VAL: u8 = 42;
|
||||||
const CONFIG_VAL: u8 = 42;
|
|
||||||
|
fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()> {
|
||||||
fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()> {
|
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
||||||
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
|
let brequest = usbd.brequest.read().brequest().bits();
|
||||||
let brequest = usbd.brequest.read().brequest().bits();
|
let wlength = usbd::wlength(usbd);
|
||||||
let wlength = usbd::wlength(usbd);
|
let windex = usbd::windex(usbd);
|
||||||
let windex = usbd::windex(usbd);
|
let wvalue = usbd::wvalue(usbd);
|
||||||
let wvalue = usbd::wvalue(usbd);
|
|
||||||
|
defmt::println!(
|
||||||
defmt::info!(
|
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
||||||
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
|
bmrequesttype,
|
||||||
bmrequesttype,
|
brequest,
|
||||||
brequest,
|
wlength,
|
||||||
wlength,
|
windex,
|
||||||
windex,
|
wvalue
|
||||||
wvalue
|
);
|
||||||
);
|
|
||||||
|
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
||||||
let request = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
|
.expect("Error parsing request");
|
||||||
.expect("Error parsing request");
|
defmt::println!("EP0: {:?}", defmt::Debug2Format(&request));
|
||||||
defmt::info!("EP0: {:?}", defmt::Debug2Format(&request));
|
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
|
||||||
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
|
// `StandardRequest` with `defmt`
|
||||||
// `StandardRequest` with `defmt`
|
|
||||||
|
match request {
|
||||||
match request {
|
// section 9.4.3
|
||||||
// section 9.4.3
|
// this request is valid in any state
|
||||||
// this request is valid in any state
|
Request::GetDescriptor { descriptor, length } => match descriptor {
|
||||||
Request::GetDescriptor { descriptor, length } => match descriptor {
|
Descriptor::Device => {
|
||||||
Descriptor::Device => {
|
let desc = usb2::device::Descriptor {
|
||||||
let desc = usb2::device::Descriptor {
|
bDeviceClass: 0,
|
||||||
bDeviceClass: 0,
|
bDeviceProtocol: 0,
|
||||||
bDeviceProtocol: 0,
|
bDeviceSubClass: 0,
|
||||||
bDeviceSubClass: 0,
|
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
||||||
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
|
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
||||||
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
|
bcdDevice: 0x01_00, // 1.00
|
||||||
bcdDevice: 0x01_00, // 1.00
|
iManufacturer: None,
|
||||||
iManufacturer: None,
|
iProduct: None,
|
||||||
iProduct: None,
|
iSerialNumber: None,
|
||||||
iSerialNumber: None,
|
idProduct: consts::PID,
|
||||||
idProduct: consts::PID,
|
idVendor: consts::VID,
|
||||||
idVendor: consts::VID,
|
|
||||||
};
|
|
||||||
let bytes = desc.bytes();
|
|
||||||
let _ = ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd);
|
|
||||||
}
|
|
||||||
|
|
||||||
Descriptor::Configuration { index } => {
|
|
||||||
if index == 0 {
|
|
||||||
let mut resp = heapless::Vec::<u8, 64>::new();
|
|
||||||
|
|
||||||
let conf_desc = usb2::configuration::Descriptor {
|
|
||||||
wTotalLength: (usb2::configuration::Descriptor::SIZE
|
|
||||||
+ usb2::interface::Descriptor::SIZE)
|
|
||||||
.into(),
|
|
||||||
bNumInterfaces: NonZeroU8::new(1).unwrap(),
|
|
||||||
bConfigurationValue: core::num::NonZeroU8::new(CONFIG_VAL).unwrap(),
|
|
||||||
iConfiguration: None,
|
|
||||||
bmAttributes: usb2::configuration::bmAttributes {
|
|
||||||
self_powered: true,
|
|
||||||
remote_wakeup: false,
|
|
||||||
},
|
|
||||||
bMaxPower: 250, // 500 mA
|
|
||||||
};
|
};
|
||||||
|
let bytes = desc.bytes();
|
||||||
let iface_desc = usb2::interface::Descriptor {
|
let _ = ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd);
|
||||||
bInterfaceNumber: 0,
|
|
||||||
bAlternativeSetting: 0,
|
|
||||||
bNumEndpoints: 0,
|
|
||||||
bInterfaceClass: 0,
|
|
||||||
bInterfaceSubClass: 0,
|
|
||||||
bInterfaceProtocol: 0,
|
|
||||||
iInterface: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
resp.extend_from_slice(&conf_desc.bytes()).unwrap();
|
|
||||||
resp.extend_from_slice(&iface_desc.bytes()).unwrap();
|
|
||||||
ep0in.start(&resp[..core::cmp::min(resp.len(), length.into())], usbd);
|
|
||||||
} else {
|
|
||||||
// out of bounds access: stall the endpoint
|
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Descriptor::Configuration { index } => {
|
||||||
_ => return Err(()),
|
if index == 0 {
|
||||||
},
|
let mut resp = heapless::Vec::<u8, 64>::new();
|
||||||
|
|
||||||
Request::SetAddress { address } => {
|
let conf_desc = usb2::configuration::Descriptor {
|
||||||
match state {
|
wTotalLength: (usb2::configuration::Descriptor::SIZE
|
||||||
State::Default => {
|
+ usb2::interface::Descriptor::SIZE)
|
||||||
if let Some(address) = address {
|
.into(),
|
||||||
*state = State::Address(address);
|
bNumInterfaces: NonZeroU8::new(1).unwrap(),
|
||||||
|
bConfigurationValue: core::num::NonZeroU8::new(CONFIG_VAL).unwrap(),
|
||||||
|
iConfiguration: None,
|
||||||
|
bmAttributes: usb2::configuration::bmAttributes {
|
||||||
|
self_powered: true,
|
||||||
|
remote_wakeup: false,
|
||||||
|
},
|
||||||
|
bMaxPower: 250, // 500 mA
|
||||||
|
};
|
||||||
|
|
||||||
|
let iface_desc = usb2::interface::Descriptor {
|
||||||
|
bInterfaceNumber: 0,
|
||||||
|
bAlternativeSetting: 0,
|
||||||
|
bNumEndpoints: 0,
|
||||||
|
bInterfaceClass: 0,
|
||||||
|
bInterfaceSubClass: 0,
|
||||||
|
bInterfaceProtocol: 0,
|
||||||
|
iInterface: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
resp.extend_from_slice(&conf_desc.bytes()).unwrap();
|
||||||
|
resp.extend_from_slice(&iface_desc.bytes()).unwrap();
|
||||||
|
ep0in.start(&resp[..core::cmp::min(resp.len(), length.into())], usbd);
|
||||||
} else {
|
} else {
|
||||||
// stay in the default state
|
// out of bounds access: stall the endpoint
|
||||||
|
return Err(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
State::Address(..) => {
|
_ => return Err(()),
|
||||||
if let Some(address) = address {
|
},
|
||||||
// use the new address
|
|
||||||
*state = State::Address(address);
|
Request::SetAddress { address } => {
|
||||||
} else {
|
match state {
|
||||||
*state = State::Default;
|
State::Default => {
|
||||||
}
|
if let Some(address) = address {
|
||||||
}
|
*state = State::Address(address);
|
||||||
|
|
||||||
// unspecified behavior
|
|
||||||
State::Configured { .. } => return Err(()),
|
|
||||||
}
|
|
||||||
|
|
||||||
// the response to this request is handled in hardware
|
|
||||||
}
|
|
||||||
|
|
||||||
Request::SetConfiguration { value } => {
|
|
||||||
match *state {
|
|
||||||
// unspecified behavior
|
|
||||||
State::Default => return Err(()),
|
|
||||||
|
|
||||||
State::Address(address) => {
|
|
||||||
if let Some(value) = value {
|
|
||||||
if value.get() == CONFIG_VAL {
|
|
||||||
defmt::info!("entering the configured state");
|
|
||||||
*state = State::Configured { address, value };
|
|
||||||
} else {
|
} else {
|
||||||
defmt::error!("unsupported configuration value");
|
// stay in the default state
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// stay in the address mode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
State::Address(..) => {
|
||||||
|
if let Some(address) = address {
|
||||||
|
// use the new address
|
||||||
|
*state = State::Address(address);
|
||||||
|
} else {
|
||||||
|
*state = State::Default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unspecified behavior
|
||||||
|
State::Configured { .. } => return Err(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
State::Configured {
|
// the response to this request is handled in hardware
|
||||||
address,
|
}
|
||||||
value: curr_value,
|
|
||||||
} => {
|
Request::SetConfiguration { value } => {
|
||||||
if let Some(new_value) = value {
|
match *state {
|
||||||
if new_value.get() == CONFIG_VAL {
|
// unspecified behavior
|
||||||
if new_value != curr_value {
|
State::Default => return Err(()),
|
||||||
defmt::info!("changing configuration");
|
|
||||||
*state = State::Configured {
|
State::Address(address) => {
|
||||||
address,
|
if let Some(value) = value {
|
||||||
value: new_value,
|
if value.get() == CONFIG_VAL {
|
||||||
};
|
defmt::println!("entering the configured state");
|
||||||
|
*state = State::Configured { address, value };
|
||||||
|
} else {
|
||||||
|
defmt::error!("unsupported configuration value");
|
||||||
|
return Err(());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defmt::error!("unsupported configuration value");
|
// stay in the address mode
|
||||||
return Err(());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
State::Configured {
|
||||||
|
address,
|
||||||
|
value: curr_value,
|
||||||
|
} => {
|
||||||
|
if let Some(new_value) = value {
|
||||||
|
if new_value.get() == CONFIG_VAL {
|
||||||
|
if new_value != curr_value {
|
||||||
|
defmt::println!("changing configuration");
|
||||||
|
*state = State::Configured {
|
||||||
|
address,
|
||||||
|
value: new_value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defmt::error!("unsupported configuration value");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defmt::println!("returned to the address state");
|
||||||
|
*state = State::Address(address);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
defmt::info!("returned to the address state");
|
|
||||||
*state = State::Address(address);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usbd.tasks_ep0status
|
||||||
|
.write(|w| w.tasks_ep0status().set_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
usbd.tasks_ep0status
|
// stall any other request
|
||||||
.write(|w| w.tasks_ep0status().set_bit());
|
_ => return Err(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// stall any other request
|
Ok(())
|
||||||
_ => return Err(()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,13 @@ fn main() -> ! {
|
||||||
|
|
||||||
// `heapless::Vec` exposes the same API surface as `std::Vec` but some of its methods return a
|
// `heapless::Vec` exposes the same API surface as `std::Vec` but some of its methods return a
|
||||||
// `Result` to indicate whether the operation failed due to the `heapless::Vec` being full
|
// `Result` to indicate whether the operation failed due to the `heapless::Vec` being full
|
||||||
defmt::info!("start: {:?}", buffer);
|
defmt::println!("start: {:?}", buffer);
|
||||||
|
|
||||||
buffer.push(0).expect("buffer full");
|
buffer.push(0).expect("buffer full");
|
||||||
defmt::info!("after `push`: {:?}", buffer);
|
defmt::println!("after `push`: {:?}", buffer);
|
||||||
|
|
||||||
buffer.extend_from_slice(&[1, 2, 3]).expect("buffer full");
|
buffer.extend_from_slice(&[1, 2, 3]).expect("buffer full");
|
||||||
defmt::info!("after `extend`: {:?}", &buffer);
|
defmt::println!("after `extend`: {:?}", &buffer);
|
||||||
|
|
||||||
// TODO try this operation
|
// TODO try this operation
|
||||||
// buffer.extend_from_slice(&[4, 5, 6, 7]).expect("buffer full");
|
// buffer.extend_from_slice(&[4, 5, 6, 7]).expect("buffer full");
|
||||||
|
|
|
@ -20,7 +20,7 @@ fn main() -> ! {
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
led.toggle();
|
led.toggle();
|
||||||
timer.wait(Duration::from_secs(1));
|
timer.wait(Duration::from_secs(1));
|
||||||
defmt::info!("LED toggled at {:?}", dk::uptime());
|
defmt::println!("LED toggled at {:?}", dk::uptime());
|
||||||
}
|
}
|
||||||
|
|
||||||
dk::exit()
|
dk::exit()
|
||||||
|
|
|
@ -19,7 +19,7 @@ fn main() -> ! {
|
||||||
// initializes the peripherals
|
// initializes the peripherals
|
||||||
dk::init().unwrap();
|
dk::init().unwrap();
|
||||||
|
|
||||||
defmt::info!("Hello, world!"); // :wave:
|
defmt::println!("Hello, world!"); // :wave:
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// breakpoint: halts the program's execution
|
// breakpoint: halts the program's execution
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
use cortex_m::asm;
|
use cortex_m::asm;
|
||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||||
use apps as _;
|
// use apps as _;
|
||||||
|
|
||||||
#[entry]
|
#[entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
|
@ -29,7 +29,7 @@ fn bar() {
|
||||||
let array = [0, 1, 2];
|
let array = [0, 1, 2];
|
||||||
let x = array[i]; // out of bounds access
|
let x = array[i]; // out of bounds access
|
||||||
|
|
||||||
defmt::info!("{}", x);
|
defmt::println!("{}", x);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index() -> usize {
|
fn index() -> usize {
|
||||||
|
|
|
@ -38,9 +38,9 @@ fn main() -> ! {
|
||||||
if packet.len() == 1 {
|
if packet.len() == 1 {
|
||||||
let destination = packet[0];
|
let destination = packet[0];
|
||||||
|
|
||||||
defmt::info!("{} -> {}", source, destination);
|
defmt::println!("{} -> {}", source, destination);
|
||||||
// or cast to `char` for a more readable output
|
// or cast to `char` for a more readable output
|
||||||
defmt::info!("{:?} -> {:?}", source as char, destination as char);
|
defmt::println!("{:?} -> {:?}", source as char, destination as char);
|
||||||
} else {
|
} else {
|
||||||
defmt::error!("response packet was not a single byte");
|
defmt::error!("response packet was not a single byte");
|
||||||
dk::exit()
|
dk::exit()
|
||||||
|
|
|
@ -27,9 +27,9 @@ fn main() -> ! {
|
||||||
let key = b'A';
|
let key = b'A';
|
||||||
let value = dict[&key]; // the key needs to be passed by reference
|
let value = dict[&key]; // the key needs to be passed by reference
|
||||||
|
|
||||||
defmt::info!("{} -> {}", key, value);
|
defmt::println!("{} -> {}", key, value);
|
||||||
// more readable
|
// more readable
|
||||||
defmt::info!("{:?} -> {:?}", key as char, value as char);
|
defmt::println!("{:?} -> {:?}", key as char, value as char);
|
||||||
|
|
||||||
// TODO try another insertion
|
// TODO try another insertion
|
||||||
// TODO try looking up a key not contained in the dictionary
|
// TODO try looking up a key not contained in the dictionary
|
||||||
|
|
|
@ -46,7 +46,7 @@ fn main() -> ! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!("{:?}", defmt::Debug2Format(&dict));
|
defmt::println!("{:?}", defmt::Debug2Format(&dict));
|
||||||
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log `heapless`
|
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log `heapless`
|
||||||
// data structures (like `LinearMap` here) with `defmt`
|
// data structures (like `LinearMap` here) with `defmt`
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ fn main() -> ! {
|
||||||
buffer.push(b'i').expect("buffer full");
|
buffer.push(b'i').expect("buffer full");
|
||||||
|
|
||||||
// look into the contents so far
|
// look into the contents so far
|
||||||
defmt::info!("{:?}", buffer);
|
defmt::println!("{:?}", buffer);
|
||||||
|
|
||||||
// or more readable
|
// or more readable
|
||||||
// NOTE utf-8 conversion works as long as you only push bytes in the ASCII range (0..=127)
|
// NOTE utf-8 conversion works as long as you only push bytes in the ASCII range (0..=127)
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"{}",
|
"{}",
|
||||||
str::from_utf8(&buffer).expect("content was not UTF-8")
|
str::from_utf8(&buffer).expect("content was not UTF-8")
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,7 +32,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"ciphertext: {}",
|
"ciphertext: {}",
|
||||||
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
||||||
);
|
);
|
||||||
|
@ -48,7 +48,7 @@ fn main() -> ! {
|
||||||
buf.push(output).expect("buffer full");
|
buf.push(output).expect("buffer full");
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"plaintext: {}",
|
"plaintext: {}",
|
||||||
str::from_utf8(&buf).expect("buffer contains non-UTF-8 data")
|
str::from_utf8(&buf).expect("buffer contains non-UTF-8 data")
|
||||||
);
|
);
|
||||||
|
|
|
@ -56,7 +56,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"ciphertext: {}",
|
"ciphertext: {}",
|
||||||
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
||||||
);
|
);
|
||||||
|
@ -72,7 +72,7 @@ fn main() -> ! {
|
||||||
buffer.push(value).expect("buffer full");
|
buffer.push(value).expect("buffer full");
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"plaintext: {}",
|
"plaintext: {}",
|
||||||
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
|
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
|
||||||
);
|
);
|
||||||
|
|
|
@ -56,7 +56,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"ciphertext: {}",
|
"ciphertext: {}",
|
||||||
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
||||||
);
|
);
|
||||||
|
@ -72,7 +72,7 @@ fn main() -> ! {
|
||||||
buffer.push(value).expect("buffer full");
|
buffer.push(value).expect("buffer full");
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"plaintext: {}",
|
"plaintext: {}",
|
||||||
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
|
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
|
||||||
);
|
);
|
||||||
|
@ -87,7 +87,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"Dongle response: {}",
|
"Dongle response: {}",
|
||||||
str::from_utf8(&packet).expect("response was not UTF-8")
|
str::from_utf8(&packet).expect("response was not UTF-8")
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,7 +57,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"ciphertext: {}",
|
"ciphertext: {}",
|
||||||
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
||||||
);
|
);
|
||||||
|
@ -75,7 +75,7 @@ fn main() -> ! {
|
||||||
*spot = plainletter;
|
*spot = plainletter;
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"plaintext: {}",
|
"plaintext: {}",
|
||||||
str::from_utf8(&packet).expect("buffer contains non-UTF-8 data")
|
str::from_utf8(&packet).expect("buffer contains non-UTF-8 data")
|
||||||
);
|
);
|
||||||
|
@ -88,7 +88,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"Dongle response: {}",
|
"Dongle response: {}",
|
||||||
str::from_utf8(&packet).expect("response was not UTF-8")
|
str::from_utf8(&packet).expect("response was not UTF-8")
|
||||||
);
|
);
|
||||||
|
|
|
@ -60,7 +60,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"ciphertext: {}",
|
"ciphertext: {}",
|
||||||
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
str::from_utf8(&packet).expect("packet was not valid UTF-8")
|
||||||
);
|
);
|
||||||
|
@ -77,7 +77,7 @@ fn main() -> ! {
|
||||||
buffer.push(plainletter).expect("buffer full");
|
buffer.push(plainletter).expect("buffer full");
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"plaintext: {}",
|
"plaintext: {}",
|
||||||
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
|
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
|
||||||
);
|
);
|
||||||
|
@ -92,7 +92,7 @@ fn main() -> ! {
|
||||||
dk::exit()
|
dk::exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"Dongle response: {}",
|
"Dongle response: {}",
|
||||||
str::from_utf8(&packet).expect("response was not UTF-8")
|
str::from_utf8(&packet).expect("response was not UTF-8")
|
||||||
);
|
);
|
||||||
|
|
|
@ -34,14 +34,14 @@ fn main() -> ! {
|
||||||
// let msg = b"Hello?";
|
// let msg = b"Hello?";
|
||||||
|
|
||||||
packet.copy_from_slice(msg);
|
packet.copy_from_slice(msg);
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"sending: {}",
|
"sending: {}",
|
||||||
str::from_utf8(msg).expect("msg was not valid UTF-8 data")
|
str::from_utf8(msg).expect("msg was not valid UTF-8 data")
|
||||||
);
|
);
|
||||||
|
|
||||||
radio.send(&mut packet);
|
radio.send(&mut packet);
|
||||||
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
|
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"received: {}",
|
"received: {}",
|
||||||
str::from_utf8(&packet).expect("response was not valid UTF-8 data")
|
str::from_utf8(&packet).expect("response was not valid UTF-8 data")
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,7 +25,7 @@ fn main() -> ! {
|
||||||
let msg = b"olleh";
|
let msg = b"olleh";
|
||||||
packet.copy_from_slice(msg);
|
packet.copy_from_slice(msg);
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"sending: {}",
|
"sending: {}",
|
||||||
str::from_utf8(msg).expect("message is not valid UTF-8")
|
str::from_utf8(msg).expect("message is not valid UTF-8")
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ fn main() -> ! {
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(crc) => {
|
Ok(crc) => {
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"received: {} (CRC = {:X})",
|
"received: {} (CRC = {:X})",
|
||||||
// ^^ print as uppercase hexadecimal
|
// ^^ print as uppercase hexadecimal
|
||||||
str::from_utf8(&*packet).expect("response is not valid UTF-8"),
|
str::from_utf8(&*packet).expect("response is not valid UTF-8"),
|
||||||
|
|
|
@ -26,7 +26,7 @@ fn main() -> ! {
|
||||||
// let msg: &[u8; 5] = &[b'H', b'e', b'l', b'l', b'o'];
|
// let msg: &[u8; 5] = &[b'H', b'e', b'l', b'l', b'o'];
|
||||||
// let msg: &[u8; 5] = b"Hello";
|
// let msg: &[u8; 5] = b"Hello";
|
||||||
|
|
||||||
defmt::info!(
|
defmt::println!(
|
||||||
"sending: {}",
|
"sending: {}",
|
||||||
str::from_utf8(msg).expect("msg is not valid UTF-8 data")
|
str::from_utf8(msg).expect("msg is not valid UTF-8 data")
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,7 +22,7 @@ fn main() -> ! {
|
||||||
fn fib(n: u32) -> u32 {
|
fn fib(n: u32) -> u32 {
|
||||||
// allocate and initialize one kilobyte of stack memory to provoke stack overflow
|
// allocate and initialize one kilobyte of stack memory to provoke stack overflow
|
||||||
let use_stack = [0xAA; 1024];
|
let use_stack = [0xAA; 1024];
|
||||||
defmt::info!("allocating [{}; 1024]; round #{}", use_stack[1023], n);
|
defmt::println!("allocating [{}; 1024]; round #{}", use_stack[1023], n);
|
||||||
|
|
||||||
if n < 2 {
|
if n < 2 {
|
||||||
1
|
1
|
||||||
|
|
|
@ -14,7 +14,8 @@ use cortex_m::{asm, peripheral::NVIC};
|
||||||
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
|
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
|
||||||
#[cfg(feature = "beginner")]
|
#[cfg(feature = "beginner")]
|
||||||
pub use hal::ieee802154;
|
pub use hal::ieee802154;
|
||||||
pub use hal::pac::{interrupt, Interrupt, NVIC_PRIO_BITS, RTC0};
|
#[doc(hidden)]
|
||||||
|
pub use hal::pac::{Peripherals, interrupt, Interrupt, NVIC_PRIO_BITS, RTC0};
|
||||||
use hal::{
|
use hal::{
|
||||||
clocks::{self, Clocks},
|
clocks::{self, Clocks},
|
||||||
gpio::{p0, Level, Output, Pin, Port, PushPull},
|
gpio::{p0, Level, Output, Pin, Port, PushPull},
|
||||||
|
@ -289,7 +290,7 @@ fn RTC0() {
|
||||||
/// Exits the application and prints a backtrace when the program is executed through the `probe-run`
|
/// Exits the application and prints a backtrace when the program is executed through the `probe-run`
|
||||||
/// Cargo runner
|
/// Cargo runner
|
||||||
pub fn exit() -> ! {
|
pub fn exit() -> ! {
|
||||||
defmt::info!("`dk::exit()` called; exiting ...");
|
defmt::println!("`dk::exit()` called; exiting ...");
|
||||||
// force any pending memory operation to complete before the BKPT instruction that follows
|
// force any pending memory operation to complete before the BKPT instruction that follows
|
||||||
atomic::compiler_fence(Ordering::SeqCst);
|
atomic::compiler_fence(Ordering::SeqCst);
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl Ep0In {
|
||||||
|
|
||||||
self.busy = true;
|
self.busy = true;
|
||||||
|
|
||||||
defmt::info!("EP0IN: start {}B transfer", n);
|
defmt::println!("EP0IN: start {}B transfer", n);
|
||||||
|
|
||||||
// start DMA transfer
|
// start DMA transfer
|
||||||
dma_start();
|
dma_start();
|
||||||
|
@ -75,7 +75,7 @@ impl Ep0In {
|
||||||
usbd.events_ep0datadone.reset();
|
usbd.events_ep0datadone.reset();
|
||||||
|
|
||||||
self.busy = false;
|
self.busy = false;
|
||||||
defmt::info!("EP0IN: transfer done");
|
defmt::println!("EP0IN: transfer done");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ pub fn init(power: POWER, usbd: &USBD) {
|
||||||
// wait until the USB cable has been connected
|
// wait until the USB cable has been connected
|
||||||
while power.events_usbdetected.read().bits() == 0 {
|
while power.events_usbdetected.read().bits() == 0 {
|
||||||
if once {
|
if once {
|
||||||
defmt::info!("waiting for USB connection on port J3");
|
defmt::println!("waiting for USB connection on port J3");
|
||||||
once = false;
|
once = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ Inside the 'configuration 1' rectangle there are two rectangles labeled 'control
|
||||||
Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints.">
|
Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints.">
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
An interface is closest to a USB device's function. For example, a USB mouse may expose a single HID (Human Interface Device) interface to report user input to the host. USB devices can expose multiple interfaces within a configuration. For example, the nRF52840 Dongle could expose both a CDC ACM interface (AKA virtual serial port) *and* a HID interface; the first interface could be used for (`defmt::info!`-style) logs; and the second one could provide a RPC (Remote Procedure Call) interface to the host for controlling the nRF52840's radio.
|
An interface is closest to a USB device's function. For example, a USB mouse may expose a single HID (Human Interface Device) interface to report user input to the host. USB devices can expose multiple interfaces within a configuration. For example, the nRF52840 Dongle could expose both a CDC ACM interface (AKA virtual serial port) *and* a HID interface; the first interface could be used for (`defmt::println!`-style) logs; and the second one could provide a RPC (Remote Procedure Call) interface to the host for controlling the nRF52840's radio.
|
||||||
|
|
||||||
An interface is made up of one or more *endpoints*. To give an example, a HID interface can use two (interrupt) endpoints, one IN and one OUT, for bidirectional communication with the host. A single endpoint cannot be used by more than one interface with the exception of the special "endpoint 0", which can be (and usually is) shared by all interfaces.
|
An interface is made up of one or more *endpoints*. To give an example, a HID interface can use two (interrupt) endpoints, one IN and one OUT, for bidirectional communication with the host. A single endpoint cannot be used by more than one interface with the exception of the special "endpoint 0", which can be (and usually is) shared by all interfaces.
|
||||||
|
|
||||||
|
|
|
@ -32,11 +32,11 @@ let array1: [u8; 3] = [0, 1, 2];
|
||||||
let array2: [u8; 4] = [0, 1, 2, 3];
|
let array2: [u8; 4] = [0, 1, 2, 3];
|
||||||
|
|
||||||
let mut slice: &[u8] = &array1;
|
let mut slice: &[u8] = &array1;
|
||||||
defmt::info!("{:?}", slice); // length = 3
|
defmt::println!("{:?}", slice); // length = 3
|
||||||
|
|
||||||
// now point to the other array
|
// now point to the other array
|
||||||
slice = &array2;
|
slice = &array2;
|
||||||
defmt::info!("{:?}", slice); // length = 4
|
defmt::println!("{:?}", slice); // length = 4
|
||||||
```
|
```
|
||||||
|
|
||||||
## Byte literals
|
## Byte literals
|
||||||
|
@ -82,8 +82,8 @@ On the other hand, `"Hello"` is a string literal with type `&str`. `str` strings
|
||||||
|
|
||||||
In this workshop we'll work with ASCII strings so byte string literals that contain no escaped characters are OK to use as packet payloads.
|
In this workshop we'll work with ASCII strings so byte string literals that contain no escaped characters are OK to use as packet payloads.
|
||||||
|
|
||||||
You'll note that `defmt::info!("{:?}", b"Hello")` will print `[72, 101, 108, 108, 111]` rather than `"Hello"` and that the `{}` format specifier (`Display`) does not work. This is because the type of the literal is `&[u8; N]` and in Rust this type means "bytes"; those bytes could be ASCII data, UTF-8 data or something else.
|
You'll note that `defmt::println!("{:?}", b"Hello")` will print `[72, 101, 108, 108, 111]` rather than `"Hello"` and that the `{}` format specifier (`Display`) does not work. This is because the type of the literal is `&[u8; N]` and in Rust this type means "bytes"; those bytes could be ASCII data, UTF-8 data or something else.
|
||||||
|
|
||||||
To print this you'll need to convert the slice `&[u8]` into a string (`&str`) using the `str::from_utf8` function. This function will verify that the slice contains well formed UTF-8 data and interpret it as a UTF-8 string (`&str`). As long as we use ASCII data (printable ASCII characters) this conversion will not fail.
|
To print this you'll need to convert the slice `&[u8]` into a string (`&str`) using the `str::from_utf8` function. This function will verify that the slice contains well formed UTF-8 data and interpret it as a UTF-8 string (`&str`). As long as we use ASCII data (printable ASCII characters) this conversion will not fail.
|
||||||
|
|
||||||
Something similar will happen with byte literals: `defmt::info!("{}", b'A')` will print `65` rather than `A`. To get the `A` output you can cast the byte literal (`u8` value) to the `char` type: `defmt::info!("{}", b'A' as char)`.
|
Something similar will happen with byte literals: `defmt::println!("{}", b'A')` will print `65` rather than `A`. To get the `A` output you can cast the byte literal (`u8` value) to the `char` type: `defmt::println!("{}", b'A' as char)`.
|
|
@ -56,7 +56,7 @@ fn main() -> ! {
|
||||||
|
|
||||||
for plainletter in 0..=127 {
|
for plainletter in 0..=127 {
|
||||||
/* ... send letter to dongle ... */
|
/* ... send letter to dongle ... */
|
||||||
defmt::info!("got response");
|
defmt::println!("got response");
|
||||||
/* ... store output ... */
|
/* ... store output ... */
|
||||||
|
|
||||||
timer.wait(Duration::from_millis(20));
|
timer.wait(Duration::from_millis(20));
|
||||||
|
|
|
@ -12,4 +12,4 @@ The other time related API exposed by the `dk` HAL is the `dk::uptime` function.
|
||||||
|
|
||||||
✅ Try changing the `Duration` value passed to `Timer.wait`. Try values larger than one second and smaller than one second. What values of `Duration` make the blinking imperceptible?
|
✅ Try changing the `Duration` value passed to `Timer.wait`. Try values larger than one second and smaller than one second. What values of `Duration` make the blinking imperceptible?
|
||||||
|
|
||||||
❗If you set the duration to below 2ms, try removing the `defmt::info!` command in the loop. Too much logging will fill the logging buffer and cause the loop to slow down, resulting in the blink frequency to reduce after a while.
|
❗If you set the duration to below 2ms, try removing the `defmt::println!` command in the loop. Too much logging will fill the logging buffer and cause the loop to slow down, resulting in the blink frequency to reduce after a while.
|
||||||
|
|
Loading…
Reference in a new issue