Monday, July 16, 2012

AVR-Stick as an infrared receiver and logger

[Image]

I have already described a project based on the AVR-Stick hardware. The next thing I wanted to try with that hardware is to create an infrared receiver and logger. In fact I wanted to emulate the dongle I described some time ago. But to emulate it I needed to know the protocol that is used. The easiest way to find out what protocol is used is to analyze signals with an oscilloscope of course but I didn't have one. So first of all I needed to implement a signal logging functionality.
In fact signals sent by remote controls are short so it should be possible to record them into ATtiny85's memory (I have allocated 100 bytes from 256 total to record up to 50 pulses)

Hardware

To receive infrared signals different kinds of Vishay IR modules are usually used. Such modules do all the job necessary to demodulate and to amplify IR signal so that it can be directly sent to a microcontroller for furtherer analysis. I didn't have any of those modules but instead I had a small assembled IR module salvaged from an old videocassette recorder.

[Image]
Project parts

on that board there is a NEC uPC2800 preamplifier chip mounted together with a photodiode and all necessary passive elements. Module requires +5V, GND and has one output line which makes it as a perfect replacement for a Vishay module. I have cut modules PCB in halve, resoldered its capacitor on the other side and mounted it onto the AVR Stick connecting its output to the PB4 of the stick:
[Image]
The final hardware

Software

Bootloader

As with my previous AVR Stick project I flashed the bootloader first. But no matter how much I tried it refused to flash my own firmware onto AVR Stick on my Windows host. Despite the fact that I was using the same hardware and software it just failed to operate correctly for some reason. Most probably the bootloader software is buggy at the moment (I'm using USBaspLoader-tiny85.2012-05-13) and it has some timing issues. Workaround was to install AVR development environment on a Linux host:
  1. Install libusb-1.0.0-dev
  2. Install gcc-avr (select avr-libc)
  3. Download avrdude sources
  4. Download bootloader sources
  5. patch avrdude/usbasp.c with the bootloader/avrdude.patch -
    cd avrdude-5.11; patch usbasp.c < avrdude.patch
  6. ./configure
  7. make
  8. sudo cp avrdude-5.11/avrdude   /usr/bin/
  9. sudo cp avrdude-5.11/avrdude.conf   /usr/local/etc

Firmware

The firmware can be used to log received IR signal timings and to decode a Technisat Skystar remote control commands. It should be possible to decode other remote controls commands too by reimplementing the decodeBuffer method. To start with signal decoding I needed to know the protocol used by that specific remote control so first of all I've implemented logging functionality.

Logging

To activate logging the firmware shall be compiled with the NODECODE define:
make USERDEFS=-DNODECODE

Additionally it might be necessary to adjust the following defines:
#define START_LEN 29 // start bit length. Recording is started after start bit
#define MIN_LEN 9 // min length of a pulse. If less then recording is stopped

These defines are set for the particular remote control to reduce false triggering. START_LEN specifies the minimum start bit length and MIN_LEN specifies minimum length of a pulse low or high. When input signal changes its state a timer is started. By next state change the timer value is read and this is where these digits come from. Timer's prescaler is set to 1024 and as far as microcontroller runs at 16.5MHz then the timer increments each 1024/16500000 seconds which is 62uS. So these digits are converted into micro seconds by multiplying by 62. This value will be used later in a awk script to properly calculate and show signal's time span.

Recording is started after receiving of a start bit and is finished when no input state change detected before the timer is overflown (it's approximately 16mS because I'm using 8 bit timer). If a state change detected with a duration less then MIN_LEN before the timer is overflown then such recording is discarded. After recording is taken it will be transfered to a host computer per USB. It's enough to open any text editor and press a button an a remote control to get the recorded signal. For example pressing OK button on my remote results in the following text:
0
32
150
31
151
31
152
30
138
17
152
30
152
17
138
17
138
17

0 denotes start of a signal block and signal highs are denoted by adding 128 to the value. So the maximum pulse's low or high period that can be logged is 127 (approximately 8mS). As you can see the start bit length is 32 and that's why the START_LEN is set to 29. I allocated 100 bytes buffer to record the signal which can store up to 50-1 pulses. Buffer's length still can be increased if needed because the firmware uses 186 bytes of RAM from total 256.

Note: During development I reflashed the stick many times and I had to connect/disconnect it constantly. To make it more convenient I used a USB adapter which is much more easier to connect/disconnect:
[Image]
USB adapter


Plot the signal

To present received signal on a graph I've written a script that parses recorded values and calls gnuplot to create a graph. The script is an awful mix of bash, awk and gnuplot scripts:
alex@xubuntu:~$ cat plotsignals.sh
#!/bin/bash

TC=62 # time constant
GRAPH_HEIGHT=125
GRAPH_WIDTH=800

printParseScript()
{
cat  <<\EOFAWK
BEGIN {counter=0}
{
  if ($1 ~ /^$/) next;
  if ($1 ~ /^0$/) {
    if (counter != 0) {
      print counter*tc " " 0;
      print counter*tc+3000 " " 0;
    }
    print "";
    counter=0;
    print "-3000 0";
  }
  else if ($1>127) {
    print counter*tc " " 0;
    counter=counter-128;
  }
  else {
    print counter*tc " " 1
  }
 
  counter=counter+$1;
}
END {print counter*tc " " 0; print counter*tc+3000 " " 0}
EOFAWK
}

printPlotScript()
{
  local blocks=$(awk 'BEGIN {counter=0}{if ($1 ~ /^$/) counter++} END{print counter}' $1) 
  local script counter

  [ $blocks -gt 0 ] || return
  script="set term wxt size ${GRAPH_WIDTH},$((GRAPH_HEIGHT*blocks))"
  script="${script}\nset yrange [-0.1:1.1]"
  [ -n "$2" ] && {
    script="${script}\nset xrange [:$2]"
  }
  script="${script}\nset multiplot layout ${blocks},1"

  counter=0
  while [ $counter -lt $blocks ]; do
    script="${script}\nunset key"
    script="${script}\nplot '$1' every :$((blocks+1)):0:${counter} with steps"
    counter=$((counter+1))
  done
  script="${script}\nunset multiplot"
  echo -e "$script"
}

GPFILE=/tmp/$(basename $1).gp
awk -v tc=$TC "$(printParseScript)" $1 > $GPFILE && { 
  /usr/bin/gnuplot -persist <(printPlotScript $GPFILE $2) 
}

Script inverts the signal and creates a plot for each signal block in an input file. For example I recorded button OK pressed 4 times on a Technisat remote control into a text file and called the plotsignals.sh with that text file name as a parameter:
[Image]
Technisat remote control OK Button pressed 4 times

And pressing Play button 4 times on the wdtv live remote control results in the following:
[Image]
WDTV Live remote control Play button pressed 4 times

Decoding Technisat infrared protocol

Analyzing signal graphs (check the graph above for the OK button) I realized that this is some kind of a manchester encoding but it wasn't the RC5. There is a start bit and a toggle bit that is followed by number of bits. I'm using a very simple principal to decode the bit stream - every long pulse's low denotes a state transition from 0 to 1 and every long pulse's high denotes a state transition from 1 to 0. To test if this decoding scheme works and to create a lookup table I've written an additional awk script:
alex@xubuntu:~$ cat ~/bin/decodesignals.sh
#!/bin/bash

printDecodeScript()
{
cat  <<\EOFAWK
function abs(value)
{
  return (value<0?-value:value);
}
BEGIN {SS=10;SF=17;LS=23;LF=30;EPS=2;curbit=0;newblock=0;}
{
  if ($1 ~ /^$/) next;
  if ($1 ~ /^0$/) {
    newblock=1;
    curbit=0;
    if (bits != "")
      print bits;
    next;
  }
  if (newblock == 1) {
    newblock=0;
    bits="";
    next;
  }

  time = $1;
  if (time>127)
    time=time-128;

  if (abs(time-SS)<=EPS) {
    bits=bits""curbit;
    next;
  }
  else if (abs(time-SF)<=EPS) {
    next;
  }
  if (abs(time-LS)<=EPS) {
    curbit = 1;
    bits=bits""curbit;
  }
  else if (abs(time-LF)<=EPS) {
    curbit = 0;
    bits=bits""curbit;
  }
  else
    print time;
}
END {print bits}
EOFAWK
}

awk "$(printDecodeScript)" $1 

Feeding the script with the same data file I used to create signal graphs I got the following result:
alex@xubuntu:~$ decodesignals.sh ~/Downloads/button_ok.txt 
101010010111
001010010111
101010010111
001010010111

the start bit is skipped and the first bit is the toggle bit. So far so good. Next I recorded buttons from 0 to 9:
[Image]
Buttons from 0 to 9
and decoding them resulted in:
alex@xubuntu:~$ decodesignals.sh ~/Downloads/button_0-9.txt 
1010000000
01010000001
1010000010
01010000011
1010000100
01010000101
1010000110
01010000111
1010001000
01010001001

It appears to be that different number of bits transferred for these buttons and OK button and it is still not clear how do I properly decode the stream (please drop me a note if you know better). The current solution is to take only the last 6 bits into account that works perfectly beside the fact that there are 2 buttons pairs with intersecting codes.

Next I've manually created a file with decoded ir codes, key codes to be sent to a host and remote control button names:
alex@xubuntu:~$ cat Downloads/decoded_buttons.txt
000000 KEY_0  0
000001 KEY_1  1
000010 KEY_2  2
000011 KEY_3  3
000100 KEY_4  4
000101 KEY_5  5
000110 KEY_6  6
000111 KEY_7  7
001000 KEY_8  8
001001 KEY_9  9
001100 KEY_W  PWR/TOGGL
001101 KEY_M  MUTE
001111 KEY_I  HELP/INFO
010000 KEY_RIGHT VOLUP
#010000 X UP
010001 KEY_LEFT VOLDOWN
#010001 X DOWN
010010 KEY_F2  MENU
010011 KEY_F9  TV/RADIO
010101 X  LEFT
010110 X  RIGHT
010111 KEY_ENTER OK
100000 KEY_UP  CHAN_UP
100001 KEY_DOWN CHAN_DOWN
100010 KEY_F  PREV
100011 KEY_S  INPUTAB
100110 KEY_L  SLEEP
101001 KEY_P  STOP
101011 KEY_F5  RED
101100 KEY_F6  GREEN
101101 KEY_F7  YELLOW
101110 KEY_F8  BLUE
101111 KEY_E  GUIDE
110110 KEY_O  OPTION
111000 KEY_V  EXTERNAL
111100 KEY_T  TELETEXT

And the final step was to convert this description into a lookup table. Again awk is here to help:
 
alex@xubuntu:~$ awk -f /home/alex/Downloads/createlookup.awk ~/Downloads/decoded_buttons.txt > lookup.c
 

where createlookup.awk is:
alex@xubuntu:~$ cat /home/alex/Downloads/createlookup.awk
function bin2dec(value)
{
  result=0;
  pow2bit=1;
  while (length(value))
  {
    bitChar = substr(value, length(value));
    if (bitChar == "1")
      result = result + pow2bit;
    pow2bit = pow2bit * 2;
    value = substr(value, 1, length(value)-1)
  }
  return result
}
BEGIN {prev=0;}
{
  if ($1 ~ /^#/)
    next;
  cur = bin2dec($1);
  while (prev<cur)
  {
    print "0,";
    prev = prev+1;
  }
  print $2",\t\t/*"$3"*/"
  prev = prev+1;
}
END { while (prev<64) {print "0,"; prev=prev+1}}


After the firmware was flashed I've found out that each command is sent 3 times by a remote control so I've added a simple counter to the main.c to discard two from 3 received commands. After this last change was flashed back everything was up and running. I disconnected the old dongle and my router was reacting the same as before to infrared commands (I'm using triggerhappy daemon to start different actions as a reaction to key presses)

[Image]
The final product

[Image]
Still live: temperature logger and IR dongle connected to a USB hub

PS: I've documented everything so scrupulously mainly for myself to not forget how it was done.

No comments: