usbd-hid
usbd-hid copied to clipboard
Example SystemControlReport code
Hi Guys
I would like to write a small application using SystemControlReport's to put a PC to sleep or wake it up. Is there any example code that shows how this could be done?
My initial attempts to use SystemControlReport's does not seem to do anything (tested on Mac, Windows 10 and Linux!)
The base code has been modified from the twitching usb mouse example (which is working fine).
pseudo code is:
let usb_hid = HIDClass::new(bus_ref, SystemControlReport::desc(), 60);
let usb_dev = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27da))
.manufacturer("My_Comany")
.product("My_Product")
.serial_number("0000")
.device_class(0)
.build();
let report = SystemControlReport {
usage_id: SystemControlKey::Sleep.into()
}
critical_section::with(|_| unsafe {
USB_HID.as_mut().map(|hid| hid.push_input(&report))
}).unwrap()
The sending of the sleep is triggered by a GPIO pin, and I can see that the report is being sent, with the return value being 1, which I believe means that one byte was successfully sent (which is the size of the SystemControlKey event.
Any ideas? Or any pointers to functional example code?
Hi Guys
Thought I would let you know that I found the solution to my problems. Down to user error as these things mostly are.
I changed the way I construct the usage_id by doing something like this:
[gen_hid_descriptor(
(collection = APPLICATION, usage_page = GENERIC_DESKTOP, usage = SYSTEM_CONTROL) = {
(usage_min = 0x81, usage_max = 0x8f, logical_min = 1) = {
#[item_settings data,array,absolute,not_null] usage_id=input;
};
}
)]
#[derive(Default)]
pub struct SystemControlReport {
pub usage_id: u16,
}
impl SystemControlReport {
pub fn new() -> Self {
Self {
..Default::default()
}
}
pub fn set(&mut self, control: SystemControlKey) {
// Convert the control key into a u8 value
let val: u8 = control.into();
let min: u8 = SystemControlKey::PowerDown.into();
self.usage_id |= 1 << (val - min);
}
}
This sets the correct bit in the usage_id through the set function, the only difference from the upstream code is that I am using a u16 for usage_id and you have a u8. I believe there are 15 control codes (0x81 to 0x8f), so you might want to consider changing upstream.
Final example pseudo code would be
// Generate an empty report
let mut report = SystemControlReport::new();
// Set the controls needed
report.set(SystemControlKey::Sleep);
// Send the report
critical_section::with(|_| unsafe {
USB_HID.as_mut().map(|hid| hid.push_input(&report))
}).unwrap()
Hi Community
I have had limited success with this so far. I am reliably able to set the host into sleep mode, but I am not able to wake any up from sleep. Looking further, all devices return false when querying usb_dev.remote_wakeup_enabled()
.
A question to you - is this something that needs to be configured host side (such as here or here, or do I need to configure something additional device side?
I ask because when on Linux (Fedora 37), I cannot see a wakeup node under /sys/bus/usb/devices/<my-device>/power/wakeup
Any help would be greatly appreciated.
Remote wakeup requires support of the mcu you are working with to function correctly.
Which MCU are you using? I've implemented this in the past for both mk20dx256 (in C) and in rust for atsam4-hal. https://github.com/atsam-rs/atsam4-hal/blob/master/src/udp/bus.rs#L180
Thanks for the response.
I am using the Seeed studio xiao rp2040, with the rp_pico as the HAL.
Are you aware of support for the rp2040?
Ok, so I updated the rp-pico hal to the latest version, which pulls in the latest rp2040-hal - and on first attempt it bought my dev machine out of sleep (Linux/Fedora 37). Will try now on the other systems.
Thanks for your help @haata
Think I spoke too soon... Needs more investigation.
Is there a proper time to call remote_wakeup() - is there an example solution available online?
I did some quick poking around and I think this is what you want https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/usb.rs#L474
In the rp2040 datasheet (https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf page 401), you can find USB: SIE_CTRL Register
.
That's the special register to initiate the remote wakeup. You only want to call this if the device is in UsbDeviceState::Suspend (similar to what I'm doing here https://github.com/kiibohd/kiibohd-firmware/blob/main/common/atsam4s/src/lib.rs#L407).
Thanks for the pointers again @haata.
Any ideas why usb_dev.remote_wakeup_enabled();
is always returning false - no matter what system I plug my device into?
Have you set supports_remote_wakeup(true)
?
https://github.com/rust-embedded-community/usb-device/blob/master/src/device_builder.rs#L86
For example https://github.com/kiibohd/kiibohd-firmware/blob/main/common/atsam4s/src/lib.rs#L300
That was the missing piece of the puzzle. I is working now thanks. Your help with this has been amazing.
One interesting observation though, with respect to remote_wakeup_enabled
,
OS | remote_wakeup_enabled returns | remote wakeup works? |
---|---|---|
Linux | Always false | Yes |
MacOS | Always true | Yes |
Windows 10 | True when in sleep mode | Yes |
Is this to be expected?
that function just returns
/// Gets whether host remote wakeup has been enabled by the host.
pub fn remote_wakeup_enabled(&self) -> bool {
self.remote_wakeup_enabled
}
Which is only set
- https://github.com/rust-embedded-community/usb-device/blob/master/src/device.rs#L385
- https://github.com/rust-embedded-community/usb-device/blob/master/src/device.rs#L400
It looks like the USB stack for Linux isn't setting the feature for some reason (probably a bug in Linux that should get fixed) and Windows is probably doing what it's supposed to be doing. But this may require some studying of the USB spec to find the correct answer (or it may actually not be specified...in which case both MacOS and Windows 10 might be correct).
I took a brief look at http://janaxelson.com/usbc.htm and couldn't find what the behaviour is supposed to be (great book).
You're probably good, but I would recommend running this tool on Windows 10 https://www.usb.org/compliancetools#anchor_usb3cv (yes you want to run xhci as you likely have a usb 3.x chipset). This tool will also catch bad hid descriptors among other things (you don't need a 100% pass, but it's a good way to figure out where you stand). Sadly this tool only works from Windows machines (VMs don't work well) so I keep an old laptop around just for this.