NRF24.Net

Thread Starter

Futurist

Joined Apr 8, 2025
748
I just got an experimental .Net class library running, that encapsulates an NRF24L01+ device.

This is made possible by use of this "SPIDriver" board that enables code on a Windows PC (or Linux or MacOS) to use SPI to communicate with peripherals.

The C# code shown below is modelled after the equivalent C code that I developed and tested on a Nucleo board that has an NRF24 attached.

C#:
using Radio.Nordic.NRF24L01P;

namespace Sandbox
{
    class Program
    {
        private static ulong address = 0x19513831AA; // Testing address
        private static byte[] payload = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };

        static void Main(string[] argv)
        {
            var port = NRF24L01P.GetNrfComPort();

            using (NRF24L01P nrf = new NRF24L01P(port))
            {
                nrf.ConnectUSB();
                nrf.Reset();
                nrf.ConfigureRadio(9, 1, 0);
                nrf.ClearInterruptFlags(true,true,true);
                nrf.SetPipeState(Pipe.Pipe_0, true);
                nrf.SetPipeState(Pipe.Pipe_1, false);

                nrf.SetAutoAck(Pipe.Pipe_0,true);
                nrf.SetTransmitMode();
                nrf.SetCRC(true,true);
                nrf.SetAddressWidth(3);
                nrf.SetAutoAckRetries(1, 10);
                nrf.PowerUp();

                nrf.SetReceiveAddressLong(address, Pipe.Pipe_0);
                nrf.SetTransmitAddress(address);

                STATUS status;

                status = nrf.ReadRegister<STATUS>();

                while (true)
                {
                    nrf.SendPayload(payload);
                    Thread.Sleep(100);
                    status = nrf.ReadRegister<STATUS>();
                }
            }
        }
    }
}
You can see how straightforward it is to use the class library so I'm looking forward to doing some interesting stuff using my desktop PC to communicate with a bunch of Nucleos!

For years I've wanted to be able to use a PC as a base station to control/manage/oversee some microcontrollers and the WiFi route though doable is a trifle cumbersome and overkill really when using these small boards.

As a slight aside, I'd be interested in hearing what hardware gurus think about that SPIDriver board, it certainly works but I wonder if it can be improved upon, a simpler smaller cheaper board that has no display but offers a similar ability to use a serial port (so far as Windows is concerned) to drive an SPI interface.
 
Last edited:

Thread Starter

Futurist

Joined Apr 8, 2025
748
This is going well, here's a simple .Net console app coded to report any transmissions that do not get auto acked by the recipient:

1747422332002.png

That test sends a simple short payload every 50 ms or so, Those messages happen when I reset the Nucleo board receivers.

I'm thinking of designing a simple messaging protocol for sending requests/commands and getting response.

This would allow the PC to probe the Nucleos (if they were doing anything real) and display the responses.

Some of the things that spring to mind are to have the protocol include setup, that is when a device starts it just "listens" and then the PC could send the details telling the receiver:

  1. Use this address
  2. Use this channel

You get the picture, the base station would configure a receiver and then communicate with it on that channel/address for the duration of the session or something.

The basic shock burst protocol is fixed length blocks of max 32 bytes which isn't a drawback at all. A Nucleo (STM32) has a unique ID which is helpful.

Ideally the base PC would send out some kind of request asking for each board to send its ID.

Then once the base has the ID's it can send commands out targeting that specific ID and only the board with the matching ID would handle those commands, other boards would ignore them.

Once the base PC has the id's things get interesting, of course the initial command "announce yourselves" so to speak might get responded to by multiple boards so I'd need some kind of random delay so that each board's response has a high probability of not being interfered with.

It could be pretty interesting (but I know well, how protocols can get pretty complex especially if done in a hurry).
 

Thread Starter

Futurist

Joined Apr 8, 2025
748
Actually this code on the Nucleo gets part of the board ID and we can literally use that as the address:


C:
private void GetDefaultAddress(uint8_t address[5])
{
    uint32_t id1 = *(uint32_t*)0x1FFF7A10; // First 32 bits
    uint32_t id2 = *(uint32_t*)0x1FFF7A14; // Second 32 bits
    uint32_t id3 = *(uint32_t*)0x1FFF7A18; // Last 32 bits

    address[4] = (id3 & 0x000000FF) >> 0;
    address[3] = (id2 & 0xFF000000) >> 24;
    address[2] = (id2 & 0x00FF0000) >> 16;
    address[1] = (id2 & 0x0000FF00) >> 8;
    address[0] = (id2 & 0x000000FF) >> 0;
}
So the initial request for each board to announce itself, would contain this 5 byte value and the base station would just use that as the board's address!
 

nsaspook

Joined Aug 27, 2009
16,322
Actually this code on the Nucleo gets part of the board ID and we can literally use that as the address:


C:
private void GetDefaultAddress(uint8_t address[5])
{
    uint32_t id1 = *(uint32_t*)0x1FFF7A10; // First 32 bits
    uint32_t id2 = *(uint32_t*)0x1FFF7A14; // Second 32 bits
    uint32_t id3 = *(uint32_t*)0x1FFF7A18; // Last 32 bits

    address[4] = (id3 & 0x000000FF) >> 0;
    address[3] = (id2 & 0xFF000000) >> 24;
    address[2] = (id2 & 0x00FF0000) >> 16;
    address[1] = (id2 & 0x0000FF00) >> 8;
    address[0] = (id2 & 0x000000FF) >> 0;
}
So the initial request for each board to announce itself, would contain this 5 byte value and the base station would just use that as the board's address!
I usually use the CPU id on the PIC32 for a board identifier and then a device serial from something like a IMU to identify a specific sensor on a board during transmissions.
C:
#ifdef __32MZ1025W104132__
    cpu_serial_id = USERID & 0x1fffffff; // get CPU device 32-bit serial number and convert that to 29 - bit ID for CAN - FD
#else
    cpu_serial_id = DEVSN0 & 0x1fffffff; // get CPU device 32-bit serial number and convert that to 29 - bit ID for CAN - FD
#endif

/*
* get IMU device serial number and convert that to 29-bit ID for CAN-FD
*/
bool sca3300_getserial(void * imup) {
    imu_cmd_t * imu = imup;
    bool gotserial = false;

    if (imu) {
        if (!imu->run) {
            delay_us(SCA3300_CHIP_ID_DELAY); // sca3300 command spacing
            sca3300_imu_transfer(imu, SCA3300_BANK1);
            sca3300_imu_transfer(imu, SCA3300_SERIAL1);
            sca3300_imu_transfer(imu, SCA3300_SERIAL2);
            imu->serial1 = (imu->rbuf32[SCA3300_REC] >> 8)&0xffff;
            sca3300_imu_transfer(imu, SCA3300_BANK0);
            if (sca3300_check_crc(imu, SCA3300_REC)) {
                imu->serial2 = (imu->rbuf32[SCA3300_REC] >> 8)&0xffff;
                gotserial = true;
                board_serial_id = (imu->serial1 + (imu->serial2 << 16)) &0x1fffffff; // reduce to 29-bit id for CAN-CD address
                imu->board_serial_id = board_serial_id;
            }
        }
    }
    return gotserial;
}
 

Thread Starter

Futurist

Joined Apr 8, 2025
748
I got a little C# test app running on PC, a replica so to speak, of my C test transmitter app that runs on a Nucleo, this code reports a message every time a receiver fails to ack.

1747501867522.png

One of the neat things about C# is lambda functions:

C#:
 var status = nrf.PollStatusUntil(s => s.MAX_RT == true | s.TX_DS == true);

That function looks like this:

C#:
public STATUS PollStatusUntil(Func<STATUS,bool> func)
{
    var reg = ReadRegister<STATUS>();

    while (!func(reg))
    {
        reg = ReadRegister<STATUS>();
    }

    return reg;
}
Which makes for neat and readable code.
 
Top