IR on RI (or IR Control Set and the Robo Interface)

New IR Control Set and the Robo Interface

The Robo Interface (RI) has support for the old IR remote control (RC5 code). There has never been a firmware update for use with the new Control Set. The TX controller has no IR support at all. Someone (I forgot his name) has analysed the new IR protocol and published it on the FT forum. Unfortunately FT deemed it necessary to remove this post. Luckily some people saved the text and I used it to implement receiver software for the RI.

The Protocol

The protocol uses Pulse Distance Modulation (PDM) with two bits (dibit) per pulse, hence four different distances. A transmission consists of 16 pulses (15 distances) for a total of 30 bits. The structure of the message is disclosed in the header file.

//header file

typedef union
{ long asLong;
struct//lowest bit first
{ unsigned stat:2; //low order byte, low word
unsigned tail:1;
unsigned par:1;
unsigned mot2d:1;
unsigned mot1d:1;
unsigned mot4d:1;
unsigned mot3d:1;
unsigned mot2s:4; //high byte, low word
unsigned mot1s:4;
unsigned mot4s:4; //low byte high word
unsigned mot3s:4;
unsigned on:1; //high byte high word
unsigned off:1;
unsigned freq:1;
unsigned rec:1;
unsigned head:4;
} asStruct;
} IR_type;

typedef void(* irqentry)(void);

void copy_irqtable();
irqentry setIRQhandler(char n, irqentry h);
void initIRhandler();

The Concept

The RC5 code was copied to a space in the TransferArea and available as an input to a RoboPro Program (RPP). We probably cannot simply replace this with the new IR code because of the totally different format. But I haven’t tried it so RP may actually return something useful. Instead I used my old approach (see the Wiki on Combining RoboPro with C) and used messages instead. There are four different SubIds for each of the four possible receivers (dip switch settings) and five different MsgIds for the four channels and the buttons (Tempomat on/off). The Tempomat function (if needed) is not implemented in this software and should be implemented in the RPP.

Interrupts

The IR interface uses an interrupt input (INT0) for the demodulated IR signal and a timer A2 with associated interrupt. As we have to replace at least the INT0 handler, the vector table has to be changed. At present the vector table resides in firmware flash but can be moved to RAM where it can be changed. This can be done as follows:

irqentry irqtable[64];

void copy_irqtable() //move the firmware irq table E0020 to near RAM
{ char j;
for (j=0;j<64;j++)
irqtable[j] = ((irqentry far*)0xE0020)[j];
asm("FCLR I");
asm("ldc #_irqtable,INTBL");
asm("ldc #0,INTBH");
asm("FSET I");
}

irqentry setIRQhandler(char n, irqentry h)

{ irqentry old = irqtable[n];
asm("FCLR I");
irqtable[n] = h;
asm("FSET I");
return old;
}

Timer A2

The timer is a free running down counter with a 0.5us clock. The width of a pulse is measured by reading the counter value twice and calculating the difference. It is inevitable that the counter will underflow every now then. This is not a problem at all as long as it happens only once in a measurement. If it happens more often the pulse is clearly too long. The firmware interrupt routine just counts the number of underflows, we could use this handler (the underflow counter is the byte at 0x14A8) but for clarity I decided to implement my own handler.

The Interrupt Handlers

#include "sfr24.h"

#define PULSEW0L 1400
#define PULSEW0U 1700
#define PULSEW1L 1700
#define PULSEW1U 1900
#define PULSEW2L 1900
#define PULSEW2U 2100
#define PULSEW3L 2100
#define PULSEW3U 2400

IR_type IR_code;

int underflow = 0;

#pragma INTERRUPT timerA2
void timerA2(void) //timer (downcounter) to measure length of IR pulses
{ if (underflow < 5)
underflow++;
else if (underflow==5)//pause > intermessage pause (max 120ms), timer takes max 33ms to overflow and more than 165ms to overflow 5 times
{ IR_code.asLong = 0L;//set all values to neutral
IR_code.asStruct.stat = 2;
send_IR_msg();
underflow++;
}
}

#pragma INTERRUPT int0
void int0(void) //interrupt on any edge of IR signal but use only falling edges
{ static unsigned old;
static unsigned long code = 0L;
static char ones = 0;
static int state = 0;
if (!p8_2) //diff represents the pulse + pause length, ! because IR sensor inverts
{ unsigned val = ta2;
int diff = old - val; // diff should be positive and multiples of 500ns because ta2 is a down counter
old = val;
if (diff<PULSEW0L || UNDERFLOW>1) // too short or underflow
{ state = 0;
code = 0L;
ones = 0;
}
else if (diff>=PULSEW0L && diff<PULSEW0U)
{ //code 00 received
state++;
code <<=2;
} else if (diff>=PULSEW1L && diff<PULSEW1U)
{ //code 01 received
state++;
code <<=2;
code +=1;
ones ++;
} else if (diff>=PULSEW2L && diff<PULSEW2U)
{ //code 10 received
state++;
code <<=2;
code +=2;
ones++;
} else if (diff>=PULSEW3L && diff<PULSEW3U)
{ //code 11 received
state++;
code <<=2;
code +=3;
ones +=2;
} else //error, too long, go back to idle state
{ state = 0;
code = 0L;
ones = 0;
}

if (state == 15)//15 pulses and 15 pauses, all bits received
{//check frame and parity
code <<=2;
if ((code>>28) == 8 && !(ones&1) && !(code&4))
{ IR_code.asLong = code;
IR_code.asStruct.stat = 2;
send_IR_msg();
}

else
{ IR_code.asStruct.stat = ones&1 ? 1 : 3; //1=PE,3=FE
}
code = 0L;
state = 0;
ones = 0;
}

underflow = 0; //reset timeout, short timeout is 33-65ms, long timeout is 128-160ms
}
}

