torstai 21. lokakuuta 2021

DSP optimization : flip bits

Story time.

Long, long time ago a friend and I worked on very different projects, details not important but he worked with extremely low power DSPs, I with larger embedded stuff, but from my reading and on topics we discussed I got some familiarity with DSP too. None that would apply to real work, but basics.

Mind you, I only know (and only roughly) how things were (with this DSP) back then; I haven't followed DSP advances since then, so any parallels to today's hardware are pure guesswork.

DSPs, for those who don't know, were (and are, I guess) most of the time very powerful when doing tasks they are suited to and do it with very small amount of power, but that tradeoff comes with a cost of programming complexity. And all code would be written in straight assembly, absolutely no one was feeling masochistic enough to use C for these babies.

So these DSPs were very finicky. They had basic pipelining inside (i.e. DSP can start processing next instruction(s) while previous is still processed) but they didn't have any pipeline protection. Like first instruction might use register X, and $deity save you if you tried to use register X again before first instruction was finished. This would cause conflict and bad data. And this applies to branching too.

Branhcing was expensive in terms of processing cycles, so you didn't really want to use that in processing loop. DSP had handy "REPEAT n" instruction that could be used instead in tight loops that had no branching cost on top of fixed initial setup cost of few cycles.

Once he mentioned that he needed to do invert bits in 16-bit word. Bit 0 becomes bit 15, bit 1 becomes 14 and so on. This is quite trivial operation in theory; something like "REPEAT 16 ; ROL A ; ROR B"; rotate highest bit from A to carry bit, then carry to B, repeat 16 times. There was no specific instruction for this so it needed to be manual (or said instruction could not be used for some reason).

Except this needed to be inside existing REPEAT loop. There was only one level of REPEAT, you could not have another repeat withing REPEAT. 

Only answer we thought up was to unroll this loop. ROL A; ROR B; ROL A ... total of 16 times. Ugly as sin, but got the job done.

Later I heard that one after one pretty much his entire software team had dropped by, trying to figure out how they could make this prettier. None could, which in the end they grumblingly admitted, so this code stayed in. I got a small chuckle out of this mental picture.


torstai 17. kesäkuuta 2021

rsync from windows to linux

Recently I suddenly found out that I have remote linux server where I need to often upload new files incrementally - only few files change and I'd rather not upload entire tree every time, so SCP and to a degree (s)FTP were out. So, rsync, which I had often heard of but never used. Also, this is firewalled machine with SSH but no usual RSYNC port available.

It turns out that rsync'ing from windows to linux isn't as easy as 1-2-3, but eventually I got it working. To help others (and myself later on too), here's a very brief listing on what I did to make it work. This may or may not work for you, and there may be security issues here too, so take all this with grain or two of salt.

So in short:

1) Set up SSH for public/private key access using ssh-keygen and adding public key to .ssh/authorized_keys of wanted user (linux_user for example). Copy private key to your windows system. I am not sure whether you can use passphrase, I didn't for this test.

2) Make sure rsync is installed in your server and in path: just run rsync command and see that it runs.

2.5) (I'm not exactly sure of this) You may need to define /etc/rsyncd.conf on server and set uid/gid there to match wanted user.

3) On windows, download cygwin and install both rsync and openssh; they are not installed by default so you have to select them explicitly.

4) Add private key (id_rsa from (1) above) to \cygwin64\home\<user>\.ssh\
ssh doesn't like it being public so chmod it:  \cygwin64\bin\chmod 700 id_rsa

5) Test ssh:  \cygwin64\bin\ssh -l linux_user myserver.test . If all went well, you should log directly in (or ssh should as passphrase first).  

5) Use following in your source directory:

rsync -vrtl --chmod=o=r -e "\cygwin64\bin\ssh -v" *  linux_user@myserver.test:/path/to/target

chmod: needed if you are copying, say, html over, to set "read" access to files, in case default doesn't set it. You may leave it out first and add if needed. Similarly --chown=user:group could be used; for me it didn't work properly though (which might be related to -a parameter I used earlier)

Even if ssh is in path and there (apparently) wasn't other conflicting version, this didn't work for me until I specified this full path, in quotes like here. -v here prints out debug data of ssh, it helps massively if you need to troubleshoot issues.

* : All files in current windows directory. 'source/*' works too, but absolute paths or file names cause issues with cygwin.

And at end target user, server and path.

6) If this works, you can take "sent rsync command" (printed out during handshake of above) and add it to authorized_keys: 'command="rsync-printed-above" ssh-rsa AA....'. Test that (5) still works. This improves security a bit, as now only thing that can be run with private key is this single command. Consider also adding, say, no-port-forwarding and other similar flags too.

 

Hope this helps!


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.


tiistai 30. maaliskuuta 2021

433 MHz receiver : different protocol for temperature

Continuing previous parts about receiver and transmitter here.

Later addition: you can find sources here.

Previously I was working with controllable outlets, and those are now working nicely; I can control them remotely from home. 

I had planned to integrate wireless temperature sensors operating on same 433 MHz band to the system. Since my system didn't seem to detect them, I had to open one up to see what kind of protocol it was using. It took me a moment to find the data pin (very different transmitter setup on board than with previous remote control), but finally I got data out from it:

Here yellow signal is (unmodulated) transmit output as probed from inside the sensor (well, very first part of it - full sequence is almost a second long) and green is demodulated data as given out by my receiver. You can once again see how noisy data signal from the receiver is when there is no data in (nearby) air.

I would guess that first yellow pulse, on left, should start transmit, and after that data begins. Receiver however doesn't receive first data pulse correctly, but I don't think that is a problem.

Looking more closely, this signal is quite different from signal sent out by outlet remote control. While it used PWM - pulse width modulation - this looks more like PPM - pulse position modulation. The positive pulses are all of same length - about 560us - but low pulse period between them changes, being either 1900us or 3800us (exactly double - not a coincidence!)

Obviously same receiver code that PWM uses doesn't work. So I just wrote parallel system that runs in same interrupt handler, in a way that they both try to figure out signals at same time. 

In the end the logic to parse this PPM signal was something like this: (again, the interrupt handler triggers on both rising and falling edge transitions of signal, so I can process just that transition and exit interrupt handler so it doesn't eat up all the of MCU time)

-On idle, detect hi-to-low signal transition
-On next rising edge, check low period. If it is between (roughly) 1600-2200us, value is zero; if it is between 3500-4100us, it's one; otherwise it's spurious signal and receiver resets to idle.
Note that my choice of short period denoting 0-bit was arbitrary, it could have been inverse, but in this case my guess ended up being correct.
-Check that received high pulse is between 250-750us (due to relatively low resolution of my timer). If it's too short or long, again reset to idle.
-Repeat until 32 bits are received.

As with the PWM receiver, my code is very simple and resets receiver very aggressively to idle. This is again calculated choice; since the input signal is noise for 99+% of the time, I want to spend as little time - and MCU cycles - in the receive interrupt handler as possible. No point waiting to the end of bad data when I can minimize overhead by just rejecting it early.

In the end I receive usually multiple times same value, for example 921EC7A hex, and possibly some other values. At the interrupt handler I just put these received values in a temporary buffer. Sensor sends same value I think 8 times.

As the transmitter sent data for about 800ms total (all 8 repetitions), I set up one-second timeout (from first received value) to trigger final parsing. There I find the value with most copies listed in temporary buffer, and if there are sufficiently many (say, 3 or more of same) I can assume that was the correct value.

Now, to decode this. It helps a bit that sensor also has small display showing the current temperature so I don't have to guess as much.

For temperature of 24,0 C the corresponding received value was 921E0CE .
I got lucky and next reading was 23,9 C - value being 921DF20 .

Note that E0 and DF in middle are exactly one step apart. So I made a guess: lower 8 bits are something else, then follows maybe 11 bits for temperature. Thus decoded values ended up being 480 and 479 - doesn't sound right.

Okay, 9 lowest bits are something else? Bingo. Dropping highest bits, end result 240 and 239 exactly. 

I have no idea what lowest bits could mean. There does not seem to be much of correlation between different readings so temperature in F or humidity are out. Possibly checksum? Humidity might have been nice, but expecting that from cheap sensor would have been too much. So far, I'm happy with just temperature anyway. 

Received successfully.

Edit: Tested again with different type of temperature sensor, and receiver code didn't recognize it. Will need to see how it operates now...












perjantai 26. maaliskuuta 2021

Covid sucks

Continuation of previous post.

The verdict is in, and we, the people, have collectively failed the pandemic response test, and will all die horribly when the next, more serious pandemic hits. Do not have any illusions about it, that new pandemic will happen eventually, and people traveling constantly around the world will only make it worse (and I do realize that everything I say around here makes me a more guilty person here.)  Whether it's more or less dangerous pandemic than current one is anyones guess, but the worse one will be there eventually. Imagine, say, ebola that spreads much, much more easily... (just to name a single example, I know that ebola isn't airborne - at least, it is not at the moment...)

In the meantime, this situation has serious mental health issues. Not being to able to go out normally, to mingle, to speak with others - it appears to have effect even on us introverts. I at least have some verbal contacts due to customers calling me quite often - wife unfortunately isn't as lucky as she has very few people to meet nearby now. And then there are people too to whom this is much, much worse situation to be in.

Not to mention the holiday situation mentioned in that previous post. That's worse. Not being able to properly relax has more serious consequences than I'd have though few years ago and I am starting to feel the results.

I'm starting to feel that I'd be willing to part a far more significant chunk of my account for a nice, safe vacation than I would have been just a few years back. And I'm fairly certain I'm not the only one.

I was hoping we could make a getaway this winter (20-21), or summer at latest. At the moment that is very unlikely, and that alone is already stressing me out. I'd love, love to be able to get vaccinated and start relaxing (even a bit) but so far the situation is bleak. As of writing, they've vaccinated only 10 percent of people here. Not even close to make sufficient coverage, and we (my family) are very, very far down on the list of people prioritized for vaccinations for now. 

This is going to be another long year, I'm afraid.

tiistai 23. helmikuuta 2021

433 MHz transmitter

 Continuing from previous part.

Later addition: you can find sources here.

After I got receiver working - that is, successfully decoding received data - and played a bit with it, it was time to start thinking about transmitter part. I mentioned that I had neglected to order one, so I went looking for one from same large web store. This is where I hit a wall though.

My system is running at about 3.2v. As it turns out, most of the transmitter and receiver modules available are designed or at least sold to be used with Arduino. Therefore most had operating voltage specified to be 5v, with lower limit at 3.5v. Just a few transmitter modules listed 3.0v as lower operating voltage, but these had somewhat worrying review history and I chose not to risk it. (received I had ordered however was specced to work at 3v and had mostly good reviews so no issue there). All in all, no luck.

Then I realized I had transmitter already. Hand-held module works with 3v battery, and circuit was relatively simple (very close to this SAW-based one from this site). Main difference is that transmitter I had was passing supply voltage of RF through LED (so when LED is on, RF is on). Easy enough to hack, I thought.

So to testing. Left pin is enable (connected to LED series resistor input), other wires are modulation input from my PCB (and other for scope probing). At lower part of board were 3v supply and ground (battery inputs). I think I could have cut the lower part of board completely off but decided against that.

Getting waveform shape roughly right didn't take long (this I did without transmitter being connected, just looking at generated modulation data from scope), but final tweaks to get it just right for receiver to accept it took a bit longer, mostly getting start and end conditions to match original signal closely enough. That code is fairly trivial, just I/O pin switching and delays of various lengths in between, done with interrupts disabled so that they don't mess with timings. Nothing fancy required here, unlike receiver where a bit of timer trickery was needed to be able to receive data buried in noise as background operation (without interfering with other operations of system)


tiistai 9. helmikuuta 2021

433MHz receiver

Later addition: you can find sources here.

I've been thinking about rudimentary home automation stuff, specifically remotely controlled heating to our cabin. Since it has munincipal water it must remain above freezing temperature, but I'd rather not keep temperature too high (i.e. near comfortable) when we're not there to save electricity, so it's often around +10 degrees C when we go there, and it takes almost a full day for it to warm to more comfortable +20 C. Not great situation.

I already have temperature monitoring in place, with remote reporting (to send an alarm if it gets too cold and needs attention, and also for curiosity monitoring the temperature from home), but controlling heaters - that is, mains voltages - is something I'd rather not fiddle with directly. Although I have no double could make it safe, there's still the issue of pesky insurance claim denials in case something were to happen.

For this purpose I bought a set of remote controlled sockets with sufficient power rating. These are controlled with 433MHz radio signal, so I would have to duplicate signals sent by remote somehow.

I did some web searching and there is actually some articles about this, but they all work on higher level than I'd like. "take Arduino and use this library" is not my thing, really. But those anyway gave me enough pointers to start moving to right direction.

I bought some cheap receivers from certain large internet dealer, but somehow managed forgot to buy transmitters! Damn it. But at least I could start with signal decode part.

This turned out to be slightly more difficult than I though, as either my surroundings is full of 433MHz signals, or these receivers are horribly noisy even when there is no signal around, as presented by this scope capture of actual signal amongst noise. Yellow is output signal of receiver, and small green dots indicate locations where there is end of actual transmitted signal as parsed by (my eventual) system.

Here the original transmitter probing came in handy. I knew that sequence started with short high, followed by fixed length low, followed by actual PWM signal. Getting scope trigger to that reliably wasn't happening, but managed to get it trigger on specific-length "low" often enough to verify that my initial assumptions are correct.

Here's a bit closed look at actual transmitted signal. Start of signal is marked with cursor X1, and end (as I think it is) by X2. At start there's short, 2000us zero pulse, followed by 24-bit PWM coded signal, then possibly "stop bit" of some kind. PWM signals are 380us for "0" and 1200us for "1" (or other way around, I don't really care - I just want to be able to replicate it.)  Next pulse starts always 1500us after previous one, regardless of width of pulse.

I already had thought of the receiver software that could parse this (and other similar signals) with MCU I had ready to go - STM32 series chip (and system) I've used often recently. The idea is this:

Connect receiver output to STM32 I/O pin that can handle external interrupts. I set this line to handle both rising and falling edge interrupts, to stop on both edges. 

I already use the RTC part of STM32 with 32768 Hz clock crystal on it. It has sub-second counter (SSR) which I set to to count to 8192 on every second - 122us per tick. Not great resolution but sufficient here (there is also small power cost involved with higher rates when using battery backup, although it is not important here as I do not need to optimize for that), as "0" and "1" signals have significant time difference. Any hardware timer that allows its internal counter to be read reliably should be fine for this though.

As there is a lot of noise, and thus lots of interrupts here, I designed the interrupt handler to be as short as possible for common case - no recognizable signal, or start of false signal - to reduce time spent there.

When idle, system looks for low period of 1800us or more to start decoding signal. (trigger SSR read on lowering edge, then check elapsed time on rising edge, calculate elapsed time in ticks - obsiously taking care of wraparounds in calculation if there are some). 

After this, on lowering edge SSR is read and time compated; I accept fairly wide range of values; around 200us to 500us as "0" and 900us to 1500us as "1". If timing is out of these bounds, signal is treated as noise and system returns to idle (this large scale is in case I want to use modules with slight timing differences, such as radio-based temperature sensor I haven't tested yet. Also there is the issue of SSR having 122us resolution, so I could expect half of that as average timing error, and also other interrupts that might have blocked this handler temporarily)

Next rising edge is also checked; it must have happened from 1300us to 1700us after before. Again, falling outside this range is noise and system returns to idle, waiting next signal. If timing was correct, system waits for lowering edge, i.e. next bit. After 24 bits data treated as successfully received.

This turned out to be more reliable than I expected, as system received all four identical sequences sent by remote extremely reliably, without any false positives so far. For further proof system might check with majority vote the actual received value, but that has to wait for now.

Next, playing with receivers some more.




lauantai 30. tammikuuta 2021

The Final Walk (part 2)

The day came. I couldn't.

This is continuation for this.

He reached age of 12, which isn't too bad for a dog of his size. In the end it was cancer. He just crashed one day, and quick prognosis from doctor was that he might have maybe few more days, at best.

I wasn't there for his final moments as I was talking care of our (human) kids, but nevertheless, it hurt Bad, Seriuously Bad. 

We have other dog, 5 years old, and he was and still is confused of the situation. At least we have him, and will get another (later), but I won't be looking forward of next time this happens either.

This is part part of the bargain we get. They love us all their life, and we must love them back - and accept that they will leave us so soon.