perjantai 16. huhtikuuta 2021

433MHz receiver : sources

Continuing from previous part 1 (rx) , part 2 (tx) , part 3 (different protocol) and part 4 (other sensor) .

Now with sources! This was written for 32bit STM32 MCU, but it should be relatively easily portable to any system (even 16-bit with fixing type definitions). Timing uses RTC's SSR (sub-second counter), but any timer should be usable instead as well. 

This obviously isn't drop-in solution, but should be fairly easily to tweak to suit your needs.

Definitions:
// PWM define
struct rf433_pwm_def
{
  unsigned int rf_ssr0;  
  unsigned int rxValue;
  unsigned char rxState;
  unsigned char rxLen;  
  unsigned char minHeader;        // minimum "header" length, 11
  unsigned char minLen0, maxLen0; // minimum and maximum period of 0 bit
  unsigned char minLen1, maxLen1; // minimum and maximum period of 1 bit
  unsigned char minPos,maxPos;  // min/max positive pulse length
};

struct rf433_ppm_def
{  
  unsigned int rf_ssr0;    
  unsigned long rxValue;
  unsigned char rxState;
  unsigned char rxLen;
  unsigned char rxExpectLength;
  unsigned char minHi,maxHi; // min/max  3..5
  unsigned char minLen0, maxLen0; // minimum and maximum period of 0 bit; 13..17
  unsigned char minLen1, maxLen1; // minimum and maximum period of 1 bit; 29..33
};

struct rf433_pwm_def rf_pwmstate;
struct rf433_ppm_def rf_ppmstate1; // 1900 / 3800us low pulses with 560us high pulse in between
struct rf433_ppm_def rf_ppmstate2; // 940 / 1900us low pulses with 520us high pulse in between

...
    // Default values for receiver states
  
    // pwm receiver, with 380 and 1140us bit pulse widths
  rf_pwmstate.rxState = 0;
  rf_pwmstate.minHeader = 11;
  rf_pwmstate.minLen0 = 1;
  rf_pwmstate.maxLen0 = 5;
  rf_pwmstate.minLen1 = 8;
  rf_pwmstate.maxLen1 = 13;
  rf_pwmstate.minPos = 10;
  rf_pwmstate.maxPos = 15;
  
    // ppm receiver 1; high pulse 520us ; low pulses 1900 and 3800us
  rf_ppmstate1.rxState = 0;
  rf_ppmstate1.minHi = 3;
  rf_ppmstate1.maxHi = 5;
  rf_ppmstate1.rxExpectLength = 32;
  rf_ppmstate1.minLen0 = 13;
  rf_ppmstate1.maxLen0 = 17;
  rf_ppmstate1.minLen1 = 29;
  rf_ppmstate1.maxLen1 = 33;

   // ppm receiver 2; high pulse 520us; low pulses 950 and 1900 us
  rf_ppmstate2.rxState = 0;
  rf_ppmstate2.minHi = 3;
  rf_ppmstate2.maxHi = 5;
  rf_ppmstate2.rxExpectLength = 36;
  rf_ppmstate2.minLen0 = 5;
  rf_ppmstate2.maxLen0 = 9;
  rf_ppmstate2.minLen1 = 13;
  rf_ppmstate2.maxLen1 = 17;
  ...

Some helper functions:
/* --------------------------------------------
 * Actual implementation left to reader.
 *
 * Calculate difference between two timer values, taking care of single overflow.
 */
signed int rtcSsrDiff(signed int first, signed int second)
{
  signed int d = second-first;
  if (d < 0)
    d += SSR_MAX; // counter wrap-around in case of overflow
  return d;
}

/* ----------------------------------------------
 * Read timer value.
 */
signed int rtcReadSsr()
{
    // implement with your customer timer. Preferably direct hardware counter, software counter in timer interrupt handler might cause some issues.
    // Counter period should be at least twice as long than the maximum period you expect to measure (i.e. single low/high pulse length in PWM/PPM),
    // If possible at least a decace or longer (that is, if you expect to measure 6ms pulse, counter should have wrap-around period of at least 60+ms)
  return 0; 
}

PWM handler.
/* -----------------------------------------------------
 * PWM receive handler, using state/definitions in 'def'
 *   ssr     : current timer value
 *   iostate : state of io pin 
 *   def     : pointer to receiver definitions
 */
void rf433_PWM_handler(unsigned int ssr, unsigned char ioState, struct rf433_pwm_def *def)
{
  switch (def->rxState)
    { case 0 : // use low signal with minimum of 5000us (40-ish ticks as start condition) (as per pwm 1 definitions)
          if (ioState == 0) // falling edge
            { def->rf_ssr0 = ssr;
              def->rxState = 1;
            }
          break;

      case 1 : { // rising edge, after short quiet period        
          signed int d = rtcSsrDiff(ssr, def->rf_ssr0);  // Calculate low period time. This handles also overflows (e.g. timer going from 8190 to 2)
          
          if (d < def->minHeader)  // too short "header", ignore and return to idle
            { def->rxState = 0;
              break;
            }
          def->rf_ssr0 = ssr; // first bit start time
          }

          // fall-through to start parse
      case 2 : {                      
          def->rf_ssr0 = ssr;
          def->rxState = 3;
          def->rxValue = 0;
          def->rxLen = 0;
          break; }

      case 3 : {
          signed int d = rtcSsrDiff(ssr, def->rf_ssr0);
          if (ioState == 0) // falling edge - Determine received pulse width and parse as bit
            { // 2..5 (380us nominal) or 8..12 (1140us nominal) (pwm1)              
              if ((d >= def->minLen0) && (d <= def->maxLen0))
                { def->rxValue <<= 1; // 0-bit
                  def->rxLen++;
                }
              else if ((d >= def->minLen0) && (d <= def->maxLen0))
                { 
                  def->rxValue = (def->rxValue << 1) | 1; // 1-bit
                  def->rxLen++;
                }
              else
                { // bad positive pulse length, drop to idle
                  def->rxState = 0;
                }

              if (def->rxLen > 31) // too long data, drop to idle (24-bit values expected)
                def->rxState = 0;                          
            }
          else
            { // next positive pulse should be at 1500us (12 nominal; 10 .. 15) (as per pwm 1 definitions)
              // in case of bad signal drop to idle
              if ((d < def->minPos) || (d > def->maxPos))
                { def->rxState = 0;                  
                }
              def->rf_ssr0 = ssr;
            }

          if ((def->rxState == 0) || (def->rxLen == 24)) // stopped (error) or received expected data; parse
            { 
              // unsigned int i,w;
              if (def->rxLen >= 20) // more than 20 bits of received data; parse it
                { 
                  debugWriteInt("rcv pwm len=", def->rxLen);

                  debugWriteHex("rcv pwm val : ", def->rxValue);

                  // TBD: handle received value : def->rxValue )                 
                }
              def->rxState = 0; // drop to idle
            }
         
          break; }
          
      default:
          def->rxState = 0;                
    }
}
PPM handler.
/* -----------------------------------------------------
 * PPM receive handler, using state/definitions in 'def'
 *   ssr     : timer value
 *   iostate : state of io pin 
 *   def     : pointer to receiver definitions
 */
void rf433_PPM_handler(unsigned int ssr, unsigned char ioState, struct rf433_ppm_def *def)
{
/* Temperature sensors appear to use PPM modulation: (ppm 1 definitions)
 *   -signal drops
 *   -low period of:
 *        1,9ms=0 -     3,8ms=1 (or other way around?)  repeat 32 times; very short low period to end
 *        15,5 ticks (accept 14,15,16)    31 ticks  (accept 30,31,32)
 *   -high pulse of 560us, low period repeats
 *                  4,5 ticks; accept 3,4,5
 */ 

  switch (def->rxState)
    { case 0 :
          if (ioState == 0) 
            { // signal dropped. mark time
              def->rf_ssr0 = ssr;
              def->rxState = 1;
              def->rxLen = 0;
              def->rxValue = 0;
            }
          break;

      case 1 :
          if (ioState == 1) // pulse risen
            { signed int d = rtcSsrDiff(ssr, def->rf_ssr0);  // Low period time in ticks
              def->rf_ssr0 = ssr;

              if ((d >= def->minLen0) && (d <= def->maxLen0)) // 1900us approx (ppm 1)
                { // treat as zero bit; shift value up
                  def->rxValue <<= 1;
                  def->rxLen++;
                  def->rxState = 2;
                }
              else if ((d >= def->minLen1) && (d <= def->maxLen1)) // 3800us approx (ppm 1)
                { def->rxValue = (def->rxValue << 1) | 1;
                  def->rxState = 2;
                  def->rxLen++;
                }
              else
                { // bad pulse period, skip to parse                
                  def->rxState = 3;
                }

              if (def->rxLen >= def->rxExpectLength)
                { def->rxState = 3;  // enough bits, parse data
                  break;
                }

            }
          
      case 2 :              
          if (ioState == 0) // signal dropped
            { signed int d = rtcSsrDiff(ssr, def->rf_ssr0);  // Low period time in ticks                                

              if ((d >= def->minHi) && (d <= def->maxHi)) // high period of ~530us (ppm 1)
                { def->rxState = 1; // next bit
                  def->rf_ssr0 = ssr; // start new time count from this point
                }
              else
                { def->rxState = 3; // bad length; move to end of transmit
                }
            }
          break;
    }

   // End of receive. Done outside switch so we can process this immediately and start waiting for next word.
  if (def->rxState == 3)
    { if (def->rxLen >= 20)
        { 
          debugWriteHex64("rval ppm=", def->rxValue);                    

           // received value in rxValue. Handling TBD.
                    
        }
      def->rxState = 0; // reset receiver for next value
    }
}

 

 Main I/O interrupt handler.

/* --------------------------------------------------------
 * RX interrupt handler. This is set to interrupt on both rising and falling edge of receive pin - F14 in this case.
 */
void rf433_rx_interrupt_handler() 
{  
  unsigned int ioState = ioGetInput(PORTF | 14); // Read RF data line. "Idle" low, expecting high pulses.
  
   // RTC SSR (sub-second counter) is (in my setup) set to run at 1/8192s resolution, i.e. 122us per tick.
   // Other hardware timers can also be used for timing, as long as they're free-running (to specific value)
   // and can be read reliably.

   // For this system to run reliably, timing resolution should be at least 200us (5000 Hz), preferably better.
  signed int ssr;
  rtcReadSsr(&ssr); 

  rf433_PWM_handler(ssr,ioState, &rf_pwmstate);
  
  rf433_PPM_handler(ssr,ioState, &rf_ppmstate1);

  rf433_PPM_handler(ssr,ioState, &rf_ppmstate2);
  
   // This is attempt to catch valid input data from noisy imput signals if one does not have access to transmitter 
   // (for example outside temperature sensor I have that is too well sealed to safely open and close again).
   // This continuously measures period of low signals (high periods ignored), storing those in FIFO and looks for 
   // low periods of almost same length in recent data. If enough same-ish low periods are found, this triggers 
   // output signal that can be used to trigger scope, with other scope channel probing receiver output data.
   // From there data can be analyzed so above receivers can be adapted to received data.
#if 0   
  if (ioState == 0)
    { rf_ssr0 = ssr; // start of low pulse
      ioPin(PORTF | 13, 0); 
    }
  else
    { // rising signal
      signed int d = rtcSsrDiff(ssr, rf_ssr0);  // Low period time in ticks
      const int FIFO_LEN = 12;          
      
      if (rf_len < FIFO_LEN)
        { rf_rxlen[rf_len++] = d; // first, fill fifo with data
        }
      else
        {  // Fifo full. Check if recent data has matches with this pulse.
          unsigned int i;
          if (d > 4) // ignore "too short" (less than 600-ish us here) pulses completely.
            { unsigned int j = 0;
              for (i = 0; i < FIFO_LEN; ++i)
                { if (abs( ((signed)rf_rxlen[i])-d ) < 2) 
                    ++j;
                }

              if (j > 6)
                { debugWriteInt("possible signal, reps=", j);
                  debugWriteInt(" pulse len=", d);
                  ioPin(PORTF | 13, 1);  
                  rf_len = 0; // clear fifo and start over again
                }
            }
          
          if (rf_len) // no trigger; roll fifo
            { for (i = 0; i < FIFO_LEN-1; ++i)
                rf_rxlen[i] = rf_rxlen[i+1];
              rf_rxlen[FIFO_LEN-1] = d;
            }
      
        }
      
    }
#endif      
  
} 

Have fun! 

 

 

 

Ei kommentteja:

Lähetä kommentti