spirilis

[Energia Library] Nordic nRF24L01+ library

352 posts in this topic

Hi folks... So I'm a little new to Energia, but since I wrote a library for the nRF24L01+ digital transceivers for native C-based apps I figured it'd be a great idea to port this to Energia.

 

I'm rewriting it from scratch, trying to make it simpler & feel more like a native C++ energia lib.

 

edit: This has been written and the latest version is up on Github.

Github project- https://github.com/spirilis/Enrf24

Latest version + comments- http://forum.43oh.com/topic/3237-energia-library-nordic-nrf24l01-library/?p=48805

 

 

 

 

To that end, I'd like some feedback on the API choices I've made.  I want to know what some of you guys think should go in here.

 

For now, here's an example of my "Nrf24.h" class definition (public's only, I haven't drafted the private's and probably won't until I really start writing the code):

class Nrf24 {
  public:
    Nrf24(uint8_t cePin, uint8_t csnPin, uint8_t irqPin);
    void begin();    // Defaults used
    void begin(uint32_t datarate);  // Specify bitrate
    void begin(uint32_t datarate, uint8_t channel);  // Specify bitrate & channel
    void end();      // Shut it off, clear the library's state

    // I/O
    boolean available();  // Check if incoming data is ready to be read
    boolean write(void *buf, uint8_t len);   /* Send packet, return true/false for autoACK
                                              * (true always if autoACK disabled or 250Kbps rate used)
                                              */
    uint8_t read(void *inbuf);    // Read contents of RX buffer, return length
    uint8_t read(void *inbuf, uint8_t maxlen);  // Read contents of RX buffer up to 'maxlen' bytes, return final length.
    void autoAck(boolean onoff);  // Enable/disable auto-acknowledgements (enabled by default)

    // Power-state related stuff-
    void deepsleep();  // Enter POWERDOWN mode, ~0.9uA power consumption
    void enableRX();   // Enter PRX mode (~14mA)
    void disableRX();  /* Disable PRX mode (PRIM_RX bit in CONFIG register)
                        * Note this won't necessarily push the transceiver into deep sleep, but rather
                        * an idle standby mode where its internal oscillators are ready & running but
                        * the RF transceiver PLL is disabled.  ~26uA power consumption.
                        */
 
    // Custom tweaks to RF parameters, packet parameters
    void setChannel(uint8_t channel);
    void setTXpower(int8_t dBm);  // Only a few values supported by this (0, -6, -12, -18 dBm)
    void setSpeed(uint32_t rfspeed);
    void setCRC(boolean onoff, boolean crc16bit);  /* Enable/disable CRC usage inside nRF24's hardware packet engine,
                                                    * specify 8 or 16-bit CRC.
                                                    */

    // Protocol addressing -- receive, transmit addresses
    void setAddressLength(uint8_t len);  // Valid parameters = 3, 4 or 5.  Defaults to 5.
    void setRXaddress(uint8_t *rxaddr);  // 3-5 byte RX address loaded into pipe#1
    void setTXaddress(uint8_t *txaddr);  // 3-5 byte TX address loaded into TXaddr register

    // Miscellaneous feature
    boolean rfSignalDetected();  /* Read RPD register to determine if transceiver has presently detected an RF signal
                                  * of -64dBm or greater.  Only works in PRX (enableRX()) mode.
                                  */
}

What do you think?  General usage would be to instantiate an Nrf24 object in the global, use begin() to start it and specify some custom params (data rate, or data rate & channel), then the transceiver is just sitting there idle until you either transmit (with setTXaddress() + write()) or enable RX mode with setRXaddress() + enableRX() and poll the available() function periodically.

 

One thing I haven't worked out yet is how to implement the IRQ feature with Energia.  The nRF24 has an IRQ pin that is best hooked up for optimal function.  Can the library just use attachInterrupt() on the irqPin value that the user passes up top?

izdane, Rickta59, xv4y and 2 others like this

Share this post


Link to post
Share on other sites

Hmm ok, brainstormed with Rick over IRC a bit.

 

I'm going to inherit from the Print class, and make a write interface that should feel somewhat intuitive although with a catch.

 

The user will set the TX address, then start writing into a buffer maintained by the lib, so that every nrf.print() function basically stuffs a buffer but doesn't actually transmit the data OTA yet.

 

Once that buffer hits its limit (32 bytes), it will force a send.  AutoACK results will be available from another function like "nrf.txAckOK()" or something.

Alternately, the user can force the library to transmit what it has using the flush() function.

 

So to send a single 16-bit integer to a remote transceiver, it will be as simple as:

 

nrf.print(intvar);

nrf.flush();  // Transmit occurs here

if (!nrf.txAckOK()) {  // Oh dear, something bad happened or the remote transceiver is no longer present!

 ...

}

 

On the receiver side though, I think the best approach will be to stick with my current idea--a read() function that expects you to provide a buffer up to 32 bytes long (or "maxlen" if you specify it) and tells you how many were read.  Doing a byte-by-byte read is neither useful (IMO) nor optimal since that means the Nrf24 lib would have to maintain yet another 32-byte buffer internally--for storing the read data.  That means the instance would take up 64 bytes + whatever else, just feels fat for an MSP430.

Share this post


Link to post
Share on other sites

Made a lot of progress-- got two simple demo programs working, one periodically sends "ON" and "OFF" alternating once a second, the other turns on the P1_0 red LED in response to those strings (strcmp(inbuf, str_on) == 0 turns it on, strcmp(inbuf, str_off) == 0 turns it off, str_on/str_off are const's)

 

 

Enrf24_TXdemo:

#include <Enrf24.h>
#include <nRF24L01.h>
#include <string.h>
#include <SPI.h>

Enrf24 radio(P2_0, P2_1, P2_2);  // P2.0=CE, P2.1=CSN, P2.2=IRQ
const uint8_t txaddr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x01 };

const char *str_on = "ON";
const char *str_off = "OFF";

void setup() {
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(1); // MSB-first

  radio.begin();  // Defaults 1Mbps, channel 0, max TX power
  radio.setTXaddress((void*)txaddr);
}

void loop() {
  radio.print(str_on);
  radio.flush();  // Force transmit (don't wait for any more data)
  delay(1000);
  radio.print(str_off);
  radio.flush();  //
  delay(1000);
}

 

 

Enrf24_RXdemo:

#include <Enrf24.h>
#include <nRF24L01.h>
#include <string.h>
#include <SPI.h>

Enrf24 radio(P2_0, P2_1, P2_2);  // P2.0=CE, P2.1=CSN, P2.2=IRQ

const uint8_t rxaddr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x01 };

const char *str_on = "ON";
const char *str_off = "OFF";

void setup() {
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(1); // MSB-first
  
  radio.begin();  // Defaults 1Mbps, channel 0, max TX power
  radio.setRXaddress((void*)rxaddr);
  
  pinMode(P1_0, OUTPUT);
  digitalWrite(P1_0, LOW);
  
  radio.enableRX();  // Start listening
}

void loop() {
  char inbuf[33];
  
  while (!radio.available(true))
    ;
  if (radio.read(inbuf)) {
    if (!strcmp(inbuf, str_on))
      digitalWrite(P1_0, HIGH);
    if (!strcmp(inbuf, str_off))
      digitalWrite(P1_0, LOW);
  }
}

Share this post


Link to post
Share on other sites

Going to roll this up (with examples in the examples/ subdir, create the IDE hints, etc) and put it up on github shortly.

 

This library is not the most flexible way to manipulate the transceiver, as it makes a few simple assumptions:

 

1. Dynamic Payload Length is always enabled

2. Only 1 receive pipe is supported, the MultiCeiver stuff is not (truth is, it's extensible enough that I could add that in the future)

3. Pipe#0 is always using its default (E7E7E7E7E7) address and any data coming in to that addr gets silently flushed.  I recommend not using the E7E7E7E7E7 address for any of your applications.  (This internally has to do with implementing AutoACK properly)

4. User needs to poll available() to see if incoming data is waiting, there are no ISRs that callback/wake-up the MSP430 in the event that data is available.

 

The goal here is simplicity, in keeping with the spirit of Energia.  I think this will fulfill that and give users a quick & simple way to use their radios.

 

Ideally I'd like to implement ISR/IRQ wakeup so the user can put the chip into deep LPM4 sleep, but it looks like the Energia/Arduino API just doesn't facilitate that.  I'd like to be able to attach an interrupt function to the IRQ pin so if it fires, it wakes the chip up, but I'd need some API in attachInterrupt() that tells it to do this (i.e. perform the __bic_SR_register_on_exit(LPM4_bits) in the main P1/P2 ISR function).  Rick tossed some ideas around with me and I'm just going to hold off on implementing any of that.

43two likes this

Share this post


Link to post
Share on other sites

Hi

Can you please send scheme or picture of how to connect nrf24l01+ device to msp430? Or add that on github.

Also will be nice to get some feedback to serial port in examples. It is hard determine if I have connected it ok or not, so i not success use your lib((

Share this post


Link to post
Share on other sites

Ok, I installed Fritzing and was going to hunt for a part for the nRF24L01+ but didn't find one at first peek... so I'll describe it in text.

 

Here's the general idea, for the G2553 chip at least:

 

nRF pin / LaunchPad pin

------------------

Vcc -> Vcc

GND -> GND

 

SCK -> P1.5

MO -> P1.7

MI -> P1.6

(note-- MO and MI should be reversed for the G2xx2 and G2xx1 chips)

CE -> P2.0

CSN -> P2.1

IRQ -> P2.2

 

That should work with the examples out of the box.  Remember the constructor for the Enrf24 class "Enrf24 radio(..., ..., ...);" near the top takes the pins in order of CE, CSN and IRQ.

Share this post


Link to post
Share on other sites

i just have tried with 2 brand new nrf modules &new launchpads. set all wires like you say,not changed code. it not work(

swapped mi/mo,same result

Share this post


Link to post
Share on other sites

Add a Serial.begin(9600); before radio.begin(), then after radio.begin(), add a Serial.println(radio.radioState());

 

Tell me what it spits out...

 

Sent from my C3PO via Tapatalk

 

 

Share this post


Link to post
Share on other sites

Thank you, that allow detect issue.

Here is color scheme of how to connect wires to balck nrf module(first phone it 2 side of nrf, second one is msp430)

https://picasaweb.google.com/arheops/Nrf24L01

 

looking forward for more options. Actualy best variant i am looking is: turn on to 16mhz,setup radio, send packet, change to read, read response from main node, go power saving radio, go 32khz,go LM3 mode, wait Xsec, radio wakeup&send packet(that can be used to create solar powered sensor network)

 

But your current code already huge breakout in code complexity decrease.

 

Checking sleep modes.

This code:

 

void loop() {
  radio.begin();
  radio.setTXaddress((void*)txaddr);
  radio.print(str_on);
  radio.flush();  // Force transmit (don't wait for any more data)
  delay(100);
  radio.deepsleep();
  delay(5000);
  radio.begin();
  radio.setTXaddress((void*)txaddr);
  radio.print(str_off);
  radio.flush();  //
  delay(100);
  radio.deepsleep();
  delay(5000);
}

 

 

take less then <10uA )) sorry,not able measure <10uA current.

 

 

update: looks like i measure it wrong. consumption without lpm3 is 0.7-1.3ma depend of voltage.

post-3-0-35261600-1359125484_thumb.jpg

post-3-0-34227200-1359125485_thumb.jpg

Share this post


Link to post
Share on other sites

After deepsleep, just do another radio.print() and radio.flush() ... during .flush() it will wake it back up, pausing 5ms or so to let it spin up.

 

Also .enableRX() will wake it up and wait 5ms likewise.

 

The low-power stuff I'm not going to do anything with for a while, since IMO Energia doesn't provide enough infrastructure to make it easy for libraries to use/etc.

 

However, if you'd like to implement it yourself, on the TX side there's nothing stopping you from sleeping (going into LPM3, waking up at your leisure and doing more I/O).  On the RX side though, the central idea is to .enableRX(), then set up the port interrupt so when the IRQ line goes low, an ISR is called that does the __bic_SR_register_on_exit(LPM4_bits); ... that's the piece Energia does not sufficiently handle right now.  Keep in mind the transceiver uses ~14mA continuously when in RX mode though, so the power savings you get are just the few mA's that the MSP430 uses when it's in active mode.

Share this post


Link to post
Share on other sites

There WAS problem in wakeup nrf after lm3 sleep when i tested it last time. looks like spi go off after that. same issue if go to 2mhz mode.

but anyway even current power consumption is low enought, i will play with modes letter.

 

my transiver use 18mA when in RX mode. but i am thinking about just start it to rx for 50ms after send packet. so on server side it do send instruction 20 ms after it get packet. working for 50ms/per every 10 second will give enought low power.

yes, i understand that RX take power. but RX side will be connected to wifi,so it need power anyway. while tx part have allow 2 side communication(at least to turn/off connected sensors and change resolution) and will be powered by solar or battery/capacitor.

Share this post


Link to post
Share on other sites

ah yeah, my own grill monitor project has a base station USB-attached to a PC so it sits in RX mode all the time.  The TX wireless unit keeps an RX back-window open for about 300ms (although I don't make use of it yet).  I figure 100ms would be a good "minimum" back-window time to give the base station enough time to process the packet I just sent and then turn around to transmit something back...

Share this post


Link to post
Share on other sites

it can be set like this :

in sensor send, get ack, go sleep 100ms, after that go receive for 20-40ms. on RX side you set sending mode(probably using other nrf module if you have more then one sensors, set retransmit to minimum, start transmit.that way nrf will do transmition when remote sensor go online for receive.

Share this post


Link to post
Share on other sites

gotcha... Just be aware, 250Kbps speed doesn't work with the auto-ack retransmit stuff, I found that out by experimentation and analysis with a logic analyzer months ago.  So keep that sort of scheme running at 1 or 2Mbps (with comparably reduced range).

Share this post


Link to post
Share on other sites

bug:

code without

 

  radio.begin();
  radio.setTXaddress((void*)txaddr);

 

 

not work after 

 

 radio.deepsleep();

so it not work like you say(auto-wakeup on flush)

Share this post


Link to post
Share on other sites

Wow ok, I think we have another problem here.  I just set up a set of those demos on a pair of launchpads with Serial output enabled at 9600bps that periodically kicks out the transceiver state, and on the TX side, what I see is anytime I do serial output, the radio.flush() function hangs indefinitely...

 

Sniffing all the I/O with my logic analyzer, it seems like there's a conflict between Energia's handling of the Serial output (which seems to happen in the background BTW) and the SPI I/O going over the USCI_B port.  Periodically when UART characters go out, a mysterious set of 8 clock ticks kick out over the SCLK line (even though the nRF24 CSN line is high).

 

Edit:

Found the bug.

Energia's USCI ISR handler, used for I2C and for Serial I/O, runs the I2C RX/TX handler when SPI is in use.  It should only run this when I2C is in use.

 

Offending section in /Applications/Energia.app/Contents/Resources/Java/hardware/msp430/cores/msp430/usci_isr_handler.c:

__attribute__((interrupt(USCIAB0TX_VECTOR)))
void USCIAB0TX_ISR(void)
{
        /* USCI_A0 UART interrupt? */
        if (UC0IFG & UCA0TXIFG)
                uart_tx_isr();

        /* USCI_B0 I2C TX RX interrupt. */
        if ((UC0IFG & (UCB0TXIFG | UCB0RXIFG)) != 0)
                i2c_txrx_isr();

}

My modified version:

__attribute__((interrupt(USCIAB0TX_VECTOR)))
void USCIAB0TX_ISR(void)
{
        /* USCI_A0 UART interrupt? */
        if (UC0IFG & UCA0TXIFG)
                uart_tx_isr();

        /* USCI_B0 I2C TX RX interrupt. */
        if ((UCB0CTL0 & UCMODE_3) == UCMODE_3 && (UC0IFG & (UCB0TXIFG | UCB0RXIFG)) != 0)
                i2c_txrx_isr();

}

*** NOTE *** No changes necessary to the USCIAB0RX_ISR handler, and that function should be kept in place as is.  This is only a change to the USCIAB0TX_ISR function.

 

This way the i2c_txrx_isr() crap is only considered if I2C mode is actually enabled, i.e. UCB0CTL UCMODE = 0b11 (3, I2C).

This should be pushed up/reported to the Energia devs...  There might be other considerations for the EUSCI chips, I didn't look at that code.

Share this post


Link to post
Share on other sites

Anyway after modifying the Energia core's usci_isr_handler.c for that, my TX demo works correctly, spitting out serial output while simultaneously forcing the LED on my other launchpad to blink (it's running the RXdemo).

 

Also, my latest GIT commit to the project includes my serial-output-ified version of the examples.

Share this post


Link to post
Share on other sites

no. that is nto bug i reported. i removes serial.

 

okay. here is code:

 

void setup() {
  //Serial.begin(9600);
  //Serial.println('1');
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(1); // MSB-first
  //Serial.println('2');

  radio.begin();  // Defaults 1Mbps, channel 0, max TX power
  Serial.println(radio.radioState());
  radio.setSpeed(1000000);
  radio.setTXaddress((void*)txaddr);

}



void loop() {
   radio.begin();
  radio.setTXaddress((void*)txaddr);
  radio.print(str_on);
  radio.flush();  // Force transmit (don't wait for any more data)
  radio.deepsleep();
  delay(1000);
  radio.begin();
  radio.setTXaddress((void*)txaddr);
  radio.print(str_off);
  radio.flush();  //
  radio.deepsleep();
  delay(1000);
}

 

 

this one is working.

this one is NOT working

 

 

 

void setup() {
  //Serial.begin(9600);
  //Serial.println('1');
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(1); // MSB-first
  //Serial.println('2');

  radio.begin();  // Defaults 1Mbps, channel 0, max TX power
  Serial.println(radio.radioState());
  radio.setSpeed(1000000);
  radio.setTXaddress((void*)txaddr);

}


void loop() {
  radio.print(str_on);
  radio.flush();  // Force transmit (don't wait for any more data)
  radio.deepsleep();
  delay(1000);
  radio.print(str_off);
  radio.flush();  //
  radio.deepsleep();
  delay(1000);
}

 

also if i switch to lpm3, after that nrf never start at all.

Share this post


Link to post
Share on other sites

ignore that. it still compile it with serial becuase there is one call to serial not commented. without serial it work ok(but still not work after power saver mode)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now