Generic routine for receiving short asynchronous transmissions

  _|_|_|_|_| _|    _|  _|_|_|_|        _|_|    _|      _|  _|_|_|_|
      _|     _|    _|  _|            _|    _|  _|_|    _|  _|
      _|     _|_|_|_|  _|_|_|        _|    _|  _|  _|  _|  _|_|_|
      _|     _|    _|  _|            _|    _|  _|    _|_|  _|
      _|     _|    _|  _|_|_|_|        _|_|    _|      _|  _|_|_|_|
                                                           - aka D1


D1 detects short asynchronous transmissions, like commands from remote controls, using IR or RF. The command is stored in ram and compared with EEPROM. If a match is found, the id of the command is returned. D1 can easily learn new commands by changing mode to save received commands in EEPROM instead of matching them.

D1 also works great with RFID and magnetic card readers, temperature loggers, accelerometers and other serial devices if a built in hardware USART is not available in the selected microcontroller.


  • Easy to use
  • Compact size
  • No hardware USART needed
  • Identifies almost any asynchronous transmission
  • Perfect for remote control applications, matching IR or RF commands
  • Highly configurable
  • Debug signaling included for easy debugging

Image showing how an incoming IR remote control signal (ch1) is sampled (ch2)


Receiving and decoding remote control codes are quite tricky. There are numerous different standards and debugging the code takes a lot of time, even with good tools like DSO or logic analyzers. You can find some code for different standards like RC5, but I could not find a decent generic version. A couple years ago, I decided to write my own generic version, it grew to a huge project spanning over several years, but eventually it worked great, and I have used it in numerous projects since.

D1 can be implemented in almost any microcontroller since it does not use any hardware USART. All it needs is a port interrupt to detect incoming transmissions and an 8-bit timer (tmr0) to generate the sampling clock. The routine can be configured in several ways like ignoring the first bits or abort sampling if transmission is interrupted. The clock can be sampled in three different ways depending on the application. The routine is written to be both generic and compact; this is of course a contradiction. If only a specific mode is needed the other modes can easily be deleted to save some space. Some applications need a more dedicated routine; D1 can then be the base that is slightly modified to work with the application. For example I have used it in a project where it was modified to only react on a certain start condition, in this way the risk for triggering on noise is reduced and the routine will more accurately detect all commands.

D1 is written in assembly (for highest performance and smallest size) and can easily be integrated in high level languages, like C using in-line assembler. It consists of two files:

  1. – Samples the data and stores it in ram
  2. – Handles received data, works in two modes:
    • COMPARE  – Compare D1 data with EEPROM (identify/decode commands)
    • SAVE – Stores received data in EEPROM (learning a new command)

D1 uses interrupt (timer & port int) to sample the data. It is very easy to use, initialize the routine and it will sample incoming transmissions. When a transmission has been received in ram, the main program is informed through the D1_RECIEVED flag (set). D1 uses the interrupt pin to sample data. Port on change int can be used instead, but it is less accurate, due to that pic microcontrollers will sometimes miss this event (if a goto instruction is being handled during the interrupt).

Note: Make sure the main program does not change any D1 registers including tmr0 and the prescaler. Disable GIE or INTE/ T0IE in main program when a command is processed to make sure D1 does not run in background.


      D1_INIT_MATCH           ; Initialize D1 for reception (auto)
      D1_TSR                  ; D1 Test Skip If Received
      goto    main_loop       ; Wait for data...
      bcf     INTCON,GIE      ; Disable global interrupt
      D2_COMPARE              ; Identify transmission
      incfsz	D2_CODE,w
      goto	main_match
      goto	main_end      ; Unknown transmission
  main_match:                 ; Transmission identified
      ....                    ; Transmission id in D2_CODE
      D1_CLEAR                ; Clear received flag
      bsf     INTCON,GIE      ; Enable global interrupt
      goto    main_loop       ; Wait for new data...


The tricky part with asynchronous transmission is the lack of a common clock signal. D1 can generate the internal clock using three different modes:

  • AUTOMATIC – the clock is automatically adjusted to the shortest pulse in the transmission.
    + The advantage with this method is that it is easy to use and works with most transmissions.
    – The disadvantage is that it can’t detect several identical bits in the beginning of a transmission. It will only detect a long “1” instead of “111”. This means that all transmissions will not be stored correctly. To compare remote control commands this is most likely not a problem, but if you would like to analyze or save transmissions, the saved data might differ from the real transmission.
    Use D1_INIT_MATCH or D1_INIT_SAVE for this mode
  • FIXED – the clock is locked to a fix value.
    + The advantage is that it will work correct with any transmission sending close to the given frequency.
    –  The disadvantage is that it will not work with different transmissions at different speeds, for example several different remote controls.
  • TRIG – Uses two identical transmissions, the first is used to detect the frequency and the second is used to get the data.
    + The advantage is that it will record correct transmissions even if identical bits are transmitted in the beginning of the transmission. It will also adapt to different transmission speeds, allowing detecting transmissions from different remote controls for instance.
    – The disadvantage is that it requires that each transmission has to be transmitted repeatedly. The detection of a transmission is slower, since it has to be transmitted two times.
    Use D1_INIT_MATCH_TRIG or D1_INIT_SAVE_TRIG for this mode



The received transmissions can be verified before setting D1_RECIEVED flag. This is done using the SAVE macros (verify mode) while the MATCH macros does not verify the transmission. Verify only works if each transmission is sent repeatedly (like most remote control transmissions). With verify enabled a successful reception will only be reported if two following, identical transmissions are recorded. Always use this mode when you are saving data. In most cases there is no need to use it to decode commands since there is a low probability that a bit error will result in matching a different command.



D1 uses interrupt (timer & port change) to sample the data in the following way.

  __   _   ___   _
    |_| |_|   |_|
    ^ ^ ^ ^   ^ ^     <- d1_gpio_int  - port change interrupt
     ^ ^ ^ ^ ^ ^      <- d1_tmr0_int  - timer interrupt (sample)
    |-|               <- IR_PUL_MIN   - sample interval
  • d1_gpio_int – adjusts the tmr0 timer at each port change interrupt (tmr0=(0xff-IR_PUL_MIN)/2). d1_gpio_int finds the shortest pulse and sets the sampling interval (IR_PUL_MIN) to that one
  • d1_tmr0_int – sample and store each bit at tmr0 interrupt. It also finds the end of transmission
  • IR_PUL_MIN – the sample interval (the shortest period of time between two d1_gpio_int). Can be generated in three ways (see INTERNAL CLOCK MODES above)



This is useful to save memory. Skipping the first bits might also solve the problem with two different remote control commands from the same button. (used to detect if key is held, or pressed repeatedly)

  ___   _   _   _
     |_| |_| |_| |
            ^ ^ ^     <- d1_tmr0_int using D1_SKIP_BITS = 3
      0 1 2 3 4 5...
Define D1_SKIP_BITS in main program, like this:
#define     D1_SKIP_BITS    .5      ; Nr of initial bits to skip



Used to avoid sampling wrong bits due to clock mismatch. After X identical bits D1 stops saving data. Besides avoiding sample errors this also saves memory.

   _   _   ______________   _
    |_| |_|              |_|
   ^ ^ ^ ^ ^ ^ .. ^ ^ - - ^           "^" - sample
     0 0 0 1 2 .. 7 8 - - 0           "-" - no sample
Define BITSTOP in LOWER nibble of BITSTOP, like this:
#define     D1_BIT_STOP     0x18    ; Init state for bit counter
If lower nibble is < 8, last bits of transmission won't be saved.
UPPER nibble of BITSTOP is used for EoT count (see beneath).



When the transmission is defined over

   _   _   ____________________
    |_| |_|
   ^ ^ ^ ^ ^ ^ ^ .. ^ ^
           1 2 3 .. 9 eot*
* end of transmission (EoT) after xxx identical bits
Define eot_count in upper nibble of BITSTOP.
eot_count = BITSTOP + upper nibble. Upper nibble has to be >= 1.
example: (eot after 9 identical bits, bitstop after 8 )
#define     D1_BIT_STOP     0x18    ; Init state for bit counter



The sample interval is controlled by the prescaler to tmr0. To get good measurements it is important that a proper sample interval is chosen to match the sampled data. 32 tmr0 counts is a good value for the shortest pulse. For most IR remote controls this is reached with a 1:16 prescaler @ 4MHz

                              Shortest sample        Longest sample
  PS2,1,0     tmr0 rate       tmr0=0xff               tmr0=0x00
  000         1:2             2e-6[s] (@4MHz)         0.512e-3[s]
  001         1:4             4e-6[s]                 1.024e-3[s]
  010         1:8             8e-6[s]                 2.048e-3[s]
  011         1:16            16e-6[s]                4.096e-3[s]
  100         1:32            32e-6[s]                8.192e-3[s]
  101         1:64            64e-6[s]                0.016384[s]
  110         1:128           0.128e-3[s]             0.032768[s]
  111         1:256           0.256e-3[s]             0.065536[s]
example: (Set prescaler to 1:16, bit 7-4:0 3:PSA 2:PS2 1:PS1 0:PS0)
#define     D1_PRESCALE     0x03    ; Set prescaler to 1:16



D1 only saves D1_SIZE bytes in RAM. If transmission is longer, the extra bits are ignored. If transmission is shorter, the remaining bits in RAM are cleared.

example: (save three bytes in RAM + D1_PUL_MIN)
#define     D1_SIZE         .4      ; SIZE in bytes saved in RAM



There are several lines marked with ***DEBUG***. These lines can be deleted to save memory, but they are very useful if you have problems and need debugging. Define D1_DEBUG port in main program and connect it to a sample oscilloscope/ logic analyzer



D1 is written to be both generic and compact. This is of course a contradiction. You can make D1 smaller by deleting modes that you are not using. For instance, if you are not using verify mode, you can make the routine even smaller.


 D1 mode/flag matrix

                                 |   D1_VERIFY_TMP
                                 |   |   D1_USE_TRIG
                                 |   |   |   D1_GET_MIN
                                 |   |   |   |   D1_NO_SAVE
 MODES           NUMBER  TYPE    v   v   v   v   v     Done?
 D1_MATCH        1       SAVE    0   0   0   0   0  >  d1_end_done
 D1_MATCH_STATIC 1       SAVE    0   0   0   1   0  >  d1_end_done
 D1_MATCH_TRIG   1       TRIG    0   0   1   0   1  >  d1_end_more
                 2       SAVE    0   0   1   1   0  >  d1_end_done
 D1_SAVE         1       SAVE    1   0   0   0   0  >  d1_end_more
                 2       VERIFY  1   1   0   0   0  >  d1_end_done
 D1_SAVE_STATIC  1       SAVE    1   0   0   1   0  >  d1_end_more
                 2       VERIFY  1   1   0   1   0  >  d1_end_done
 D1_SAVE_TRIG    1       TRIG    1   0   1   0   1  >  d1_end_more
                 2       SAVE    1   0   1   1   0  >  d1_end_more
                 3       VERIFY  1   1   1   1   0  >  d1_end_done


D1 source code (PIC 12F629 assembler)

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

3 Replies to “Generic routine for receiving short asynchronous transmissions”

Comments are closed.