Although the pulse width should be constant and only the pulse distance should be variable, it turned out to be much more accurate to measure the time of the pulse plus the distance, hence between two leading edges of the pulses (which are in fact falling edges because the IR receiver inverts). After all bits have been received, the header and the parity are checked and if everything is OK the send_IR_msg() is called to send the code to the RPP.

#include "TA_FirmwareTAF_00D.h"
#include "TA_FirmwareTAF_00P.h"
#include "TA_FirmwareMsg_00D.h"

#define IRBASEPORT 210
#define HWID_IRQ MSG_HWID_RF_SELF

void send_IR_msg()
{ char subid = 0;
static long old[4] = ;
//for test only
sTrans.MPWM_Update = 0x01; // Update PWM values every 10ms
if (IR_code.asStruct.rec) subid++;
if (IR_code.asStruct.freq) subid+=2;
if ((IR_code.asLong^old[subid]) & 0x00f00080L)
{ int s = IR_code.asStruct.mot3d ? IR_code.asStruct.mot3s : -IR_code.asStruct.mot3s;
SendFtMessage(HWID_IRQ, IRBASEPORT+subid, make_message("IR3",s), 0 /*ms*/, MSG_SEND_NORMAL);
}

if ((IR_code.asLong^old[subid]) & 0x000f0040L)
{ int s = IR_code.asStruct.mot4d ? IR_code.asStruct.mot4s : -IR_code.asStruct.mot4s;
SendFtMessage(HWID_IRQ, IRBASEPORT+subid, make_message("IR4",s), 0 /*ms*/, MSG_SEND_NORMAL);
}

if ((IR_code.asLong^old[subid]) & 0x0000f020L)
{ int s = IR_code.asStruct.mot1d ? IR_code.asStruct.mot1s : -IR_code.asStruct.mot1s;
SendFtMessage(HWID_IRQ, IRBASEPORT+subid, make_message("IR1",s), 0 /*ms*/, MSG_SEND_NORMAL);

//for test only
sTrans.MPWM_Main[0] = (IR_code.asStruct.mot1s+1) >> 1; // PWM for Output O1
sTrans.MPWM_Main[1] = (IR_code.asStruct.mot1s+1) >> 1; // PWM for Output O2
if (IR_code.asStruct.mot1s != 0)
sTrans.M_Main = IR_code.asStruct.mot1d ? 0x01 : 0x02; // switch Output O1/O2 ON
else
sTrans.M_Main = 0; // switch Output O1/O2 OFF
}

if ((IR_code.asLong^old[subid]) & 0x00000f10L)
{ int s = IR_code.asStruct.mot2d ? IR_code.asStruct.mot2s : -IR_code.asStruct.mot2s;
SendFtMessage(HWID_IRQ, IRBASEPORT+subid, make_message("IR2",s), 0 /*ms*/, MSG_SEND_NORMAL);
}

if ((IR_code.asLong^old[subid]) & 0x03000000L)
{ int s = 2 * IR_code.asStruct.on + IR_code.asStruct.off;
SendFtMessage(HWID_IRQ, IRBASEPORT+subid, make_message("IRT",s), 0 /*ms*/, MSG_SEND_NORMAL);
}

old[subid] = IR_code.asLong;
}

The function make_message scrambles the argument string into a 16 bit integer and combines it with a 16 bit value to the 32 bit FT (msgid, msg) pair. The msgids could advantageously be precomputed because they are constant.

The interrupt handlers must be installed before RoboPro is started, this is achieved in a small C main() program that first installs the handlers and then calls RoboPro().

void initIRhandler()
{ setIRQhandler(31,int0);
setIRQhandler(7,timerA2);
/*
//precompute message names IR1, IR2 etc.
ir1 = make_message("IR1",0);
ir2 = make_message("IR2",0);
ir3 = make_message("IR3",0);
ir4 = make_message("IR4",0);
irt = make_message("IRT",0);
*/
}

void main()
{ copy_irqtable();
initIRhandler();
RoboPro(); //see other wiki on how this works
}

I hope that eventually something similar to this can be done with the TX controller, either with one of the fast inputs or via an extension. Let’s just hope FT lays the documentation open this time.


Erstellt und hochgeladen von Ad.
Hochgeladen am 11.7.2009.

Hinweis: Wir vertrauen auf die Sachkunde und Sorgfalt unserer Nutzer. Trotzdem könnten sich Fehler eingeschlichen haben. Eine Haftung für die Richtigkeit der Inhalte können wir nicht übernehmen.