Author Topic: Questions about some code in RFM69  (Read 5913 times)

TD22057

  • Newbie
  • *
  • Posts: 26
  • Country: us
Questions about some code in RFM69
« on: February 18, 2016, 12:03:42 AM »
I'm in the process of trying to write my own version of the RFM69 class.  Not because there is anything wrong w/ the official one - I just find it a good way to understand things better and I'd like to experiment w/ some different interface ideas.  Along the way, I've run into a couple of minor items that I think could be removed from the library (or it's equally likely that I'm missing something) and a few other questions.

Looking at: https://github.com/LowPowerLab/RFM69/blob/master/RFM69.cpp

1) In init(), there are these lines 93-96:
Code: [Select]
  unsigned long start = millis();
  uint8_t timeout = 50;
  do writeReg(REG_SYNCVALUE1, 0xAA); while (readReg(REG_SYNCVALUE1) != 0xaa && millis()-start < timeout);
  start = millis();
  do writeReg(REG_SYNCVALUE1, 0x55); while (readReg(REG_SYNCVALUE1) != 0x55 && millis()-start < timeout);

But in CONFIG, lines 78-79, there are these lines:
Code: [Select]
    /* 0x2F */ { REG_SYNCVALUE1, 0x2D },      // attempt to make this compatible with sync1 byte of RFM12B lib
    /* 0x30 */ { REG_SYNCVALUE2, networkID }, // NETWORK ID

Does writing SYNCVALUE1 twice with different values do something if it's going to get over written during the CONFIG writing?  And why is the do...while construct necessary in that case but not in any case where a register is being updated?

2) In setAddr() line 185 is calling:
Code: [Select]
 writeReg(REG_NODEADRS, _address);
but the config line 82 has node address turned off.  Obviously doesn't hurt anything - but it could be removed.

3) In interruptHandler() line 314:
Code: [Select]
 if (_mode == RF69_MODE_RX && (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PAYLOADREADY))
I'm curious about why the second register check is needed.  It seems like the only way the interrupt gets called in RX mode is if a payload is actually ready which seems like it makes the check unnecessary.

4) There are several cases where the AGC process is restarted by setting the PACKET2_RXRESTART bit.  The comment for these lines says "avoid RX deadlock".  The calls appears in receiveBegin() and all the send calls. I was wondering if anyone could explain what RX deadlock means and what this fixes?

5) I'm curious in general about the use of the interrupt system.  It's used to trigger a packet received which I understand.  I'm assuming that But in sendFrame, there is a while using a digitalRead on the interrupt pin to see find the send event rather than setting a flag in the interrupt handler to indicate that it was called.  So why use the handler for receive but not transmit?  And - what would happen if there was the same digitalRead() in receiveDone() to test for a received packet?  That would allow for multiple radios to be created in one sketch (as a frequency bridge) by eliminating the static self pointer.  There is also commented out code in sendFrame() that is watching the IRQFLAGS register to see if the packet was sent.  Was this removed because the interrupt/pin system is faster than reading the register?
Code: [Select]
  while (digitalRead(_interruptPin) == 0 && millis() - txStart < RF69_TX_LIMIT_MS); // wait for DIO0 to turn HIGH signalling transmission finish
  //while (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PACKETSENT == 0x00); // wait for ModeReady

FYI - in case anyone cares:  one of my goals is to see how hard it is to remove all of the while/timeout calls which could cause the radio to hold on to the execution for up to a second - especially in send() which might not return for that long because of the CSMA_LIMIT timeout.  I'd prefer not to have my other code not run for that long if I can help it (even if it's unlikely to actually occur).

Other things I'm trying to accomplish:
- Remove macros from the header - I been bitten by macro conflicts (with other libraries) in the past that are hard to debug so I never like having #defines in any part of a public api.
- See if improving efficiency makes any difference.  Each call to readReg/writeReg is triggering an spi select/unselect even though register calls tend to occur in groups.  I'm going to try making it so there is a single select/unselect for each function whenever possible and see if there is any noticeable change speed.
- Have a state change return/callback poll() as the primary API method that can notify the caller on a variety of conditions like message received, message sent, ack received, ack sent, timeouts, errors, able to send, etc.
- Make certain calls more obvious as to what they're doing (at least to me).  For example, there are a several calls like:
Code: [Select]
  writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFE) | (key ? 1 : 0));
When I see this, I always have to check the bit pattern of 0xFE to make sure it's doing what it I think it should be.  However, using an inlined function call (which generates the same assembly instructions) - it's easier for me to see that the following call is setting bit 0 of the register to 1 or 0 depending on the key.
Code: [Select]
  setRegBit( REG_PACKETCONFIG2, BIT0, key ? 1: 0 );

Obviously these are just my opinions - I'm not saying there is anything wrong w/ the existing library at all (and if this comes across as critical of Felix's or anyone else's work - that's not my intent).  If I wanted to actually get something working, I'd just use the library as is.  This is really just more of an interesting experiment for me to try out some ideas and get a better understanding of how the radio works.  They might not work, or might work and be worse, who knows.

emjay

  • Full Member
  • ***
  • Posts: 119
  • Country: nl
Re: Questions about some code in RFM69
« Reply #1 on: February 18, 2016, 10:30:51 AM »
@TDn,

Quote
In init(), there are these lines 93-96:

The clue is in the code section - this is init time and good practice is to validate your environment somewhat.
The write/readback sequences are a quick validation that there is actually a radio module on the SPI bus talking sensibly to you.

What are you using as your radio module reference document?  The answer to some of your other questions will need thumbing that guide well.



TD22057

  • Newbie
  • *
  • Posts: 26
  • Country: us
Re: Questions about some code in RFM69
« Reply #2 on: February 18, 2016, 10:47:09 AM »
@TDn,

Quote
In init(), there are these lines 93-96:

The clue is in the code section - this is init time and good practice is to validate your environment somewhat.
The write/readback sequences are a quick validation that there is actually a radio module on the SPI bus talking sensibly to you.

What are you using as your radio module reference document?  The answer to some of your other questions will need thumbing that guide well.

I don't think the code does that though. It does a write/read sequence in a while loop with a time out.  If it was testing for a valid radio, shouldn't it test the return value and then return false to indicate that the init failed?  If there is no radio, this code is just a delay and then it keeps going.

I'm looking at the v1.3 data sheet.  I haven't made it very far through it yet - it's not exactly light reading.   http://www.hoperf.com/upload/rf/RFM69W-V1.3.pdf

emjay

  • Full Member
  • ***
  • Posts: 119
  • Country: nl
Re: Questions about some code in RFM69
« Reply #3 on: February 18, 2016, 11:25:00 AM »
@TDn,

Hey - you asked what the code snippette was for, not if it actually worked  ;) 
It is based on the same functionality from the original JeeLib library that did not have a timeout function, but hung at this point with an absent/failing RF module/bad SPI bus interaction as a debugging aid.

The documentation is better if you head over to the Semtech site http://www.semtech.com/images/datasheet/sx1231h.pdf for the chip used on the RF module.

executivul

  • Newbie
  • *
  • Posts: 48
  • Country: ro
Re: Questions about some code in RFM69
« Reply #4 on: March 02, 2016, 09:24:25 AM »
I stick my question to this thread hope you won't mind.

In RFM69::sendFrame we have:
Code: [Select]
writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_00); // DIO0 is "Packet Sent"
.......................
while (digitalRead(_interruptPin) == 0 && millis() - txStart < RF69_TX_LIMIT_MS);

coud it be replaced with:
Code: [Select]
while ((readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PACKETSENT) == 0 && millis() - txStart < RF69_TX_LIMIT_MS);

Thank you.
« Last Edit: March 02, 2016, 09:26:18 AM by executivul »

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6461
  • Country: us
    • LowPowerLab
Re: Questions about some code in RFM69
« Reply #5 on: March 02, 2016, 10:38:53 AM »
I stick my question to this thread hope you won't mind.

In RFM69::sendFrame we have:
Code: [Select]
writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_00); // DIO0 is "Packet Sent"
.......................
while (digitalRead(_interruptPin) == 0 && millis() - txStart < RF69_TX_LIMIT_MS);

coud it be replaced with:
Code: [Select]
while ((readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PACKETSENT) == 0 && millis() - txStart < RF69_TX_LIMIT_MS);

Thank you.

At first glance, probably yes. But why would you want to spend the time doing SPI transactions when you can simply read a GPIO which is much faster  :o ???

executivul

  • Newbie
  • *
  • Posts: 48
  • Country: ro
Re: Questions about some code in RFM69
« Reply #6 on: March 02, 2016, 03:28:27 PM »
In my simplified version of your library I don't use interrupts at all, just call interruptHandler() at the beginning of receiveDone(), in the end interruptHandler just moves data out of the radio in local variables and has a simple validation, that could all be done in a simple timer function.

Not using interrupts means I don't need DIO0 at all.
Now blaming myself for buying 8 ch level converters since I could get along with smaller 4ch (MISO,MOSI,SCK,SS).

By the way what happens if I do not respond to an interrupt? I mean radio receives a packet and I do not read it from the FIFO. Does the radio remain in RX and receives the next packet and the first one is lost? Then maybe some auto mode trick to go to standby after a valid packet is received with manual rx restart later after processing is more desirable.

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6461
  • Country: us
    • LowPowerLab
Re: Questions about some code in RFM69
« Reply #7 on: March 02, 2016, 04:45:23 PM »
In my simplified version of your library I don't use interrupts at all, just call interruptHandler() at the beginning of receiveDone(), in the end interruptHandler just moves data out of the radio in local variables and has a simple validation, that could all be done in a simple timer function.

Not using interrupts means I don't need DIO0 at all.
Now blaming myself for buying 8 ch level converters since I could get along with smaller 4ch (MISO,MOSI,SCK,SS).

By the way what happens if I do not respond to an interrupt? I mean radio receives a packet and I do not read it from the FIFO. Does the radio remain in RX and receives the next packet and the first one is lost? Then maybe some auto mode trick to go to standby after a valid packet is received with manual rx restart later after processing is more desirable.
If you don't use interrupts you are on your own :)
What happens if you don't respond to interrupts? That begs the question - then why do you have interrupts enabled? It feels like a bad practice to have interrupts enabled, but not use them, instead poll the registers. You will have to dig the datasheet for the behavior and what to expect. Perhaps you can analyze what RFM69lib does after responding to an interrupt and the states that it puts the radio through.

executivul

  • Newbie
  • *
  • Posts: 48
  • Country: ro
Re: Questions about some code in RFM69
« Reply #8 on: March 03, 2016, 05:36:01 AM »
Sorry for not making myself clear. I don't have the interrupt enabled anymore (so I'm on my own  8) )I just kept the function name interruptHandler() but it's called from receiveDone() instead.

The original lib as I see it: in the interruptHandler() you just test to be in receive mode (interrupt triggered also in sendFrame() from the DIO0 remapping I've asked before), then depending if the packet is ok you store it or discard it and restart listening either way. receiveDone() then sets the radio in standby.

Conclusion 1: As long as I don't call receiveDone() frequently enough I lose all packets except the last one (they get overwritten in the interruptHandler).
Conclusion 2: After calling receiveDone() and getting a true the radio remains in standby untill I call receiveDone() again. Suppose I don't send anything back, neither an ACK.

Is that correct?

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6461
  • Country: us
    • LowPowerLab
Re: Questions about some code in RFM69
« Reply #9 on: March 03, 2016, 08:22:01 AM »
OK let's just include the code for reference:

Code: [Select]
bool RFM69::receiveDone() {
  noInterrupts(); // re-enabled in unselect() via setMode() or via receiveBegin()
  if (_mode == RF69_MODE_RX && PAYLOADLEN > 0)
  {
    setMode(RF69_MODE_STANDBY); // enables interrupts
    return true;
  }
  else if (_mode == RF69_MODE_RX) // already in RX no payload yet
  {
    interrupts(); // explicitly re-enable interrupts
    return false;
  }
  receiveBegin();
  return false;
}

Calling ReceiveDone() repeatedly will eventually fall out of the IF statement completely and just call receiveBegin(), here is that function, notice it resets and flueshes everything and puts the radio in RX:

Code: [Select]
void RFM69::receiveBegin() {
  DATALEN = 0;
  SENDERID = 0;
  TARGETID = 0;
  PAYLOADLEN = 0;
  ACK_REQUESTED = 0;
  ACK_RECEIVED = 0;
  RSSI = 0;
  if (readReg(REG_IRQFLAGS2) & RF_IRQFLAGS2_PAYLOADREADY)
    writeReg(REG_PACKETCONFIG2, (readReg(REG_PACKETCONFIG2) & 0xFB) | RF_PACKET2_RXRESTART); // avoid RX deadlocks
  writeReg(REG_DIOMAPPING1, RF_DIOMAPPING1_DIO0_01); // set DIO0 to "PAYLOADREADY" in receive mode
  setMode(RF69_MODE_RX);
}

I think both your conclusions are correct. If you get a TRUE from Receive done then you should immediately read the packet form the buffers, otherwise a subsequent call will wipe it and put radio in RX.

TD22057

  • Newbie
  • *
  • Posts: 26
  • Country: us
Re: Questions about some code in RFM69
« Reply #10 on: March 03, 2016, 12:22:45 PM »
FYI - there is an interesting approach in this library: https://github.com/iwanders/plainRFM69
which uses the automatic mode switching functionality of the radio to handle RX/TX/STANDBY modes.  All of my radios are on custom PCB's without the right interrupt wiring (DIO2 I think) so I haven't tried it but I thought it was an interesting approach.

TD22057

  • Newbie
  • *
  • Posts: 26
  • Country: us
Re: Questions about some code in RFM69
« Reply #11 on: March 05, 2016, 10:18:57 PM »
One more FYI - the author of the plainRFM69 library emailed me (he doesn't have an account here) and mentioned that he was able to use the automatic mode switching with DIO0 (details here: https://github.com/iwanders/plainRFM69/issues/3).  When I get some free time, I'm going to see how well that library does streaming large data blocks - if auto-mode is a lot faster, it might be a good way to speed up over the air programming.

Felix

  • Administrator
  • Hero Member
  • *****
  • Posts: 6461
  • Country: us
    • LowPowerLab
Re: Questions about some code in RFM69
« Reply #12 on: March 06, 2016, 05:29:51 PM »
Interesting find, if you have more results please post them here.
Thanks :)

executivul

  • Newbie
  • *
  • Posts: 48
  • Country: ro
Re: Questions about some code in RFM69
« Reply #13 on: March 11, 2016, 09:01:42 AM »
Thanks TD for the links, but is a little overkill for what I need in the bootloader.

Another question for Felix:
Why is the address filtering done in the software instead of the radio? Are there any problems having address check in the radio, having nodeID/broadcast stored in registers and radio configured to accept only local or b-cast messages?

TD22057

  • Newbie
  • *
  • Posts: 26
  • Country: us
Re: Questions about some code in RFM69
« Reply #14 on: March 11, 2016, 10:11:52 AM »
Another question for Felix:
Why is the address filtering done in the software instead of the radio? Are there any problems having address check in the radio, having nodeID/broadcast stored in registers and radio configured to accept only local or b-cast messages?

I've tried and it works very well.  I ran some tests with a lot of traffic and it definitely improves things by not even signalling when packets arrive unless they match the address or broadcast address.  IMHO, the only reason not to use it is in promiscuous mode where you're explicitly trying to read every packet.  I think one concrete improvement in RFM69 would be to make hardware filtering on by default and have setPromiscuous(true) change the registers to turn it off.