Merge branch 'master' into tie-loose-ends

This commit is contained in:
Jorge Aparicio 2020-07-14 09:07:08 +00:00 committed by GitHub
commit a61f676b9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 2419 additions and 1930 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
// f6c27c0a5af464795d1a64c45fa1b3039f44a62a
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::fmt::Write as _;
use core::{convert::TryFrom, fmt::Write as _};
use async_core::unsync::Mutex;
use hal::{
radio::{self, Channel, Packet},
radio::{self, Channel},
usbd,
};
use heapless::{consts, String};
@ -14,17 +14,67 @@ use panic_abort as _;
#[no_mangle]
fn main() -> ! {
let mut stx = usbd::serial();
let (mut rtx, mut rrx) = radio::claim(Channel::_20);
let mut output = String::::new();
let stx = Mutex::new(usbd::serial());
let (mut hidout, _) = usbd::hid();
let (rtx, mut rrx) = radio::claim(Channel::_20);
let mut output = String::<consts::U128>::new();
output.push_str("deviceid=").ok();
write!(output, "{:08x}{:08x}", hal::deviceid1(), hal::deviceid0()).ok();
write!(output, " channel={} TxPower=+8dBm app=loopback.hex\n", rtx.channel()).ok();
write!(
output,
" channel={} TxPower=+8dBm app=loopback.hex\n",
rtx.channel()
)
.ok();
let task = async {
let mut packet = Packet::new().await;
stx.write(output.as_bytes());
let rtx = Mutex::new(rtx);
let t1 = async {
let mut output = String::<consts::U128>::new();
let mut hidbuf = usbd::Packet::new().await;
let zlp = radio::Packet::new().await;
loop {
hidout.recv(&mut hidbuf).await;
semidap::info!("HID: {}", *hidbuf);
let arg = if hidbuf.len() == 1 {
// Linux / macOS
Some(hidbuf[0])
} else if hidbuf.len() == 64 {
// Windows (it zero pads the packet)
Some(hidbuf[0])
} else {
None
};
if let Some(arg) = arg {
if let Ok(chan) = Channel::try_from(arg) {
let mut rtx = rtx.lock().await;
rtx.set_channel(chan);
// send a zero-length packet to force the radio to listen on the new channel
rtx.write(&zlp).await.ok();
drop(rtx);
output.clear();
writeln!(output, "now listening on channel {}", chan).ok();
stx.lock().await.write(output.as_bytes());
} else {
stx.lock()
.await
.write(b"requested channel is out of range (11-26)\n");
}
} else {
stx.lock().await.write(b"invalid HID packet\n");
}
}
};
let t2 = async {
let mut packet = radio::Packet::new().await;
stx.lock().await.write(output.as_bytes());
loop {
let crcres = rrx.read(&mut packet).await;
@ -39,7 +89,7 @@ fn main() -> ! {
let mut busy = false;
if crcres.is_ok() {
packet.reverse();
busy = rtx.write(&packet).await.is_err();
busy = rtx.lock().await.write(&packet).await.is_err();
}
output.clear();
@ -64,12 +114,12 @@ fn main() -> ! {
if busy {
output.push_str("didn't reply -- channel was busy\n").ok();
stx.write(output.as_bytes());
stx.lock().await.write(output.as_bytes());
}
stx.write(output.as_bytes());
stx.lock().await.write(output.as_bytes());
}
};
executor::run!(task)
executor::run!(t1, t2)
}

1960
boards/dongle/puzzle.hex Executable file → Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
// f6c27c0a5af464795d1a64c45fa1b3039f44a62a
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::fmt::Write as _;
use core::{fmt::Write as _, convert::TryFrom};
use async_core::unsync::Mutex;
use hal::{radio::{self, Packet, Channel}, usbd, led};
use heapless::{consts, LinearMap, String};
use panic_abort as _;
@ -25,8 +25,9 @@ fn main() -> ! {
// so we can visually differentiate this one from `loopback.hex`
led::Green.on();
let mut stx = usbd::serial();
let (mut rtx, mut rrx) = radio::claim(Channel::_25);
let stx = Mutex::new(usbd::serial());
let (mut hidout, _) = usbd::hid();
let (rtx, mut rrx) = radio::claim(Channel::_25);
let mut output = String::<consts::U128>::new();
let mut dict = LinearMap::<_, _, consts::U128>::new();
@ -38,9 +39,52 @@ fn main() -> ! {
write!(output, "{:08x}{:08x}", hal::deviceid1(), hal::deviceid0()).ok();
write!(output, " channel={} TxPower=+8dBm app=puzzle.hex\n", rtx.channel()).ok();
let task = async {
let rtx = Mutex::new(rtx);
let t1 = async {
let mut output = String::<consts::U128>::new();
let mut hidbuf = usbd::Packet::new().await;
let zlp = radio::Packet::new().await;
loop {
hidout.recv(&mut hidbuf).await;
semidap::info!("HID: {}", *hidbuf);
let arg = if hidbuf.len() == 1 {
// Linux / macOS
Some(hidbuf[0])
} else if hidbuf.len() == 64 {
// Windows (it zero pads the packet)
Some(hidbuf[0])
} else {
None
};
if let Some(arg) = arg {
if let Ok(chan) = Channel::try_from(arg) {
let mut rtx = rtx.lock().await;
rtx.set_channel(chan);
// send a zero-length packet to force the radio to listen on the new channel
rtx.write(&zlp).await.ok();
drop(rtx);
output.clear();
writeln!(output, "now listening on channel {}", chan).ok();
stx.lock().await.write(output.as_bytes());
} else {
stx.lock()
.await
.write(b"requested channel is out of range (11-26)\n");
}
} else {
stx.lock().await.write(b"invalid HID packet\n");
}
}
};
let t2 = async {
let mut packet = Packet::new().await;
stx.write(output.as_bytes());
stx.lock().await.write(output.as_bytes());
loop {
let crcres = rrx.read(&mut packet).await;
@ -76,7 +120,7 @@ fn main() -> ! {
});
}
busy = rtx.write(&packet).await.is_err();
busy = rtx.lock().await.write(&packet).await.is_err();
}
output.clear();
@ -101,12 +145,12 @@ fn main() -> ! {
if busy {
output.push_str("didn't reply -- channel was busy\n").ok();
stx.write(output.as_bytes());
stx.lock().await.write(output.as_bytes());
}
stx.write(output.as_bytes());
stx.lock().await.write(output.as_bytes());
}
};
executor::run!(task)
executor::run!(t1, t2)
}

8
common/pids/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
name = "pids"
version = "0.0.0"
[dependencies]

4
common/pids/src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
#![no_std]
pub const LOOPBACK: u16 = 0x0309;
pub const PUZZLE: u16 = 0x0310;

View file

@ -5,6 +5,7 @@
- [Beginner Workbook](./beginner-workbook.md)
- [Parts of an Embedded Program](./parts-embedded-program.md)
- [Building an Embedded Program](./building-program.md)
- [Binary Size](./binary-size.md)
- [Flashing the Program](./flashing-program.md)
- [Viewing Logs](./viewing-logs.md)
- [Running the Program from VS Code](./running-from-vsc.md)
@ -46,3 +47,9 @@
- [Getting it Configured](./getting-device-configured.md)
- [Next Steps](./advanced-next-steps.md)
- [References and Resources](./references-resources.md)
- [Troubleshooting](./troubleshooting.md)
- [`cargo-size` is not working](./troubleshoot-cargo-size.md)
- [`cargo-flash` is not working](./troubleshoot-cargo-flash.md)
- [Rust analyzer is not working](./troubleshoot-rust-analyzer.md)
- [`cargo-build` fails to link](./troubleshoot-cargo-build.md)
- [`dongle-flash` is not working](./troubleshoot-dongle-flash.md)

View file

@ -0,0 +1,50 @@
# Binary Size
ELF files contain metadata like debug information so their size on disk is not a good indication of the amount of Flash the program will use once it's loaded on the target device's memory.
To display the amount of Flash the program will occupy on the target device use the `cargo-size` tool, which is part of the `cargo-binutils` package.
✅ Use the following command to print the binary's size in system V format.
``` console
$ cargo size --bin hello -- -A
```
Expected output:
The breakdown of the program's static memory usage per *linker section*.
``` console
hello :
section size addr
.vector_table 256 0x0
.text 9740 0x100
.rodata 4568 0x270c
.data 8 0x20000000
.bss 2124 0x20000008
.uninit 0 0x20000854
```
**🔎 More details about each linker section:**
The first three sections are contiguously located in Flash memory -- Flash memory spans from address `0x0000_0000` to `0x0010_0000` (1 MB).
* The `.vector_table` section contains the *vector table*, a data structure required by the Cortex-M ISA.
* The `.text` section contains the instructions the program will execute.
* The `.rodata` section contains constants like strings literals.
The next three sections, `.data`, `.bss` and `.uninit`, are located in RAM -- RAM memory spans the address range `0x2000_0000` - `0x2004_0000` (256 KB). These sections contain statically allocated variables (`static` variables).
Another other useful tool to analyze the binary size of a program is `cargo-bloat`:
``` console
$ cargo bloat --bin hello
File .text Size Crate Name
0.7% 13.5% 1.3KiB std <char as core::fmt::Debug>::fmt
0.5% 9.6% 928B hello hello::__cortex_m_rt_main
0.4% 8.4% 804B std core::str::slice_error_fail
0.4% 8.0% 768B std core::fmt::Formatter::pad
0.3% 6.4% 614B std core::fmt::num::<impl core::fmt::Debug for usize>::fmt
(..)
5.1% 100.0% 9.4KiB .text section size, the file size is 184.5KiB
```
This breakdowns the size of the `.text` section by function. This breakdown can be used to identify the largest functions in the program; those could then be modified to make them smaller.

View file

@ -1,12 +1,6 @@
# Building an Embedded Program
The following command cross compiles the program to the ARM Cortex-M4 architecture.
``` console
$ cargo build --bin hello
```
The default in a Cargo project is to compile for the host (native compilation) but the `beginner/apps` project has been configured for cross compilation. This configuration can be seen in the Cargo configuration file (`.cargo/config`):
The default in a Cargo project is to compile for the host (native compilation). The `beginner/apps` project has been configured for cross compilation to the ARM Cortex-M4 architecture. This configuration can be seen in the Cargo configuration file (`.cargo/config`):
``` text
# .cargo/config
@ -14,49 +8,18 @@ The default in a Cargo project is to compile for the host (native compilation) b
target = "thumbv7em-none-eabi" # = ARM Cortex-M4
```
✅ Inside the folder `beginner/apps`, use the following command to cross compile the program to the ARM Cortex-M4 architecture.
``` console
$ cargo build --bin hello
```
The output of the compilation process will be an ELF (Executable and Linkable Format) file. The file will be placed in the `target/thumbv7em-none-eabi` directory.
✅ Run `$ file target/thumbv7em-none-eabi/debug/hello` and compare if your output is as expected.
Expected output:
``` console
$ file target/thumbv7em-none-eabi/debug/hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
```
## Binary size
ELF files contain metadata like debug information so their size on disk is not a good indication of the amount of Flash the program will use once it's loaded on the target device's memory.
To display the amount of Flash the program will occupy on the target device use the `cargo-size` tool (part of the `cargo-binutils` package):
``` console
$ cargo size --bin hello -- -A
hello :
section size addr
.vector_table 256 0x0
.text 9740 0x100
.rodata 4568 0x270c
.data 8 0x20000000
.bss 2124 0x20000008
.uninit 0 0x20000854
```
This gives you a breakdown of the program's static memory usage per *linker section*.
The `.vector_table` section contains the *vector table*, a data structure required by the Cortex-M ISA. The `.text` section contains the instructions the program will execute. The `.rodata` section contains constants like strings literals. These three sections are contiguously located in Flash memory -- Flash memory spans from address `0x0000_0000` to `0x0010_0000` (1 MB).
The next three sections, `.data`, `.bss` and `.uninit`, are located in RAM -- RAM memory spans the address range `0x2000_0000` - `0x2004_0000` (256 KB). These sections contain statically allocated variables (`static` variables).
Another other useful tool to analyze the binary size of a program is `cargo-bloat`:
``` console
$ cargo bloat --bin hello
File .text Size Crate Name
0.7% 13.5% 1.3KiB std <char as core::fmt::Debug>::fmt
0.5% 9.6% 928B hello hello::__cortex_m_rt_main
0.4% 8.4% 804B std core::str::slice_error_fail
0.4% 8.0% 768B std core::fmt::Formatter::pad
0.3% 6.4% 614B std core::fmt::num::<impl core::fmt::Debug for usize>::fmt
(..)
5.1% 100.0% 9.4KiB .text section size, the file size is 184.5KiB
```
This breakdowns the size of the `.text` section by function. This breakdown can be used to identify the largest functions in the program; those could then be modified to make them smaller.
```

View file

@ -8,7 +8,7 @@ The `Ep0In` API has two methods: `start` and `end` (also see their API documenta
To implement responding to a GET_DESCRIPTOR request, do the following:
1. **Extend the parser implementation to handle a GET_DESCRIPTOR request:** make the `common/usb/lib.rs::get_descriptor_configuration()` test run successfully.
1. **Extend the GET_DESCRIPTOR parser implementation to handle a CONFIGURATION request:** make the `common/usb/lib.rs::get_descriptor_configuration()` test run successfully.
2. **Answer the Descriptor Request:** extend `usb-3.rs` so that it uses `Ep0In` to respond to the `GET_DESCRIPTOR Device` request (and only to that request). The response must be a device descriptor with its fields set to these values:
- `bDeviceClass = bDeviceSubClass = bDeviceProtocol = 0`, these are unimportant for enumeration

View file

@ -57,9 +57,33 @@ If you run the `serial-term` application you should see the following output:
``` console
$ serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex
(..)
```
This line is printed by the `loopback` app on boot. It contains the device ID of the dongle, a 64-bit unique identifier (so everyone will see a different number); the radio channel that the device will use to communicate; and the transmission power of the radio in dBm.
At this point you should *not* get more output from `serial-term`. If you get "received N bytes" lines in output like this:
``` console
$ serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm
received 7 bytes (CRC=Ok(0x2459), LQI=0)
received 5 bytes (CRC=Ok(0xdad9), LQI=0)
received 6 bytes (CRC=Ok(0x72bb), LQI=0)
```
That means the device is observing interference traffic, likely from 2.4 GHz WiFi or Bluetooth. In this scenario you should switch the listening channel to one where you don't observe interference. Use the `tools/change-channel` tool to do this. The tool takes a single argument: the new listening channel which must be in the range 11-26.
``` console
$ change-channel 11
requested channel change to channel 11
```
Then you should see new output from `serial-term`:
``` console
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm
(..)
now listening on channel 11
```
Leave the Dongle connected and the `serial-term` application running. Now we'll switch back to the Development Kit.

View file

@ -1,12 +1,12 @@
# Flashing the Program
The following command will flash the ELF file to the device.
✅ Use the following command to flash the ELF file to the device.
``` console
$ cargo flash --chip nRF52840_xxAA --elf target/thumbv7em-none-eabi/debug/hello
```
> NOTE if you run into an error along the lines of "Debug power request failed" retry the operation and the error should disappear
> NOTE: If you run into an error along the lines of "Debug power request failed" retry the operation and the error should disappear.
Alternatively you can run this command, which builds the application before flashing it.
@ -16,4 +16,6 @@ $ cargo flash --chip nRF52840_xxAA --bin hello
The `cargo-flash` subcommand flashes and runs the program but won't display logs. It is a deployment tool.
**🔎 How does flashing work?**
The flashing process consists of the PC communicating with a second microcontroller on the nRF52840 DK over USB (J2 port). This second microcontroller, named J-Link, is connected to the nRF52840 through a electrical interface known as JTAG. The JTAG protocol specifies procedures for reading memory, writing to memory, halting the target processor, reading the target processor registers, etc.

View file

@ -1,26 +1,30 @@
# Parts of an Embedded Program
[❗Intro ]
Open the `beginner/apps` folder in VS Code.
We will look at the elements that distinguish an embedded Rust program from a desktop program.
✅ Open the `beginner/apps` folder in VS Code.
``` console
$ # or use "File > Open Folder" in VS Code
$ code beginner/apps
```
Then open the `src/bin/hello.rs` file.
Then open the `src/bin/hello.rs` file.
If you find it more convenient to open the repository at the root then please also add the `beginner/apps` folder to the VS Code workspace: right click the left side panel, select "Add folder to workspace" and add the `beginner/apps` folder.
[❗No optional way of doing things here]
Note the differences between this embedded program and a desktop program:
## In the file, you will find the following attributes:
[❗Have a non embedded program up to compare]
### `#![no_std]`
The `#![no_std]` attribute indicates that the program will not make use of the standard library, `std` crate. Instead it will use the `core` library, a subset of the standard library that does depend not on a underlying operating system (OS).
The `#![no_std]` attribute indicates that the program will not make use of the standard library, the `std` crate. Instead it will use the `core` library, a subset of the standard library that does not depend on an underlying operating system (OS).
### `#![no_main]`
The `#![no_main]` attribute indicates that the program will use a custom entry point instead of the default `fn main() { .. }` one.
The `#[entry]` attribute declares the custom entry point of the program. The entry point must be a divergent function; note that the return type is the never type `!`. The function is not allowed to return; therefore the program is not allowed to terminate.
### `#[entry]`
The `#[entry]` attribute declares the custom entry point of the program. The entry point must be a divergent function whose return type is the never type `!`. The function is not allowed to return; therefore the program is not allowed to terminate.

View file

@ -0,0 +1,3 @@
# `cargo-build` fails to link
If you have configured Cargo to use sccache then you'll need to disable sccache support. Unset the `RUSTC_WRAPPER` variable in your environment *before* opening VS code. Run `cargo clean` from the Cargo workspace you are working from (`beginner/apps` or `advanced/firmware`). Then open VS code.

View file

@ -0,0 +1,45 @@
# `cargo-flash` is not working
## Debug power request failed
``` console
$ cargo flash --chip nRF52840_xxAA --bin hello
ERROR probe_rs::architecture::arm::communication_interface > Debug power request failed
Error processing command: An error specific to the selected architecture occured
```
This is a spurious error that occurs only once on a new development kit. Running the command again should make the error go away. If you still get the error run `RUST_LOG=probe_rs=debug cargo flash --chip nRF52840_xxAA --bin hello ` once.
## 'erase_sector' failed with code 1
``` console
$ cargo flash --chip nRF52840_xxAA --bin hello
(..)
Error failed to flash app: The execution of 'erase_sector' failed with code 1
```
flash write protection is enabled in the device. To disable it use the `nrf-recover` tool. Instructions can be found in the [setup page](https://oxidizeconf.com/oxidize-global-setup/) and in the top-level README of this repository.
## Linux permissions
``` console
$ cargo flash --chip nRF52840_xxAA --bin hello
Error: An error specific to a probe type occured: USB error while opening USB device: Access denied (insufficient permissions)
Caused by:
USB error while opening USB device: Access denied (insufficient permissions)
```
udev rules need to be changed to allow non-root access. Instructions can be found in the [setup page](https://oxidizeconf.com/oxidize-global-setup/) and in the top-level README of this repository.
## Wrong Windows Driver
``` console
$ cargo flash --chip nRF52840_xxAA --bin hello
Error: An error specific to a probe type occured: USB error while opening USB device: Entity not found
Caused by:
USB error while opening USB device: Entity not found
```
You need to bind the BULK interface of the J-Link USB device to the WinUSB driver using the Zadig tool. Instructions can be found in the [setup page](https://oxidizeconf.com/oxidize-global-setup/) and in the top-level README of this repository.

View file

@ -0,0 +1,9 @@
# `cargo-size` is not working
```
$ cargo size --bin hello
Failed to execute tool: size
No such file or directory (os error 2)
```
`llvm-tools-preview` is not installed. Install it with `rustup component add llvm-tools-preview`

View file

@ -0,0 +1,9 @@
# `dongle-flash` not working
``` console
$ dongle-flash loopback.hex
packaging iHex using nrfutil ...
Error: No such file or directory (os error 2)
```
this indicates that `nrfutil`, the Python tool, is not installed or not available in your PATH. Instructions on how to install `nrfutil` can be found in the [setup page](https://oxidizeconf.com/oxidize-global-setup/) and in the top-level README of this repository. If you install `nrfutil` in a virtual environment you'll need to activate the environment; the `nrfutil` binary must be available in your PATH.

View file

@ -0,0 +1,9 @@
# Rust-Analyzer is not working
If Rust-Analyzer is not analyzing your code, that is you get no type annotations, no "Run" button and no syntax hightlighting then:
- check that you have a single folder open in VS code; this is different from a single-folder VS code workspace. First close all the currently open folders then open a single folder using the 'File > Open Folder' menu. The open folder should be the `beginner/apps` folder for the beginner workshop or the `advanced/firmware` folder for the advanced workshop.
- use the latest version of the Rust-Analyzer plugin. If you get a prompt to update the Rust-Analyzer extension when you start VS code accept it. You may also get a prompt about updating the Rust-Analayzer binary; accept that one too. The extension should restart automatically after the update. If it doesn't then close and re-open VS code.
- You may need to wait a little while Rust-Analyzer analyzes all the crates in the dependency graph. Then you may need to modify and save the currently open file to force Rust-Analyzer to analyze it.

View file

@ -0,0 +1,3 @@
# Troubleshooting
If you have issues with any of the tools used in this workshop check out the sections in this chapter.

25
tools/Cargo.lock generated
View file

@ -122,9 +122,19 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "change-channel"
version = "0.0.0"
dependencies = [
"anyhow",
"consts",
"hidapi",
"pids",
]
[[package]]
name = "consts"
version = "0.1.0"
version = "0.0.0"
[[package]]
name = "crc32fast"
@ -158,7 +168,7 @@ dependencies = [
[[package]]
name = "dk-run"
version = "0.1.0"
version = "0.0.0"
dependencies = [
"anyhow",
"arrayref",
@ -174,7 +184,7 @@ dependencies = [
[[package]]
name = "dongle-flash"
version = "0.1.0"
version = "0.0.0"
dependencies = [
"anyhow",
"ihex",
@ -516,6 +526,10 @@ dependencies = [
"wasmparser",
]
[[package]]
name = "pids"
version = "0.0.0"
[[package]]
name = "pkg-config"
version = "0.3.17"
@ -768,7 +782,7 @@ dependencies = [
[[package]]
name = "serial-term"
version = "0.1.0"
version = "0.0.0"
dependencies = [
"anyhow",
"consts",
@ -932,9 +946,10 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "usb-list"
version = "0.1.0"
version = "0.0.0"
dependencies = [
"consts",
"pids",
"rusb",
]

View file

@ -1,5 +1,6 @@
[workspace]
members = [
"change-channel",
"dk-run",
"dongle-flash",
"serial-term",

View file

@ -0,0 +1,12 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
name = "change-channel"
version = "0.0.0"
[dependencies]
anyhow = "1.0.27"
consts = { path = "../../advanced/common/consts" }
hidapi = "1.2.2"
pids = { path = "../../common/pids" }

View file

@ -0,0 +1,33 @@
use std::env;
use anyhow::{anyhow, bail, ensure};
use hidapi::HidApi;
fn main() -> Result<(), anyhow::Error> {
let args = env::args()
.skip(1) // skip program name
.collect::<Vec<_>>();
ensure!(!args.is_empty(), "expected exactly one argument");
let api = HidApi::new()?;
let dev = api
.device_list()
.filter(|dev| dev.vendor_id() == consts::VID && check_pid(dev.product_id()))
.next()
.ok_or_else(|| anyhow!("device not found"))?
.open_device(&api)?;
let chan = args[0].parse::<u8>()?;
if chan < 11 || chan > 26 {
bail!("channel is out of range (`11..=26`)")
}
const REPORT_ID: u8 = 0;
dev.write(&[REPORT_ID, chan])?;
println!("requested channel change to channel {}", chan);
Ok(())
}
fn check_pid(pid: u16) -> bool {
pid == pids::LOOPBACK || pid == pids::PUZZLE
}

View file

@ -7,4 +7,5 @@ version = "0.0.0"
[dependencies]
consts = { path = "../../advanced/common/consts" }
pids = { path = "../../common/pids" }
rusb = "0.5.5"

View file

@ -6,8 +6,8 @@ fn main() -> Result<(), Box<dyn Error>> {
let suffix = match (desc.vendor_id(), desc.product_id()) {
(0x1366, 0x1015) => " <- J-Link on the nRF52840 Development Kit",
(0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)",
(0x2020, 0x0309) => " <- nRF52840 Dongle (loopback.hex)",
(0x2020, 0x0310) => " <- nRF52840 Dongle (puzzle.hex)",
(0x2020, pids::LOOPBACK) => " <- nRF52840 Dongle (loopback.hex)",
(0x2020, pids::PUZZLE) => " <- nRF52840 Dongle (puzzle.hex)",
(consts::VID, consts::PID) => " <- nRF52840 on the nRF52840 Development Kit",
_ => "",
};