LowPowerLab Forum

Hardware support => RF - Range - Antennas - RFM69 library => Topic started by: ChemE on June 22, 2017, 06:04:34 PM

Title: New Functions
Post by: ChemE on June 22, 2017, 06:04:34 PM
After a hiatus I am once again working on my atomic, lightweight, faster RFM69 library based heavily on discussions and ideas in this forum.  These routines should be plug-and-play with Felix's library since my rewrite of SPI calls use the same name.

I've read a lot about automatic frequency correction and how crucial it could/should be to very low bitrate instances but I've not found working routines anywhere online.  Below are two the first of which uses joelucid's technique to oversample the on-chip thermometer (https://lowpowerlab.com/forum/moteino/328p-rfm69-now-a-precision-thermometer/) to obtain the temperature in Cx10 with better than one degree precision and nearly 0.1 degree accuracy.  The second use john k2ox's regression of Freq vs. Temperature (https://lowpowerlab.com/forum/moteino/freq-vs-temperature-update/) to correct Fstep.

Get Temperature x 10 in C with roughly 0.1C precision
Code: [Select]
// Return the temperature x 10 in C. Leaves the radio in sleep mode once done to save power
static inline int16_t RadioGetTemp(void) {
  uint16_t total;
  writeReg( REG_OPMODE, SYNTHESIZER_MODE );         // Radio must be in standby or synthesizer to measure               
  for( uint8_t cnt=40; cnt; cnt-- ) {
    writeReg( REG_TEMP1, RF_TEMP1_MEAS_START );     // Initiate a temperature conversion
    while ( readReg( REG_TEMP1 ));                  // Wait for the result to be ready
    total += (uint8_t) ~readReg( REG_TEMP2 ) - 91;  // Add the current result to the running total
  }                                                 // Take 40 such temperature readings
  writeReg( REG_OPMODE, SLEEP_MODE );               // Place radio in sleep mode to save power
  return( (total+2)>>2 );                           // Return temp x 10 in C and account for rounding error

Correct the RF Carrier Frequency Based on the on-Chip Temperature
Code: [Select]
static inline void Correct_Freq(int32_t Base_Freq) {
  uint16_t Tempx10 = RadioGetTemp();    // Get the current radio temperature x10 in C
  uint8_t offset =((float) Tempx10*Tempx10*Tempx10/575261ul - (float) Tempx10*Tempx10/724 - (float) 100*Tempx10/526 + (float) 1531/16 + 0.5);
  uint32_t target = Base_Freq + offset;
  uint32_t Fstep = (uint32_t) readReg( REG_FRFMSB )<<16 | (uint16_t) readReg( REG_FRFMID )<<8 | readReg( REG_FRFLSB );
  if ( target != Fstep ) {  // Change Fstep
    writeReg( REG_FRFMSB, target>>16 );
    writeReg( REG_FRFMID, target>>8 );
    writeReg( REG_FRFLSB, target);   

Next up a nicety for listen mode, a simple little thing that calibrates the RC oscillator for instances when the radio is powered up for a long time and the temperature may drift (listen mode for instance).  The radio must be in standby mode for this routine to do anything otherwise the radio just ignores the call for calibration.

Calibrate RC Oscillator
Code: [Select]
// Transceiver must be in Standby
static inline uint8_t Osc_Cal(void) {
  uint8_t i;
  writeReg( REG_OSC1, RF_OSC1_RCCAL_START );
  while ((readReg(REG_OSC1) & RF_OSC1_RCCAL_DONE) == 0x00) i++;

And finally, I've never had fast and bulletproof results switching operating modes and I really dislike calls to millis to guard against locking up in code.  This routine so far as I know is lightning fast and bulletproof at switching modes.  Notably it works whether or not the radio is in auto mode since it only inspects the flags of interest and ignores others in REG_IRQFLAGS1.

Very Robust Mode Switching
Code: [Select]
static inline uint8_t SwitchMode(uint8_t NewMode) {
  uint8_t timeout=0, mask = RF_IRQFLAGS1_MODEREADY;
  if ( readReg( REG_OPMODE ) == NewMode ) return(255);  // 255 = no switch was needed
  writeReg( REG_OPMODE, NewMode );
  if (NewMode == RECEIVER_MODE)    mask |= RF_IRQFLAGS1_RXREADY;
  while ( ++timeout && ( ~readReg(REG_IRQFLAGS1) & mask ) );
  return(timeout);    // return the number of reads it took to change modes

Hope this is useful to someone!  I'll keep working away at my own library.
Title: Re: New Functions
Post by: WhiteHare on June 23, 2017, 06:57:58 AM
Regarding the switching of modes:  What I've seen done, in for example some of Perky's code, is if the mode switch hasn't yet happened after testing for it, then you invoke the mode switch again (and again and again, if necessary) until the new mode "sticks".  It's never been clear to me whether it was overkill, or actually worse than what you're doing, but then again I've never had it fail on me either.  So, I'll be interested to hear if your approach works better or whether it ever times out and fails to switch modes when it shouldn't have.
Title: Re: New Functions
Post by: ChemE on June 23, 2017, 07:20:42 AM
What I have been able to work out so far is that after issuing a mode switch, if I immediately read the mode ready flag in IRQ1 it is 0.  Keep in mind I've sped my SPI code up as much as I believe is possible (if I'm wrong I'd love to know that!).  I had worried about code just falling through if I read mode ready before it switched off but that can't happen so far as I have been able to test.  As soon as the opmode register is updated that flag toggles false.

I am getting ready to write my own routines for send w/ACK and receive routines that ACK so I'll have some occasion to test mode switching then.  I want to see if I can squeeze a transmission and ACK into a smaller time slice than is currently done.  I plan for this library to also do automatic gain control, automatic temperature correction, and FEI.
Title: Re: New Functions
Post by: gregcope on June 24, 2017, 06:00:05 AM
If this gives a native on board tempmrwadong, it would be good to incorporate it into the main trunk code so that we then have an onboard temp sensor.
Title: Re: New Functions
Post by: ChemE on June 24, 2017, 07:53:57 AM
If this gives a native on board tempmrwadong, it would be good to incorporate it into the main trunk code so that we then have an onboard temp sensor.

This does exactly that.  It takes 3.5ms to get a fairly precise temperature measurement in synthesizer mode which uses 9mA or 4ms to do the same from standby mode which only uses 1.5mA.  I really should change it so it measures from standby, I don't think gaining 0.5ms is worth the huge power penalty.  But this routine could easily be added to the main library.