Skip to content

initial ieee802.15.4 API#143

Merged
bors[bot] merged 22 commits intonrf-rs:masterfrom
japaric:radio
Mar 3, 2021
Merged

initial ieee802.15.4 API#143
bors[bot] merged 22 commits intonrf-rs:masterfrom
japaric:radio

Conversation

@japaric
Copy link
Copy Markdown
Contributor

@japaric japaric commented May 5, 2020

what the title says

this PR adds an ieee802154::Radio API that wraps the RADIO peripheral and puts it in IEEE 802.15.4 mode.

I haven't checked the silicon erratas of (or tested) other chips (the code contains one workaround for (nRF52840 specific?) silicon errata) so for now I have put the API directly under nrf52840-hal. But I guess the API should also work on other nRF52 devices -- I think the nRF51 chips do not have an 802.15.4 mode? And I don't know about other chip families.

The implementation is not super IEEE compliant. It's missing a backoff delay mechanism when doing CCA (Clear Channel Assessment) before transmitting packets and it's also missing IFS (inter-frame spacing) between consecutive packets (the hardware has some built-in support for this but the PR doesn't expose it). But it is sufficient to send packets around and receive them on other nRF52s running the same implementation, though it would be good to check this against some third party implementation like the DWM1001 or the MRF24J40.

There are some TODOs here and there in terms of additional things the API could expose like changing the transmit power. I'm not sure if all those need to be added before a review though. They could be added as follow up PRs too.

TODO items

  • API to change TX power (the default value doesn't go very far, like tens of centimer or so in a noisy 2.4 GHz environment)
  • API to change the start of frame delimiter (the default value matches the IEEE spec)
  • API to extract the LQI (Link Quality Indicator)
  • verify that CRC are being checked. May need to send a malformed packet from a different device (MRF24J40?)
  • "The total usable payload (PSDU) is 127 octets, but when CRC is being used, this is reduced to 125 octets of usable payload." -- adjust MAX_LEN accordingly

@thalesfragoso
Copy link
Copy Markdown
Contributor

Thanks for this work.

But I guess the API should also work on other nRF52 devices -- I think the nRF51 chips do not have an 802.15.4 mode? And I don't know about other chip families.

Both the nRF52810 and nRF52832 don't support 802.15.4 mode. 51 probably doesn't support it either.

@Yatekii
Copy link
Copy Markdown
Contributor

Yatekii commented May 5, 2020

Cool stuff! I need to test this on my boards =)

The 52840, 52811 52833 and 5340 support 802.15.4.

Are you aware of the https://crates.io/crates/ieee802154 crate? I think it is higher level (MAC) layer, but could be useful to test this and not reinvent the wheel.

Thanks for putting in the work!

@japaric
Copy link
Copy Markdown
Contributor Author

japaric commented May 6, 2020

Are you aware of the https://crates.io/crates/ieee802154 crate? I think it is higher level (MAC) layer, but could be useful to test this and not reinvent the wheel.

That's the MAC frame. Yes, that's what you should put in the payload of the Packet defined here (which is actually the PHY layer frame) if you want to be IEEE compliant.

Also, yes that would be useful to test this against the MRF24J40, which (iirc) does do HW-level MAC address filtering. Though that may end up being more of a test for the ieee802154 crate itself.

@hannobraun
Copy link
Copy Markdown
Contributor

@japaric

though it would be good to check this against some third party implementation like the DWM1001 or the MRF24J40

The DW1000 uses UWB (ultra-wideband) in the PHY layer, so I don't think it can communicate with the nRF radio (I might be wrong wrong; despite my work with the DW1000/DWM1001, I know very little about radios).

Also, yes that would be useful to test this against the MRF24J40, which (iirc) does do HW-level MAC address filtering. Though that may end up being more of a test for the ieee802154 crate itself.

The DW1000 also does hardware-based address matching and this is used in the driver. To my knowledge, this has never caused any problems with ieee802154.

@jonathanpallant
Copy link
Copy Markdown
Member

Yes, the DW1000 is a UWB PHY (so around 500 MHz bandwidth at 3 to 10 GHz depending on channel) and hence incompatible with a 2.4 GHz PHY (around 80 MHz bandwidth at 2.4 GHz) based system.

I wasn't aware the DW1000 had hardware address detection, so that's good to know!

Getting back on topic, Rust 802.15.4 on the nRF52 series is excellent. Great work :)

@blueluna
Copy link
Copy Markdown
Member

blueluna commented May 8, 2020

Nice! Looks better than my old implementation.
https://github.com/blueluna/nrf52840-dk-experiments/tree/master/nrf52-radio-802154
I hope to try it out when I get time.

@japaric
Copy link
Copy Markdown
Contributor Author

japaric commented May 11, 2020

for those not getting notifications on new commits being pushed: I have added APIs to change the TX power and the start of frame delimiter; added a method the get a packet's Link Quality Indicator; and added CRC verification to the Radio.recv method (the method now returns a Result)

japaric added 3 commits June 5, 2020 12:54
not all values in the -40..=8 range are valid configurations
this method lets you insert a delay between failed CCAs to be IEEE spec
compliant
Copy link
Copy Markdown

@hubertmis hubertmis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This driver looks nice. Based on experience with similar code in C, I've posted multiple suggestions that could be helpful to improve it.

}

/// Default Clear Channel Assessment method = Carrier sense
pub const DEFAULT_CCA: Cca = Cca::CarrierSense;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CarrierSense mode needs some calibration to work correctly. I would recommend EnergyDetection mode as a default one.

Comment thread nrf52840-hal/src/ieee802154.rs Outdated
/// IEEE 802.15.4 channels
///
/// NOTE these are NOT the same as WiFi 2.4 GHz channels
// TODO it is possible to use non-standard frequencies below 2_400 MHz; should those be exposed too?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list is correct. To be compliant with IEEE 802.15.4 other frequencies shall not be exposed. The hardware in selected nRF devices is capable of using other frequencies, but it should not be used in 802.15.4 protocol.

Comment thread nrf52840-hal/src/ieee802154.rs Outdated

radio.radio.pcnf1.write(|w| {
w.maxlen()
.bits(Packet::MAX_LEN + 2 /* CRC */) // payload length
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is a little confusing. When we configure hardware, we usually work on PHY layer. Max PSDU length is 127 and it includes CRC. I would change this constant to MAX_PSDU_LEN = 127 and use it here without + 2. It would be unambiguous.

Comment thread nrf52840-hal/src/ieee802154.rs Outdated
pub fn set_channel(&mut self, channel: Channel) {
self.disable();

// NOTE(unsafe) radio is currently disabled
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is OK to change frequency while radio is enabled. The FREQUENCY register is double buffered and it is read on RXEN or TXEN tasks.
There is no need to call self.disable(); before.

Comment thread nrf52840-hal/src/ieee802154.rs Outdated

/// Changes the Clear Channel Assessment method
pub fn set_cca(&mut self, cca: Cca) {
self.disable();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like with frequency it is OK to change CCA mode while radio is enabled.

match state {
STATE_A::DISABLED => return,

STATE_A::RXRU | STATE_A::RXIDLE | STATE_A::TXRU | STATE_A::TXIDLE => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RXIDLE state may indicate ongoing CCA procedure that may result in TX with radio shorts. To be fully safe, following set of tasks should be triggered:
CCASTOP
STOP
DISABLE

}

/// Moves the radio from any state to the DISABLED state
fn disable(&mut self) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a possibility that with RADIO SHORTS enabled this function would work in an unexpected way. I would add at least assertion here to verify that selected shorts are disabled.

Comment thread nrf52840-hal/src/ieee802154.rs Outdated

/// An IEEE 802.15.4 packet
///
/// This `Packet` is closest to the PPDU (PHY Protocol Data Unit) defined in the IEEE spec. The API
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closest to the PPDU is ambiguous. I would state here clearly that this packet is PHR concatenated with PSDU

Comment thread nrf52840-hal/src/ieee802154.rs Outdated
///
/// NOTE `src` data will be truncated to `MAX_PACKET_SIZE` bytes
pub fn copy_from_slice(&mut self, src: &[u8]) {
let len = cmp::min(src.len(), Self::MAX_LEN as usize) as u8;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is a good idea to silently limit the given slice size. When user of this function wants to transmit 200 bytes packet and calls this function, would get only 125 bytes transmitted and would not be notified that there was a problem. I would change function signature to return Result or add assertion.

Comment thread nrf52840-hal/src/ieee802154.rs Outdated
///
/// NOTE `len` will be truncated to `MAX_LEN` bytes
pub fn set_len(&mut self, len: u8) {
let len = cmp::min(len, Self::MAX_LEN);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again silently truncated. Using such API could be a source of hard to find bugs in the higher layers.

@japaric
Copy link
Copy Markdown
Contributor Author

japaric commented Jun 9, 2020

@hubertmis thanks for the great feedback! (I have seen it just now). I'll incorporate it in the PR in the coming days.

- Packet::set_len: panic instead of silently clapping the value
- Packet::copy_from_slice: panic instead of silently truncating
- rename some internal constants related to the size of the parts of Packet
- doc tweak: Packet is PHR + PSDU
- keep Channel enum as it is (removed TODO)
- add send_no_cca method
- shortcut ccaidle to txen in try_send
- simplify transitions to the TXIDLE / RXIDLE state
Copy link
Copy Markdown

@hubertmis hubertmis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The driver looks solid. There is place to add more features and optimizations.

I'm not a fan of blocking recv method. But making it asynchronous would need serious refactoring of the driver. For simple applications blocking approach is sufficient.

Comment thread nrf52840-hal/src/ieee802154.rs Outdated
@hubertmis
Copy link
Copy Markdown

@japaric , to verify CRC of generated frames, you can easily use https://github.com/NordicSemiconductor/nRF-Sniffer-for-802.15.4 . Firmware used in this project reports only frames with valid CRC.

@hubertmis
Copy link
Copy Markdown

hubertmis commented Jul 25, 2020

I was thinking about timing out while receiving. Current implementation blocks execution of the calling thread until a frame is received or a timeout fires. It's not good for the system performance.

In drivers in C usually asynchronous approach is implemented: function like recv changes the driver state to 'receive' and ends it execution - it does not wait for actual frame. If the driver receives a frame, it calls a callback asynchronously. But it looks that this asynchronous callback approach is not perfect fit for Rust, because I think it does not go well with futures or programs using async/await.

Recently I've spotted https://github.com/rust-embedded/nb and it looks like a solution to this problem. The user of the driver could call recv() just to enter receive state. Later the user could call a new function like read() that would implement this nb scheme and return a frame if it was received before the function was called, or WouldBlock if the RX buffer is empty. With such API, the calling thread would not be blocked while waiting for frames and futures, async/await, or other models could be used.

What do you think about it?

Comment thread nrf52840-hal/src/ieee802154.rs Outdated
Comment thread nrf52840-hal/src/ieee802154.rs Outdated
Comment thread nrf52840-hal/src/ieee802154.rs Outdated
Comment thread nrf52840-hal/src/ieee802154.rs Outdated
Comment thread nrf52840-hal/src/ieee802154.rs Outdated
Comment thread nrf52840-hal/src/ieee802154.rs Outdated
Comment thread nrf52840-hal/src/ieee802154.rs Outdated
Comment thread nrf52840-hal/src/ieee802154.rs Outdated
pub const DEFAULT_CCA: Cca = Cca::CarrierSense;

/// Default radio channel = Channel 20 (`2_450` MHz)
pub const DEFAULT_CHANNEL: Channel = Channel::_18; // _17 todo undo; set this to interfere with my wifi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this needs to be reset to channel 17.

Are the defaults specified somewhere or were they picked arbitrarily?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the FREQUENCY register reset value is 2, which doesn't match to any of the Channel values because they are all multiples of 5.
The channel values (the frequencies) are the standard IEEE frequencies.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the default channel to 11 (2405 MHz) which is closer to the reset value of the FREQUENCY register (2402 MHz)

Comment thread nrf52840-hal/src/ieee802154.rs Outdated
japaric and others added 4 commits March 3, 2021 13:45
Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
to enforce that the buffer is RAM allocated
@jonas-schievink
Copy link
Copy Markdown
Contributor

Okay, I think this is ready to go. The remaining notes can be addressed later, and an async/non-blocking API can also be added then.

bors r+

@bors
Copy link
Copy Markdown
Contributor

bors Bot commented Mar 3, 2021

Build succeeded:

@bors bors Bot merged commit 6b299aa into nrf-rs:master Mar 3, 2021
@jonas-schievink
Copy link
Copy Markdown
Contributor

I only just noticed that the module was added directly to the nrf52840-hal instead of nrf-hal-common. I've opened #299 to move it (and enable it for the nRF52833 in the process).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants