Skip to content

Comments

drivers/wireless: Add support for the RN903 and RN2483 LoRa modules#15828

Merged
acassis merged 2 commits intoapache:masterfrom
linguini1:rn2903-driver
Mar 6, 2025
Merged

drivers/wireless: Add support for the RN903 and RN2483 LoRa modules#15828
acassis merged 2 commits intoapache:masterfrom
linguini1:rn2903-driver

Conversation

@linguini1
Copy link
Contributor

@linguini1 linguini1 commented Feb 13, 2025

Summary

Add support for the RN2903 and RN2483 family of LoRa radio transceivers. This initial support includes transmit and receive functionality and configuration and reading of radio parameters like frequency and bandwidth.

Impact

NuttX users can now use these radio transceivers!

This changes touches the build system, wireless drivers and documentation. The documentation includes examples of how to use the driver and its available commands.

Testing

Testing was performed from an RP2040 based custom flight computer. The chip this code was tested again was the RN2903, but commands for both modules are the same. The only difference is the values allowed for each command, but the radio module does its own error checking so the driver code will not need to change between modules.

The test script that was used to test the commands was:

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <syslog.h>
#include <unistd.h>

#include <nuttx/wireless/lpwan/rn2xx3.h>

#define handle_errno(msg, err)                                               \
  if ((err) < 0)                                                             \
    {                                                                        \
      fprintf(stderr, msg ": %d\n", errno);                                  \
      close(radio);                                                          \
      return EXIT_FAILURE;                                                   \
    }

int main(int argc, FAR char *argv[])
{
  int err;
  int radio;

  radio = open("/dev/rn2903", O_RDWR);
  if (radio < 0)
    {
      fprintf(stderr, "Couldn't open RN2903: %d\n", errno);
      return EXIT_FAILURE;
    }

  /* Set frequency */

  err = ioctl(radio, WLIOC_SETRADIOFREQ, 902400000);
  handle_errno("Couldn't set freq", err);

  /* Get frequency */

  uint32_t freq;
  err = ioctl(radio, WLIOC_GETRADIOFREQ, &freq);
  handle_errno("Couldn't get freq", err);

  printf("Frequency: %lu Hz\n", freq);

  /* Set TX power */

  float txpower = 13.0;
  err = ioctl(radio, WLIOC_SETTXPOWERF, &txpower);
  handle_errno("Couldn't set txpwr", err);

  printf("Transmit power set to %.2f dBm\n", txpower);

  /* Get TX power */

  float txpwr;
  err = ioctl(radio, WLIOC_GETTXPOWERF, &txpwr);
  handle_errno("Couldn't get txpwr", err);

  printf("Tx power level: %.2f\n", txpwr);

  /* Get SNR */

  int8_t snr;
  err = ioctl(radio, WLIOC_GETSNR, &snr);
  handle_errno("Couldn't get snr", err);

  printf("SNR: %d\n", snr);

  /* Set preamble length */

  err = ioctl(radio, WLIOC_SETPRLEN, 8);
  handle_errno("Couldn't set prlen", err);

  /* Get preamble length */

  uint16_t prlen;
  err = ioctl(radio, WLIOC_GETPRLEN, &prlen);
  handle_errno("Couldn't get prlen", err);

  printf("Preamble length: %u\n", prlen);

  /* Set spread */

  err = ioctl(radio, WLIOC_SETSPREAD, 8);
  handle_errno("Couldn't set spread factor", err);

  /* Get spread */

  uint8_t spread;
  err = ioctl(radio, WLIOC_GETSPREAD, &spread);
  handle_errno("Couldn't get spread factor", err);

  printf("Spread factor: sf%u\n", spread);

  usleep(10000);

  /* Set modulation */

  err = ioctl(radio, WLIOC_SETMOD, RN2XX3_MOD_FSK);
  handle_errno("Couldn't set modulation", err);

  /* Get modulation */

  enum rn2xx3_mod_e modulation;
  err = ioctl(radio, WLIOC_GETMOD, &modulation);
  handle_errno("Couldn't get modulation", err);

  printf("Modulation: %d\n", modulation);

  usleep(10000);

  /* Set bandwidth */

  uint32_t bandwidth = 250;
  err = ioctl(radio, WLIOC_SETBANDWIDTH, bandwidth);
  handle_errno("Couldn't set bw", err);

  /* Get bandwidth */

  err = ioctl(radio, WLIOC_GETBANDWIDTH, &bandwidth);
  handle_errno("Couldn't get bw", err);

  printf("Bandwidth: %lu kHz\n", bandwidth);

  /* Set sync word */

  uint64_t syncword = 0xdeadbeefdeadbeef;
  err = ioctl(radio, WLIOC_SETSYNC, &syncword);
  handle_errno("Couldn't set sync", err);

  /* Get sync word */

  err = ioctl(radio, WLIOC_GETSYNC, &syncword);
  handle_errno("Couldn't get sync", err);

  printf("Sync word is: %016llX\n", syncword);

  /* Set bit rate */

  err = ioctl(radio, WLIOC_SETBITRATE, 1542);
  handle_errno("Couldn't set bitrate", err);

  /* Get bit rate */

  uint32_t bitrate = 300000;
  err = ioctl(radio, WLIOC_GETBITRATE, &bitrate);
  handle_errno("Couldn't get bitrate", err);

  printf("Bit rate is: %lu\n", bitrate);

  /* Disable IQI */

  err = ioctl(radio, WLIOC_IQIEN, 0);
  handle_errno("Couldn't disable IQI", err);

  printf("IQI disabled\n");

  /* Enable CRC */

  err = ioctl(radio, WLIOC_CRCEN, 1);
  handle_errno("Couldn't enable CRC", err);

  printf("CRC enabled\n");

  /* Set coding rate */

  err = ioctl(radio, WLIOC_SETCODERATE, RN2XX3_CR_4_8);
  handle_errno("Couldn't set coding rate", err);

  /* Get coding rate */

  enum rn2xx3_cr_e cr;
  err = ioctl(radio, WLIOC_GETCODERATE, &cr);
  handle_errno("Couldn't get coding rate", err);

  printf("Coding rate: %d\n", cr);

  /* Reset radio module */

  printf("Resetting radio.\n");

  err = ioctl(radio, WLIOC_RESET, 0);
  handle_errno("Couldn't reset radio", err);

  printf("Radio reset.\n");

  close(radio);
  return 0;
}
}

The output is:

nsh> hello
Frequency: 902400000 Hz
Transmit power set to 13.00 dBm
Tx power level: 13.00
SNR: -128
Preamble length: 8
Spread factor: sf8
Modulation: 1
Bandwidth: 250 kHz
Sync word is: DEADBEEFDEADBEEF
Bit rate is: 5442
IQI disabled
CRC enabled
Coding rate: 3
Resetting radio.
Radio reset.

I also performed transmit/receive testing using a second RN2903 which I used to transmit/receive from a Python script. I was able to successfully receive messages sent from the NuttX driver, and I was also able to successfully receive messages with the NuttX driver sent from the Python script controlled transceiver.

The code works with read/write functions, so it can be echoed into or catted from to transmit and receive in the shell.

Documentation was built locally and verified for correct render in the browser.

@github-actions github-actions bot added Area: Documentation Improvements or additions to documentation Area: Drivers Drivers issues Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. labels Feb 13, 2025
@nuttxpr
Copy link

nuttxpr commented Feb 13, 2025

[Experimental Bot, please feedback here]

Yes, this PR appears to meet the NuttX requirements, although there's room for improvement in the level of detail provided.

Strengths:

  • Clear Summary: The summary explains the what and why of the change well.
  • Impact Description: Addresses most impact areas, highlighting key changes in build, drivers, and documentation.
  • Testing Evidence: Provides test code, output, and a description of additional transmit/receive tests, which is good. The mention of documentation build verification is also positive.

Areas for Improvement:

  • Summary - How it works: The summary should briefly explain how the change is implemented (e.g., new driver using UART, specific files modified).
  • Impact - More Detail: While the impact sections are addressed, they lack specifics. For example:
    • Build: How does the build process change? New Kconfig options? Build dependencies?
    • Hardware: Which specific arch/board was tested? Mentioning RP2040 is good, but the full board name/configuration would be better. Clarify the RN2903/RN2483 compatibility – are both fully supported or is there limited functionality for one?
    • Documentation: Where are the documentation changes located? (e.g., "Added a new section to the wireless drivers documentation").
    • Compatibility: Are there any known backward compatibility issues?
  • Testing - Before/After Logs: The provided test code is good, but the "Testing logs before change" and "Testing logs after change" sections should show the actual console output before and after the driver was added. While the provided output demonstrates the driver functionality, it doesn't demonstrate the change itself. The transmit/receive test with Python is good, but some snippets of that code/output would further strengthen the testing evidence.
  • Testing - Build Host: Provide details about your build host environment (OS, compiler version, etc.).

Specific Recommendations:

  1. Expand the Summary's "How it Works": Briefly describe the implementation (e.g., "A new character driver was implemented for the RN903/RN2483 LoRa radio transceivers using the UART interface. Modifications were made to the wireless driver subsystem and the build system.").
  2. Provide More Specifics in Impact Sections: For instance, in "Build," mention specific Kconfig options or build dependencies introduced. In "Hardware," specify the exact board used (e.g., "Custom RP2040 board with configuration X"). In "Documentation," give the file path of the updated documentation.
  3. Improve Testing Logs: Show console output before the driver existed (likely an error message when trying to access the device) and after (the successful output already shown). Include snippets of the Python test code and its output.
  4. Add Build Host Details: Specify your build host OS, compiler, and versions.

By addressing these points, the PR will be even stronger and provide reviewers with all the necessary information to efficiently evaluate the changes.

@lupyuen
Copy link
Member

lupyuen commented Feb 13, 2025

Thanks @linguini1! I suggest we rename the PR Title to "drivers/wireless: Add support for..."? So it looks nicer in the NuttX Release Notes :-) https://nuttx.apache.org/releases/12.8.0/

@tcpipchip
Copy link

tcpipchip commented Feb 13, 2025

Hi, do you plans to port to STM32WL55 ?

@linguini1 linguini1 changed the title Add support for the RN903 and RN2483 LoRa modules drivers/wireless: Add support for the RN903 and RN2483 LoRa modules Feb 13, 2025
@linguini1
Copy link
Contributor Author

Hi, do you plans to port to STM32WL55 ?

If that MCU is supported by NuttX, then this driver should work on it. This is a generic driver that should work with any UART interface.

@tcpipchip
Copy link

Hi, do you plans to port to STM32WL55 ?

If that MCU is supported by NuttX, then this driver should work on it. This is a generic driver that should work with any UART interface.

SX1262 ?

@linguini1
Copy link
Contributor Author

SX1262 ?

If NuttX supports the STM32WL55, so long as you connect the RN2xx3 to a UART interface on that chip, you can use this driver with that chip running NuttX. This driver is not for the SX1262. It appears that the WL55 is supported so this driver could be used on that chip.

*
****************************************************************************/

static ssize_t read_line(FAR struct rn2xx3_dev_s *priv, FAR char *buf,
Copy link
Contributor

Choose a reason for hiding this comment

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

Something still does not quite sit right here with me. You implicitly trust in data returned from the modem.

Can modem send more than 2 lines at a time? Can your driver lead to situation where 2 lines are in uart buffer? What will happend when there are 2 lines in buffer (like 10 char long) and you call it with nbytes == 15? You will read half of line, and next call will return invalid data.

Second case, what if line in uart buffer is longer than nbytes? You again will read half of frame - and report success, so at least 2 consecutive calls will return invalid data. I think you should sanitize received frame here to line level. If you did not read whole line, return -1, and discard from uart buffer until you reach \n.

I think you should implement small (at least 2 times the maximum size of single line modem can throw at you) ring buffer here and use that to read data. You can then call file_read when buffer gets empty to fill it back in.

Otherwise you will be returning invalid data to userspace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the catch on this case. I have resolved case 2. However, in regards to case 1, no it is not possible with this driver for 2 lines to be present in the UART buffer. All commands to the modem are call/response, and before each call/response sequence I flush the UART buffer to make sure there is no lingering data that will interrupt the regular flow. The modem cannot send more than one line at a time, unless I deliberately issue more than one command at a time.

@keever50
Copy link
Contributor

Good idea. The same can be done to my pr.
I really just want these to be pushed so that's out of the way now

@linguini1
Copy link
Contributor Author

However, I do think introducing this new API requires some conversation and work with many other developers. This seems outside the scope of this PR. Would that delay the merge of this PR or would it be something implemented afterward as a work in progress?

what about marking this driver as EXPERIMENTAL? to make it clear that in the future there may be some breaking changes in the API

Sure, I'll mark this in the docs and code comments.

@linguini1 linguini1 force-pushed the rn2903-driver branch 2 times, most recently from 1a30fa5 to 2a4bac2 Compare March 3, 2025 16:13
@linguini1 linguini1 requested a review from keever50 March 3, 2025 16:13
@linguini1 linguini1 marked this pull request as ready for review March 3, 2025 16:13
@keever50
Copy link
Contributor

keever50 commented Mar 3, 2025

@linguini1 I see that you added common IOCTL commands, however i would suggest looking at #15856 maybe. The common API is still being worked on. I just started implementing the features into ioctl.h
Maybe you can look at that? It is fine if you want to do it yourself too while you are at it.
The main difference between my common IOCTL and yours is i tagged LoRa related commands with WLIOC_LORA_x.
This is done to keep some seperation between two obviously different command groups.
Personally it feels "messy" when all modulation types are considered common.

@linguini1
Copy link
Contributor Author

@linguini1 I see that you added common IOCTL commands, however i would suggest looking at #15856 maybe. The common API is still being worked on. I just started implementing the features into ioctl.h
Maybe you can look at that? It is fine if you want to do it yourself too while you are at it.
The main difference between my common IOCTL and yours is i tagged LoRa related commands with WLIOC_LORA_x.
This is done to keep some seperation between two obviously different command groups.
Personally it feels "messy" when all modulation types are considered common.

Hey yeah, I did see that discussion and I like some of the solutions proposed there. However, I don't want to start implementing the API from that discussion in this PR since I know it is still under discussion (I have some ideas to discuss as well). My plan was just to merge this prototype (marked as EXPERIMENTAL) and then make changes to it once we have a better idea of a fleshed out API. I'm willing to commit to making those changes in another PR along with changing the sx17x legacy driver.

@keever50
Copy link
Contributor

keever50 commented Mar 3, 2025

@linguini1
Ah okay i understand! I do feel a bit uncomfortable knowing that the experimental changes will touch the already established common IOCTL. Which is the part that is kinda not experimental.
What i did for the SX126x, I made seperate commands specifically for the SX126x and left the already common ones untouched for the time being. Ill remove or change these sx126x's later when merging with the future API.

So i suggest maybe only adding the name of your driver infront of the commands for this temporary experimental phase?
Leaving the already established common commands(section) untouched. Might provide a smoother porting experience.

Lets see what the others think.

@linguini1
Copy link
Contributor Author

@linguini1 Ah okay i understand! I do feel a bit uncomfortable knowing that the experimental changes will touch the already established common IOCTL. Which is the part that is kinda not experimental. What i did for the SX126x, I made seperate commands specifically for the SX126x and left the already common ones untouched for the time being. Ill remove or change these sx126x's later when merging with the future API.

So i suggest maybe only adding the name of your driver infront of the commands for this temporary experimental phase? Leaving the already established common commands(section) untouched. Might provide a smoother porting experience.

Lets see what the others think.

I see, I assumed we would just rework those common commands entirely. I think @raiden00pl mentioned that he was (afaik) the only user of the SX127X driver and was fine with having those commands be completely changed. As far as I'm aware, the only drivers using WIOCTL commands right now are the SX127X, your SX126X and my RN2XX3 drivers. I wouldn't mind changing the WIOCTL commands I added to be specific to the RN2XX3 in the interim though, I just assumed they'd be overhauled shortly anyways. I can make that change.

@keever50
Copy link
Contributor

keever50 commented Mar 3, 2025

Mar 3, 2025

Hmm. I thought adding more to what has to be removed might deliver extra work.
However, some commands like set radio frequency dont have to be changed. Some things can stay. maybe.

My strategy is to 1: add experimental drivers, 2: add common API, 3: port drivers and then 4: remove legacy. Slowly.

When we want to rework the entire complete thing at once, we need to rework everything connected to the wireless ioctl.h
For example, the NRF24l01 is also using these commands. They would all require testing before PR can be accepted.

It would be very nice to be able to work them one by one without breaking them or porting them all at once.
We can take our time with these.
So we have a legacy period of extra commands that will slowly go away.

But i think as long the already established common ones stay completely untouched and your solution doesn't get away in this process, its fine. I think just moving them a bit down under a comment also is completely OK.

I'm really happy that people start to add LoRa to NuttX though! I can really really use these features. Thank you for doing this

@linguini1
Copy link
Contributor Author

But i think as long the already established common ones stay completely untouched and your solution doesn't get away in this process, its fine.

The only one I will be touching is the TXPWR commands, to change from int32_t dBm to int32_t 0.01 dBm, as per the earlier discussion on this PR.

I think just moving them a bit down under a comment also is completely OK.

Okay, I can definitely make that change easily!

I'm really happy that people start to add LoRa to NuttX though! I can really really use these features. Thank you for doing this

Me as well, I'm very excited about your work, too. My rocketry team wants to move away from the integrated RN2483 and towards the SX series LoRa chips, so your work is a huge help in that direction.

Copy link
Contributor

@jerpelea jerpelea left a comment

Choose a reason for hiding this comment

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

please move documentation to a separate commit in the same PR

@linguini1 linguini1 force-pushed the rn2903-driver branch 2 times, most recently from 4d22158 to 9d13bc6 Compare March 4, 2025 13:52
@linguini1 linguini1 requested a review from jerpelea March 4, 2025 13:52
Copy link
Contributor

@jerpelea jerpelea left a comment

Choose a reason for hiding this comment

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

please update the commit tile to
Documentation: Add documentation for RN2XX3 driver

9d13bc6

radio transceivers. This initial support includes transmit and receive
functionality and configuration and reading of radio parameters like
frequency and bandwidth.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Adds a documentation page about the driver's programming interface and
new WLIOC commands.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
@acassis acassis merged commit c1858a2 into apache:master Mar 6, 2025
40 checks passed
* of the address is driver specific */
#define WLIOC_SETTXPOWER _WLCIOC(0x0005) /* arg: Pointer to int32_t, */
/* output power (in dBm) */
/* output power (in 0.01 dBm) */
Copy link
Member

Choose a reason for hiding this comment

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

this is true only for this driver. All the other drivers use the old unit. Either all other usage of this command should be fixed or this comment should say that this value depends on the driver

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, it was my intention to change this globally once we decided on a better LoRa framework, but I can patch the docs if that's necessary.

@linguini1 linguini1 deleted the rn2903-driver branch November 9, 2025 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Documentation Improvements or additions to documentation Area: Drivers Drivers issues Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants