3.09.2012

Odbiornik podczerwieni cd. - dekodowanie RC5

Jak już wcześniej napisałem, chyba najpowszechniejszym protokołem transmisji używanym w pilotach TV jest protokół RC5. Jak mamy piloty Philipsa lub "no name" to prawdopodobnie używają RC5. Można również taki pilot zakupić. Najtańsze już za około 10, 15 zł. Spróbujemy zdekodować taki sygnał.

Bardzo dokładnie protokół został opisany na stronie http://www.sbprojects.com/. Najważniejsze informacje jakie musimy wiedzieć:
  • bit ramki to pojedynczy impuls niski lub wysoki
  • ramka zawsze zaczyna się od bitu 0
  • pojedynczy bit ramki trwa 889us
  • każdy impuls powyżej 1.2*889us to dwa bity
  • odbieramy 28 bitów ramki
  • bity ramki zakodowane są kodem menchester - występują tylko pary 01 i 10
  • pary 01 zamieniamy na 1, a 10 na 0
  • po zamianie bitów ramki otrzymujemy 14 bitów danych
  • dane zawsze zaczynają się dwoma jedynkami i bitem toggle
  • adres urządzenia to kolejne 5 bitów, a kod przycisku to następne 6
Dobra. Sprawdzamy krótkim programikiem czy nasz pilot (piloty) używają RC5. Wykrywamy długość pierwszego impulsu 1 i następującego 0. Jeśli długości będą zbliżone do 889us, to jest to RC5 (pokaże się informacja w oknie output).

public class Program
{
    private const int MinWidth = 640; //us
    private const int MaxWidth = 1140; //us

    private static DateTime _nextCommand = DateTime.MinValue;
    private static int _okImpulses;

    public static void Main()
    {
        using(var receiver = new IRReceiver(Stm32F4Discovery.FreePins.PB5))
        {
            receiver.Pulse += ConsumePulse;
            Thread.Sleep(Timeout.Infinite);
        }
    }

    private static void ConsumePulse(TimeSpan width, bool state)
    {
        DateTime now = DateTime.Now;
        if (now < _nextCommand)
            return;

        long usWidth = width.TotalMicroseconds();
        //czekamy na 1
        if (state)
        {
            if (usWidth > MinWidth && usWidth < MaxWidth)
                _okImpulses++;
        }
        else
        {
            if (_okImpulses == 1)
            {
                if (usWidth > MinWidth && usWidth < MaxWidth)
                    Debug.Print("RC5!");

                _okImpulses = 0;
                _nextCommand = now.AddMilliseconds(500);
            }
        }
    }
}

Jeśli mamy zgodny pilot to budujemy dekoder RC5 do podglądania kodów klawiszy. Najciekawsza jest procedura dekodująca impulsy. Algorytm mógłby się opierać na tablicy 28 bitów ramki, które po zebraniu dekodowalibyśmy na 14 bitów danych. Jednak tablica nie jest potrzebna. Bity zbieramy i dekodujemy parami.

private void ConsumePulse(TimeSpan width, bool state)
{
    long usWidth = width.TotalMicroseconds();

    //poczatek ramki
    if (usWidth > NextFrame || _cnt == 0)
    {
        _cnt = 1;
        _prevBit = false; //zawsze zaczyna sie od 0
        _frame = 0;
        return;
    }

    if (_cnt == 0)
        return;

    //jesli szerokosc impulsu szersza niz 1 bit to dwa bity
    int bitCnt = usWidth > MaxOneBitTime ? 2 : 1;
    for (int i = 1; i <= bitCnt; i++)
    {
        _cnt++;

        if (_cnt%2 == 0)
            DecodeMenchester(_prevBit, state); //co dwa bity dekodujemy bit ramki
        else
        {
            //jesli mamy przedostatni bit to nie czekamy na ostatni mamy całą ramke
            if (_cnt == 27)
            {
                _cnt++;
                //ostatni bit jest przeciwienstwem przedostatniego
                //tutaj błąd w dekodowaniu menchester nie moze się pojawić
                DecodeMenchester(state, !state);
                //mamy ramke
                OnFrame(_frame);
                //zaczynamy od nowa
                _cnt = 0;
            }
            else
                _prevBit = state; //to tylko kolejny bit
        }
    }
}

private void DecodeMenchester(bool bit0, bool bit1)
{
    //kontrola czy jest ok
    if (!(bit0 ^ bit1))
    {
        Debug.Print("Invalid frame data");
        //jesli nie jest ok to zaczynamy ramke od nowa
        _cnt = 0;
    }

    //dekodowanie menchester: 01->1 , 10->0
    if (bit0) 
        return;

    _frame |= 1 << (Framelength - _cnt/2);
}

Jeśli zbierzemy bity całej ramki, to dekodujemy je na adres i kod przycisku i przekazujemy poprzez zdarzenie zainteresowanym.

private void OnFrame(int frame)
{
    int command = (frame & 0x3F);
    bool toggle = (frame & 0x0800) > 0;
    bool extended = (frame & 0x1000) == 0;
    int address = (frame & 0x1F) >> 6;
    //korekta dla extended RC5
    if (extended)
        command |= (1 << 6);

    //Debug.Print(" Addr:" + address + " Cmd:" + command + " Toggle: " + toggle);
    var args = new FrameEventArgs
                   {
                       Command = command,
                       Address = address,
                       Toggle = toggle
                   };

    if (Frame != null)
        Frame(this, args);
}

Krótki, główny programik pozwoli nam wyświetlić te informacje w oknie output.

public class Program
{
    private static DateTime _nextCommand = DateTime.MinValue;

    public static void Main()
    {
        using(var receiver = new IRReceiver(Stm32F4Discovery.FreePins.PB5))
        {
            var detector = new RC5Decoder(receiver);
            detector.Frame += (s, f) =>
                                  {
                                      DateTime now = DateTime.Now;
                                      if (now < _nextCommand)
                                          return;

                                      Debug.Print("Addr:" + f.Address +
                                                  " Cmd:" + f.Command +
                                                  " Toggle: " + f.Toggle);

                                      _nextCommand = now.AddMilliseconds(500);
                                  };

            Thread.Sleep(Timeout.Infinite);
        }
    }
}

Pełny kot: DemoIRReceiverRC5

Brak komentarzy:

Prześlij komentarz