Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement nRF24L01 driver #1

Draft
wants to merge 37 commits into
base: main
Choose a base branch
from
Draft

implement nRF24L01 driver #1

wants to merge 37 commits into from

Conversation

2bndy5
Copy link
Member

@2bndy5 2bndy5 commented Jul 9, 2024

  • add source code to drive the nRF24L01
  • add a build/test/lint CI workflows; no release CI for crates.io (yet)
  • add unit tests using embedded-hal-mock (and activate codecov)
  • add (Linux only) FFI bindings (consider FFI bindings #2)
    • python binding
    • node binding
  • add examples

@2bndy5 2bndy5 added documentation Improvements or additions to documentation enhancement New feature or request labels Jul 9, 2024
includes
- CI workflow
- funding info
- dependabot config
- prefer to `use rf24_rs::radio::prelude::*` in user code to pull in all the required radio traits.
- support LNA_CUR bit in non-plus/clones
- prefer to `use rf24_rs::radio::{RF24 Nrf24Error}` in user code
  because namespaces will be very important in the future
@2bndy5 2bndy5 changed the title impement nRF24L01 driver implement nRF24L01 driver Jul 10, 2024
@codecov-commenter
Copy link

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

- add badges
- tick first milestone in roadmap
rust is so cool!

This new macro generates rust code at compile time and then compiles everything with the generated code in place of the macro calls.
removes the need to construct a mutable byte
@2bndy5
Copy link
Member Author

2bndy5 commented Aug 1, 2024

This is just an informative collection of my thoughts, and reasons why I paused this development.

Examples

Here's the chips I'm looking to support (initially) in the examples:

  • The implementation of embedded-hal on Linux for SoM (System on Module) boards is freakishly easy to use.
  • The implementation of embedded-hal on RP2040 is designed to be run as bare metal (no RTOS).
  • The implementation of embedded-hal on ATSAMD seems to have abstractions for various related boards (which seem to be recommended in docs).
  • The implementation of embedded-hal on AVR (supports ATmega238 and ATTiny chips) is not even published to crates.io. This is odd and a bit of a red flag for me. I could pin to a git source using a commit SHA or tag, but the process of uploading the compiled binaries recommends using a tool that wraps around the AVRdude tool used by the Arduino IDE (see ravedude).
  • The implementation for embedded-hal on Nordic chips has several abstractions based (probably generated from) on the dev kit for each supported chip (ie nRF52840-hal). It is designed to be used on bare metal (no RTOS) and does not employ a softdevice (which is supported in the embassy project -- see below).
  • The implementation for embedded-hal on esp32 seems to be a rust binding to the ESP-IDF (written in C). Like the ESP-IDF, the embedded-hal for esp32 uses a custom tool to build and upload binaries to the board.

Writing the examples is tough. The embedded-hal implementations for various chips are not conformed like the Arduino Cores for various chips families. Writing an example for the RP2040 will look significantly different from an example for the ATSAMD21.

Due to this complexity, I've decided to try and put the examples in its own "crate" (a rust term for a library) in a sub-directory named "examples". This way I can allow users to control which chip to compile for using cargo features defined in examples/cargo.toml.

Furthermore, flashing the compiled binary to a board requires a separate dependency called probe.rs. This is another reason why the examples need to use a separate crate.

Embassy Project

There is an embassy project that focuses on some SoC (System on Chip) that employ an RTOS. This seems useful for multi-processing, but it is ultimately unnecessary for simple examples. Nonetheless, I could use this embassy project for ESP32, RP2040, and nRF5x chips because they seem to have a somewhat unified API for those chips (I think).

No STD libs in embedded-hal

See the embedded-hal book about rationale. Basically, driver libraries for embedded-hal projects cannot use rust's standard libraries. This includes Vec, BTreeMap data collections because it involves allocating memory at runtime.

This will be a problem down the road when I get around to implementing the Mesh layer because we need a way to dynamically allocate memory as Logical Addresses are assigned to a mesh node's ID. There are third party libs that aim to accommodate this shortcoming, but I'll cross that bridge when I get there.

Tip

The embeded-hal implementations on Linux and ESP32 boards can use the std libs. 🎉


The embedded-hal framework (which doesn't impose embedded-hal implementations use uniformed API names for data structures) had been in beta for years. It only reached a stable release in Jan 2024. This fact has caused a side effect that can be seen in many embedded-hal implementations; they still support embedded-hal framework v0.2.x (the last beta release) as an optional cargo feature. This driver library will only support v1+ of the embedded-hal framework (unless requested otherwise).

@2bndy5 2bndy5 force-pushed the impl-rf24 branch 2 times, most recently from aec4db1 to 6031361 Compare October 8, 2024 06:07
introduce supplemental docs (using mkdocs) and WIP examples (incomplete at this time)

migrate to using [just](https://just.systems) as a task runner and utilize it in CI jobs.
@2bndy5 2bndy5 force-pushed the impl-rf24 branch 2 times, most recently from f974aaf to aef9f61 Compare October 8, 2024 13:54
@2bndy5
Copy link
Member Author

2bndy5 commented Oct 10, 2024

I have added python and node.js bindings 🎉 . I have also tested them on my RPi4, but there are no complete examples (yet).

Python binding

The python binding will be published to PyPI as rf24-py.

Using REPL after build-from-source

Activate a python venv and (from repo root)

pip install maturin
maturin dev
python

Then play around with it

> from rf24_py import RF24
> r = RF24(22, 0)
> # for RPi5
> r = RF24(22, 0, dev_gpio_chip = 4)
> # reduce SPI speed
> r = RF24(22, 0, spi_speed=4000000)

The API is almost the same as pyRF24 with a few exceptions. There's no undocumented camelCased API because it is unconventional in python (and increases binary size significantly). See below for more differences.

Node.js binding

The node binding will be published under the name @rf24/rf24. I have already reserved the org name rf24 on npmjs. This is because the actual native binaries are published under a different package that corresponds to the target system:

target pkg name
linux-arm-gnueabihf @rf24/rf24-linux-arm-gnueabihf

The @rf24/rf24 package will automatically decide which native binary package to import at runtime. This paradigm was not my choice, rather this is the paradigm offered by the napi-rs build/generator project.

using REPL after build-from-source

yarn build:debug
node

Then play around

> const native = require('./bindings/node/index.js')
> r = new native.RF24(22, 0)
> // if using RPi5
> r = new native.RF24(22, 0, {devGpioChip: 4})
> // to lower the SPI speed
> r = new native.RF24(22, 0, {spiSpeed: 4000000})
> r.begin()
> r.openRxPipe(1, Buffer.from("1Node"))
> r.openTxPipe(Buffer.from("2Node"))
> r.stopListening()
> r.send(Buffer.from("a msg"))
true
> r.startListening()
> r.available()
true
> r.availablePipe()
{ available: true, pipe: 1 }
> rxBuf = r.read(32)
<Buffer 61 6e 6f 74 68 65 72 20 6d 73 67 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
> rxBuf.toString()
'another msg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
> r.getStatusFlags()
{ rxDr: true, txDs: false, txDf: false }
> r.update()
> r.getStatusFlags()
{ rxDr: false, txDs: false, txDf: false }

Please beware of API differences

Since I'm writing this lib from scratch, I figured it was a good opportunity to revise the API observed in C++. Actually, it is like a mix of the CircuitPython API and C++ behavior; the best of both experiences IMO.

API differences include

  • no timeouts when transmitting
  • no class properties. Prefer using get_*() or set_*() instead.
  • most functions will return an error (throw an exception in bindings) if binary corruption over SPI or any other type of hardware failure is observed.
  • power_up() takes an optional arg to add a delay (in microseconds)
  • no multitude of write***() functions. There's write() for non-blocking use and send() for blocking (normal) use.
  • status byte is exposed via get_status_flags(). Unlike C++ whatHappened(), the flags are not updated nor cleared. The flags are simply returned from the state of last SPI transaction. Use update() to update the flags and clear_status_flags() to clear them.
  • set_status_flags() take 3 boolean values to configure the IRQ pin. Similar to C++ maskIRQ(), but the parameters meanings are inversed: true means enable IRQ for corresponding event.

There also isn't any printDetails() equivalent (yet). I might make this possible using a function to interpret a buffer of values dumped from registers (similar to C++ sprintfPrintPrettyDetails()). Logging in rust is possible, but it would have to be conditionally compiled feature.

@2bndy5
Copy link
Member Author

2bndy5 commented Oct 14, 2024

I ported the examples from the pyRF24 project and did some comparisons... rf24-py (this lib's python binding) runs nearly as fast as pyRF24 (C++ bindings) 🎉 There seems to be a little more runtime overhead in rf24-py than in pyRF24, but there's a lot more error checking (and probably more type checking) in rf24-py during runtime. Still, rf24-py is easier to build/maintain than pyRF24 and is considerably faster than a pure-python implementation (like the CircuitPython-nRF24L01 lib).

Now to add rust examples and node.js (preferably typescript) examples, (& more docs about bindings).

- implements print_details()
- adds 1 example in typescript
- more WIP on rust example
- rename enums.rs to types.rs
- move all bindings to bindings folder
- rename lib folder to library (for gitignore reasons)
- add a new CI related to examples only
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants