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!