keskiviikko 28. huhtikuuta 2021

PS4 controller

I am not fan of console gaming - mainly because I like to quite often take a short break by alt-tabbing out from the game and doing something else. Can't do that on console.

Nevertheless, there are some PC games that just work better with controller than my preferred mouse + keyboard combo. Yakuza series for example. So recently I got myself a PS4 controller which I paired with  my PC, and with Steam it works just great.

It has maybe few dozen gaming hours on it (I do prefer M+K on most games), but it already has developed apparently way too common left stick drift. Makes playing kinda annoying when Kiryo decides to jump left, away from opponent, in middle of fight. This isn't minor case either, stick jumps almost all the way to left when it happens (generally, soon after returning it to center from left).

Well, some searching suggested that analog wiper (the part that senses stick position) is dirty. Certainly shouldn't be work, not with so few hours. Common cure also seems to be air. Sometimes there is suggestion of blowing there, but don't. Air from lungs has moisture in it, so you might just make situation worse that way. I (as it happens) had bottle of air duster - effectively pressurized air - which I used but first round didn't do nothing. I was already opening it up when I remembered that it's very recent purchase - I dug out warranty info and it's less than 6 months old. Might as well replace it entirely.

I did try one more time with air first, though, pressing stick down and turning to side, which appears to have done the trick. No more drift - at least for now. We'll see if it comes back.

Edit, two days later: Started drifting again. Guess I'll use warranty card here...



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! 

 

 

 

tiistai 6. huhtikuuta 2021

433 MHz receiver : other temperature sensor

Continuing directly from last part here.

Later addition: you can find sources here.

So last time I got one temperature sensor working, but later noted that other sensor - one I have at home that is used for outside temperature - didn't work.

Previously I had opened transmitter unit and used modulation input data to easily see what sensor is sending, but this specific sensor was closed so tightly that I could not get inside it without very likely destroying its IP rating in the process - something I didn't want to do.

After brief thinking moment I wrote simple piece of code that measures the "low" pulse durations of receiver data, puts those in a short, 12-sample FIFO and whenever new low pulse is received, it's compared against previous FIFO data; if multiple similar length samples are found in FIFO, it is very likely that I'm seeing actual received data rather than random noise. After slight fine tuning of parameters (minimum pulse length threshold, and number of similar samples that needs to be received) I managed to catch data from this sensor to scope screen.

This specific sensor also uses PPM (pulse position modulation), but unlike previous one this uses sequences half as long; 950us for 'zero' bit (other has 1800us), and 1900us for 'one' (other 3600). Also, it seemed that this transmitter uses 36 bits per word, whereas other used 32 bits per word.

Disclaimer here: This is my interpretation of modulation and data I am receiving, it might be horribly wrong too in details. It does produce data I can use, so I'm happy anyway.

I considered it unlikely that I could write sufficiently flexible receiver that it could decode both of these signals reliably at same time, so I decided to instead refactor code so that I could parametrize receiver to handle slightly different signalling.

So now I have three different state machines running in interrupt handler: One for PWM modulated 24-bit remote signal; one for first 1800/3600us 32-bit PPM; and one for 950/1900us 36-bit PPM. Latter two use same receiver code, but use different parameter block for data parsing.

Now, the actual data decode. This sensor didn't have handy display to show data it's sending, so I ended up just heating sensor (by putting it on top of scope exhaust vent), copying received data to libreoffice calc worksheet and trying to figure out what is changing between samples.

After some experimentation I determined that this one seems to have temperature data on bits 12..23. Again, negative values are twos complement so easy to decode (test method: I put it in freezer for a while). Lowest 12 bits in this case, though, appear to be relatively static - value hovering around 1800..2000 regardless of temperature (over -10 C .. +30 C range). No idea on that. 

So, one more step closer to remote cottage temperature monitoring and control system.