Please enable JavaScript to display the menu.
Sending InfraRed keyboard signal to FM77AV/AV40 with Arduino UNO

ysflight.com

2019/07/23

I have finally succeeded in sending Infra-Red keyboard signal to my FM77AV40 with Arduino UNO.

I have succeeded in the same thing with IRToy v2.  But, this device has a critical flaw that the device commits suicide if '$' (0x49) is accidentally sent to the device.  To resurrect the device, you need to reinstall the firmware from Windows XP.  You should be able to do so from Windows 7, but I saw reports of failure here and there.  Probably you have 50:50 chance of success from Windows 7.  From Windows 10, for sure you cannot resurrect the device.  It is an excellent hardware.  The problem is in the control software.  It shouldn't be set to the firmware-update mode so easily.  It should be like an 8-byte sequence that is unlikely to be sent.  But, it takes only one byte 0x49 to put it into the firmware-update mode that you cannot pull it out of unless you have an XP machine or 7 machine.  Unfortunately the developer, Dangerous Prototypes, is not interested in development of IRToy any more.  This critical flaw probably won't be fixed forever.

My FM77AV Keyboard Emulator blocks 0x49 if it is included in the data stream to be sent to the device.  Therefore IRToy is safe as long as my program is in control.  But, you might end up connecting a terminal program and IRToy, and then typing '$'.  Boom!  IRToy is gone.  It is one unfortunate example of an excellent hardware combined with poorly designed software.

If IRToy is that fragile, how about doing the same with Arduino?


If you are busy and just interested in the final product:

[Infra-Red Emitter Schematic and PCB for Arduino UNO]

[Arduino Source]

First Failure

When I made my FM77AV Keyboard Emulator, with tremendous help from Mr. Apollo, Mr. Kobayashi from Classic PC Club, and Mr. Deskuma, I knew what code needs to be sent over Infra Red signal.  (I want to tell my thanks to Mr. Deskuma, but I cannot contact him.)  So, all I need to do is to send.  Sounds easy right?  I thought IR LED should not be too different, so I picked a random one from Amazon.  I chose a package of 10 IR LED and IR sensors from Gikfun.  I first thought Gifu-ken (Gifu prefecture 岐阜県 in Japanese).  Later I realized it was Gikfun, but it was imprinted to me as Gifuken in my brain.  Anyway, I got them.

I had to send 100us pulses with IRToy v2.  So, for the first attempt, I used micros() Arduino function to turn it on and off.  Micro-second is one 1,000,000th of a second.

But, my FM77AV40 didn't respond.  First try was a failure.

I was assuming that IR LED should take similar current as visible-light LED.  You are not supposed to connect an LED to the data-pin and GND without inserting a current-limiting resistor.  Visible-light LEDs are typically designed to take like 20mA or 0.02A, and the voltage used by the LED is somewhere like 2V.  If you directly connect +5V to LED to GND, 5V is applied to LED, more than double of the designed voltage, and the LED will be fried.  Let's say the LED is consuming 20mA and 2V, then something else must eat extra 3V so that all 5V is not applied to the LED.  What I know about this something is that the current going through this thing is 20mA.  Why?  When some amount of current flows in to the series, the same amount must come out.  Otherwise, electrons will build up in the series.  Resistors and LEDs cannot build up electrons like a capacitor.  Therefore, if 20mA is flowing through the LED, the same 20mA is flowing throught the resistor.  Another thing we know is that the resistor must consume 3V.  3V and 20mA.  Then, from E=I*R, 3V=20mA*Rohms, 3V=0.02A*Rohms, R=3/0.02ohms, R=150ohms.  If you insert 150ohms resistor in the series, LED should light as expected.

LEDs specifications may vary.  If I put 200ohms, it should be safe and I should get bright enough light emission.  So, I was just using a 220ohms resistor for my IR LED.  But, it turned out a typical IR LED takes 100mA, 5 times of the typical visible-light LED, and the voltage should be 1.3V to 1.4V.  If the voltage between data pin and GND are 5V, the resistor must eat 3.6V.  3.6V and 100mA yields 36ohms.  220ohms was way too high.

Then, why not 36ohms?  But, Mr. Apollo suggested that the data pin may supply up to 20mA or so.  Dragging out 100mA might shut the internal breaker.  Hopefully Arduino has a protection for the over current.  But, almost for sure not enough for lighting up an IR LED.

Well, if that's the case, how about using four data pins and four IR LEDs, making it 20mA times four?

No luck.  The light intensity from an LED is not linear.  20mA current won't give 1/5 of the designed brightness.  Most likely it was less than 1% of the intensity compared to 100mA.

So, that was end of my first attempt.

Constant-Current Circuit

So, my first attempt, connecting LED and current-limiting resistor in a series between a data-pin and GND, failed utterly.  I am a programmer, not a hardware person.  But, this time I studied the way to use a transistor as a switch.  Instead of connecting the data pin and LED, data pin is connected to the BASE pin of the transistor, and the EMITTER goes to GND.  Then, I can connect an LED-resistor series between +5V and COLLECTOR pin of the transistor.  +5V or Vcc can supply 100mA easily, and the transistor works as a switch.  I tried it with a visible-light LED, and confirmed the behavior.

To send a keyboard signal to FM77AV, I need 100us (=10KHz) pulses in Infra-Red.  (That's what I was thinking then.  Turned out I needed 1/76Ksed pulse.)

Actually I also looked into the schematic of IRToy v2 since I have already succeeded in sending keyboard signal with this hardware.  This hardware uses surface-mount parts, which I don't think I can solder manually.  So, I rewrote it with compatible through-hole mount parts.

I didn't understand the logic (now I understand.  I'll describe below.), but this will guarantee that 100mA flows through IR LEDs as long as the input voltage can support.  I tried to make this circuit with my Gifuken IR LEDs.  If it works as advertised, each IR LED should consume like 1.3V to 1.4V.

So, I built on my breadboard.  I sent signal.  My FM77AV40 ignored it.

I did another experiment.  I kept the data-pin high.  If 100mA current is flowing through and my LED is emitting a bright infra-red, infra-red signal from IRToy v2 should not be recognized by my FM77AV40 because of the background infra-red intensity.  My FM77AV40 must be blinded by my IR LEDs.

I connected IRToy v2 and sent a keyboard signal, and then....

My FM77AV40 recognized it with no problem.

At least my IR LEDs were emitting something.  I confirmed with the IR sensor on my IRToy.  But, the intensity seems to be too weak to be detected by my FM77AV40.

Is my LED consuming voltage as expected?  I measured the voltage applied to my IR LED.  It was 1V.  But, it looked to be a constant-current because if I make it a series of two IR LEDs, each was taking 1V.

But, 1V?  It should be somewhere around 1.3V or 1.4V.  1V sounds to me too low.  What about the voltage between my data pin and GND?  My tester indicated 2V.  2V!!!!????  Shouldn't it be 5V?  Was Arduino data pin 2V?  I thought there was a possibility of 3.3V, but 2V was impossible.

Many things didn't make sense.  Why many things didn't make sense?  That was because I did not know exactly the specification of the Gifuken IR LEDs.  The specification is listed on Amazon product page.  But, what was unfortunate was it was a package of IR emitter and receiver.  I cannot tell if the forward voltage was meant for the receiver or emitter.  1.2-1.3V 1.5W, Vf=1.1-1.4V, Maximum Power 70mW、Maximum Forward Current 30mA, which ones are for the emitter and receiver?  I couldn't believe the If is 30mA for an IR LED.

(Later I unplugged everything and confirmed the voltage between the data-pin set to HIGH and GND is indeed 5V.)

Constant-Current Circuit (2)

OK.  Ordering a random IR LED was my mistake.  If I use a random part, things go random.  I ordered IR LEDs from Digikey.  I wanted to order the same IR LED as IRToy.  IRToy seems to be using TSAL7400, which was no longer available.  Instead I picked the one that appeared to have a similar specification called VSLB3940.

I replaced my IR LEDs on my breadboard with VSLB3940s, kept the data pin high, and measured the voltage with my tester.  I got 1.32V.  The datasheet tells that VSLB3940 should be taking close to 100mA when 1.32V is applied.  Perfect!

Also, I learned how the constant-current circuit works.  The assumption was that the voltage drop of one typical diode is somewhere around 0.7V, and the voltage drop from base to emitter is also 0.7V.  It was different from what I learned in my junior-high school science class.  The voltage is governed by the power supply, and the resistance (like a small light bulb) doesn't have a jurisdiction to decide how much voltage it eats.  But, it was different for semi-conductors apparently.

If you want to know exactly how much voltage drops through a diode, you need to look up a datasheet for the diode.  But, I looked at some datasheets, and typically for a small current, the voltage starts at 0.6 or 0.7V.  The voltage quickly raise til 10mA or so, and then slowly goes up beyond that.  Or, if you look at it the other way round, if you apply 0.6V or 0.7V, it will pass somewhere between 1mA and 10mA.  If you apply beyond 0.8V, the current rapidly increases.  Again, it is not like voltage first or current first.  It stabilizes at the equilibrium state.  You can think if 1mA current is supposed to be flowing through the diode, it should be taking somewhere around 0.6V or 0.7V.  Or, you can think that if 0.6V or 0.7V is applied, it should be taking somewhere between 1mA and 10mA.

In this circuit, we know that two-diode series connects between the BASE and GND.  GND is 0V.  If I go upstream from 0V by two diodes, the voltage must be 1.4V relative to GND.  Then, what voltage is applied to 6.8ohms resistor?  Since 0.7V drops from BASE to EMITTER, 1.4V-0.7V is 0.7V.  Then, the current flowing through 6.8ohms resistor is 0.7V/6.8ohms is close to 0.1A or 100mA.

Everything will be jeopardized if nothing is between the two-diode series and the data pin because the data pin will raise the voltage 5V where it is supposed to be 1.4V.  So, we put something that eats extra 3.6V, which is a 3.6Kohms resistor.  Let's say there is nothing between the data pin and GND other than 3.6K resistor.  The current will be 5V/3600ohms=0.0014A or 1.4mA.  Very small current.  It is good enough to activate two diodes and transistor to consume 0.7V each.  So, at the equilibrium state 3.6V is applied to the 3.6Kohms resistor, and the current will go down to 0.001A or 1mA.

We know that 100mA should be flowing through 6.8ohms resistor, but where are they coming from?  From data pin, even if entire current goes into the emitter, only 1mA.  100mA can only come from the collector.  Therefore, IR LED series upstream from the collector will need to pass 100mA current.

Since I understood the logic, I also checked voltages applied to other parts.  IR LED was taking 1.32V, 3.6Kohms resistor was taking 3.6V, and 6.8ohms resistor was fluctuating between 0.69V to 0.7V.  These agreed with the theory.  But, the voltage applied to the downstream diode was measured -0.3V, and the upstream 2.5V.  Voltage between base and emitter was 2.5V.  For this kind of semi-conductor, the very small current flowing through the tester might influence the behavior.  But, at least I measured 5V between the data pin and GND.  It was NOT 2V.

Then how strong was the IR emission?  This time, IRToy detected pulses from 1m away (that was the farthest I could pull my IRToy).  IR emission was strong.

Finally I shot my IR pulses to my FM77AV40, and....

No response.

Although my FM77AV40 did not respond to my IR signal, one step forward.  I kept the pin high and kept the IR emission constant ON.  I tried to send a keyboard signal from IRToy, but my FM77AV40 didn't read it!  So, IR signal from my emitter was strong enough to disrupt the signal from IRToy.  It means that it is strong enough to be recognized by my FM77AV40.

What's PWM?  (Succeeded in controlling my cable-TV set-top box)

I made an equivalent constant-current circuit as IRToy.  Strong IR emission is confirmed.  One thing that was hanging in my mind was the measurement of IR signal from the original FM77AV keyboard that Mr. Apollo sent me January of last year.  It was showing that 100us pulse was divided into multiple 25us pulses.  Instead of keeping IR on for the duration of the pulse, IR led of FM77AV keyboard was flickering.  I tried it, but no luck.

However, the IRToy specification was telling something about PWM.  The IRTX pin of the constant-current IR emitter circuit was with "hardware PWM".

What's PWM (Pulse Width Modulation)?

My first search came back with this: a PWM is used for making output signal an approximated sine wave.  No thank you.

But, further search suggested that PWM is used vaguely.  Many articles were suggesting that a conventional infra-red remote control uses 38KHz 50% duty cycle PWM.  What does it mean?  If I want to send a 100us IR pulse, instead of keeping IR LED constant ON for 100us, I need to send shorter burst of IR pulses like a machine gun for 100us.  The rate of the shot is 38KHz, means 38,000 shots per seconds.  50% duty cycle means that the IR LED needs to be ON for 50% of the time.  So, the IR LED must be ON for one 78,000th seconds, and then OFF for the next 78,000th seconds.  Continue ON and OFF for the duration of the pulse.

Why is it called a PWM?  I don't think it is 'modulating' anything.  But anyway it apparently is called a 38KHz 50% duty-cycle PWM in the world if infra red remote.

In 38KHz PWM one shot takes roughly 26us (13us ON, 13us OFF).  If I want to send 100us pulse, I need to send 4 shots, which apparently was double shots compared to the measurement I have received from Mr. Apollo.  Nonetheless, I found a description in IRToy manual saying default PWM of IRToy v2 was 38KHz 50% duty cycle, which is a standard PWM for IR remote controllers.

This time, it looked to be promising.

I also moved one-step back.  Maybe FM77AV40's keyboard signal is very picky.  How about testing with my TV remote?  IRToy v2 failed to capture FM77AV40's keyboard signal before, but it succeeded in capturing and re-creating signals from my cable-TV remote.

So, I tried to send signals to my cable-TV set-top box, which was successful!  Below is the source code.

#define PIN_IR_LED        11
#define PIN_PILOT_LED     13

unsigned char xfinity_guide_button[]=
{
209,161,105,93,11,89,52,152,11,90,52,154,11,86,52,150,11,93,52,145,11,97,104,252,11,93,104,254,11,92,52,151,11,90,52,151,11,92,52,151,11,91,52,147,11,96,52,155,11,86,52,153,11,89,104,255,11,91,52,152,11,91,104,255,11,90,105,2,11,89,255,255,209,167,52,246,11,86,255,255,209,170,52,244,11,89,255,255
};
unsigned char xfinity_exit_button[]=
{
209,141,105,103,11,82,53,57,10,183,105,8,11,84,52,159,11,83,52,170,11,72,105,4,11,85,52,157,11,86,52,161,11,81,52,170,11,70,52,158,11,84,52,159,11,83,52,155,11,87,52,167,11,75,105,6,11,85,52,164,11,221,104,127,11,77,105,26,11,64,255,255,209,161,53,3,11,226,255,255,209,159,52,255,11,79,255,255,209,155,52,254,11,229,255,255,209,160,52,255,11,78,255,255
};
unsigned char xfinity_ok_button[]=
{
209,164,105,96,11,85,105,0,11,91,52,155,11,87,52,151,11,90,52,148,11,94,105,4,11,86,52,145,11,97,52,149,11,93,52,145,11,98,52,155,11,87,52,156,11,85,52,151,11,91,52,145,11,98,52,145,11,97,104,250,11,95,104,254,11,94,104,251,11,94,255,255,209,162,52,240,11,94,255,255,209,166,52,238,11,93,255,255
};

static unsigned long testCycle[64];

void MakeCycleFromRawSample(unsigned char sample[])
{
  int k=0;
  for(int i=0; sample[i]!=0xff || sample[i+1]!=0xff; i+=2)
  {
    testCycle[k]=sample[i];
    testCycle[k]<<=8;
    testCycle[k]+=sample[i+1];

    testCycle[k]*=17;   // 1 tick is supposed to be 0.17us
    testCycle[k]/=100;

    ++k;
  }
  if(0!=(k&1))
  {
    testCycle[k]=0;
    ++k;
  }
  testCycle[k  ]=0xffff;
  testCycle[k+1]=0xffff;
}

void setup() {
  Serial.begin(115200);
  
  // put your setup code here, to run once:
  pinMode(PIN_PILOT_LED,OUTPUT);
  pinMode(PIN_IR_LED,OUTPUT);
}

// Arudino UNO Digital Pin 
//    9=PB1
//   11=PB3

// #define SetIRHigh digitalWrite(PIN_IR_LED,HIGH);
#define SetIRHigh PORTB|=_BV(3);

// #define SetIRLow digitalWrite(PIN_IR_LED,LOW);
#define SetIRLow PORTB&=~_BV(3);

void SendCyclePWM(unsigned long cycle[])
{
  // noInterrupts();
  unsigned long tm=micros();
  for(int i=0; cycle[i]!=0xffff; i+=2)
  {
    auto tm0=tm;
    tm+=cycle[i];

    for(;;)
    {
      auto t=micros();
      if(tm<=t)
      {
        break;
      }
      // auto d=(((t-tm0)<<1)/25)&1; // 40KHz 50% PWM
      // auto d=(((t-tm0)<<1)/26)&1; // 38KHz 50% PWM
      auto d=(((t-tm0)<<1)/28)&1; // 36KHz 50% PWM
      // auto d=((t-tm0)/25)&1; // 20KHz 50% PWM
      if(0==d)
      {
        SetIRHigh;
      }
      else
      {
        SetIRLow;
      }
    }

    SetIRLow;
    tm+=cycle[i+1];
    while(micros()<tm);
  }
  SetIRLow;
}

void loop() {
  digitalWrite(PIN_PILOT_LED,HIGH);
  MakeCycleFromRawSample(xfinity_guide_button);
  SendCyclePWM(testCycle);
  delay(2000);

  digitalWrite(PIN_PILOT_LED,LOW);
  MakeCycleFromRawSample(xfinity_exit_button);
  SendCyclePWM(testCycle);
  delay(2000);

  digitalWrite(PIN_PILOT_LED,HIGH);
  MakeCycleFromRawSample(xfinity_ok_button);
  SendCyclePWM(testCycle);
  delay(2000);

  digitalWrite(PIN_PILOT_LED,LOW);
  MakeCycleFromRawSample(xfinity_exit_button);
  SendCyclePWM(testCycle);
  delay(2000);
}

This program repeats sending "Guide", "Exit", "OK", and "Exit".  I have confirmed that my set-top box was receiving signals as expected.

By the way, the schematic of the circuit connected to my Arduino was as follows.

Visible-light LED D3 is just for testing purpose.

By the way, I have seen many examples using delayMicroseconds() function, which is less likely to work.  This function itself has 4us precision, which means the actual duration of delay may have up to 4us error.  On top of it, depending on what you do between delayMicroseconds() function calls, the error will get bigger and accumulates over time.  Rather, if one pulse got bigger, the next pulse should be made shorter to compensate.

How do you do it?  Instead of measure duration with delayMicroseconds(), calculate at what time point the pulse should stop/start from one reference time point upfront.  Then use micros() to see if the timer reached the time point when you are supposed to turn on/off the light.  Like, if you are making 100us pulse, you do

auto t0=micros();

Then, the first pulse ends at t0+100, the next ends at t0+200, and on.

I have also tested different PWM frequencies.  The set-top box was pretty permissible.  It could take from 5KHz to 40KHz.

ATMega328 CPU Timer

Nonetheless it worked with my cable TV.  It may work with FM77AV40.

Such a hope didn't last long.  My FM77AV40 didn't read my IR signal.

So far, my cable TV responds, infrared is emitted, PWM is working.  Then, all I could imagine was lack of time precision.

I need 38KHz 50% duty cycle.  In one cycle, IR should be ON for 13us and then OFF for 13us.  If I use micros(), it could become as short as 12us, as long as 16us.  If I get an IRQ, the precision may get worse.

And, I first thought 16MHz Arduino should be able to achieve 1us precision easily.  I was wrong.  1us is 1/1,000,000 second or 1/1M second.  16MHz CPU can do 16M/1M=16 clocks of work in 1us.  If I am writing assembly, I can pack many things in 16 clocks, but I am writing in C.  The compiler may not pack instructions efficiently.  Then, what I can do in 1us is very limited.  Although majority of the instructions take only 1 or 2 clocks, let's look at micros() function.  This function could be doing, (1) mask IRQ, (2) push some registers to stack, (3) read timer, (4) write to the return-value location, (5) pop registers, (6) unmask IRQ, and (7) return.  Since ATMega328 is an 8-bit CPU, it takes 4 clocks to read a 32-bit value, and another 4 clocks to write a 32-bit value.  Easily use 16+ clocks.  This function, micros(), itself may have 1us error.

Achieving precise 38KHz 50% duty cycle with micros() seemed to be impossible.  There seemed to be no hope.

Then I found "~" beside the Arduino pin number.  Also under the "~" was "DIGITAL (PWM ~)".  Arduino does have hardware PWM!

My search told me that Arduino's hardware PWM uses CPU's internal timer, and it can toggle a data pin once set up without going through CPU instructions.

Arduino's PWM has two modes, one is CTC (Clear on Time Compare) mode and PWM mode.  PWM mode can generate either a saw-tooth wave or a triangular wave.  I am looking for a 38KHz PWM, so I initially thought I should use PWM mode, but that was wrong.  I need a rectangular wave, which can be generated by CTC mode.  I think Arduino is more accurately using the terms.  Infra-red world looks to be calling this burst shots of infrared emission as PWM, but it's not modulating anything.  I don't under stand why it is called Pulse Width MODULATION.  PWM mode cannot choose frequency flexibly.  Frequency is limited to CPU clocks * integer * 256.  The integer can be one of the pre-scalar values, 1, 8, 32, 64, 128, 256, 1024.  Therefore, I cannot achieve 38KHz 50% duty cycle in the ATMega 328's PWM mode.  Utterly useless.  CTC mode in, PWM mode out.

When I need to choose an arbitrary frequency, you need to go with CTC mode.  CTC stands for Clear on Time Compare.  The CPU timer counts up every machine cycle times the pre-scalar value, and when it reaches the threshold, it goes back to zero.  When it goes back to zero, it can either, set, clear, or TOGGLE one or both of the data pins connected to the timer.  Since there is no CPU intervention once the timer starts, it can achieve 38KHz 50% duty cycle in much higher time precision.

Then, what threshold value to be used to achieve 38KHz 50% duty cycle?  The counter increments every N clocks where N is the pre-scalar value.  The smaller N, the more precise the timing.  For this application, we can go with N=1, or no pre-scaling.  When you cannot use the highest precision?  If the timer threshold value that gives you the desired frequency goes above 255 (65535 for timer 1) since the timer counter is 8 bit (16 bit for timer 1).

In my application, I need 38KHz times 2 equals 76KHz from 16MHz clock.  Therefore, 16M/76K=210.53.  With 1x pre-scaling, the pin output needs to be toggled every 211 counts, which is below 255.  One of the 8-bit counter timer can handle it.  The error will be 0.53/16M seconds.  In microseconds, 0.53/16us=0.03us.  Should be small enough.

It takes care of 38KHz 50% PWM.  I'll use Timer 1 for this purpose.  Then how can I measure 100us?  I decided to use Timer 2.  I mask IRQ when starting transmission, and run a busy wait to measure 100us.  If I use 1x pre-scalar, as soon as I check the timer value, the timer value changes in the next instruction, which would cost time precision.  Rather if I choose 8x pre-scalar, the timer value stays the same for 8 CPU clocks at least.  If I choose 8x pre-scalar, it would give 0.5us precision.  200 ticks corresponds to 100us.  I need 100us pulse, but some times the pulse stays on for two terms.  Then, I need to measure 200us.  8-bit counter cannot measure 400.  So, I need to reset the timer to 0 before it overflows.  But, as long as I can zero the timer value within 8 clocks, the loss should be minimum.  I don't want to go up to 32x pre-scaling.  2us precision wouldn't be good enough.

And, below is the code.

#define SetPin9and10Low PORTB&=~(_BV(1)|_BV(2));

#define START_TIMER1 {TCNT1=PWM_TIMER_THRESHOLD-1;TCCR1A=bit(COM1A0)|bit(COM1B0);TCCR1B=bit(CS10)|bit(WGM12);}
#define STOP_TIMER1 {TCCR1A=0;TCCR1B=0;TCNT1=0;}

void SendCycleHWPWM(unsigned long cycle[])
{
  noInterrupts();

  for(int i=0; cycle[i]!=0xffff; i+=2)
  {
    TCNT2=0;
    START_TIMER1;
    auto w=cycle[i];
    while(TCNT2<(w<<1))
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=120;
      }
    }

    STOP_TIMER1;
    SetPin9and10Low;

    TCNT2=0;
    w=cycle[i+1];
    while(TCNT2<(w<<1))
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=120;
      }
    }
  }	
  SetPin9and10Low;

  interrupts();
}

cycle[i*2] and cycle[i*2+1] are duration of ON and OFF respectively in micro seconds.  To avoid function-call overhead everything is directly written to the CPU registers.  No digitalWrite().  As soon as I start Timer 1, it start burst-shooting 38KHz 50% duty cycle infrared.  (By the way Timer 1 is connected to pins 9 and 10.)

The value of Timer 2 is monitored in the busy wait.  To be far enough from 255, when it reaches 240, the timer value (TCNT2) is reset to 0, and duration count w is reduced accordingly.

I have also tested this code with my cable TV.  Confirmed to work.

Then, I sent key strokes of "fm77av", and finally, FINALLY!  My FM77AV40 recognized my infra-red signal from Arduino!!!


My Infrared Emitter in front of FM77AV40

However, it dropped one stroke every a few strokes.  It was not practically ready.

Last Piece: 100us, 125us, 175us Pulse Widths

At this time I have something that my FM77AV40 could recognize.  But, it dropped strokes too often to be practical.  I thought of two possibilities: (1) is it because I was not sending key-release code? or (2) is it because I am sending all 100us pulse instead of 100us, 125us, 175us combination as suggested by Mr. Kobayashi?

By the way, a key code for FM77AV is 30bit.  If I send every 3 bits in 100us, 125us, and 175us pulses, each 3 bits takes 400us.  Times 10 equals 4000us.  If I send pulses in 100us width, I need to repeat every third bit, or I need to send every 3 bits in 100us, 100us, and 200us pulses, which makes up 4000us total.

When I was sending pulses in IRToy, all 100us pulses worked with no problem.  I first tried to insert key-release codes after every key-press codes, which didn't change the situation.  My FM77AV40 was still missing every a few strokes.

Then I tried 100us, 125us, and 175us pulse-width combination, which finally eliminated the missed strokes!  My Arduino kept sending "fm77av" key strokes, and my FM77AV40 received all strokes without missing any.


No missing strokes with 100us, 125us, 175us pulse widths

// 50% duty cycle: On and off within 1/38000 sec.  Must togle every 1/76000 sec.
#define TIMER_THRESHOLD_38K_WITH_1xPRESCALE ((F_CPU/38000)/2)
// Experiment with FM77AV40 IR LED receiver:  19000Hz No response 34000-36000 drops key.  38000 perfect.  40000 drops keys.

#define PWM_TIMER_THRESHOLD TIMER_THRESHOLD_38K_WITH_1xPRESCALE

// Succeeded Xfinity remote

#define PIN_OC1A 9
#define PIN_OC1B 10
#define PIN_OC2A 11
#define PIN_OC2B 3

// PIN9=PB1 (PortB bit 1)
#define SetPin9High  PORTB|=_BV(1);
#define SetPin9Low   PORTB&=~_BV(1);
// PIN10=PB2 (PortB bit 2)
#define SetPin10High PORTB|=_BV(2);
#define SetPin10Low  PORTB&=~_BV(2);
// PIN11=PB3 (PortB bit 3)
#define SetPin11High PORTB|=_BV(3);
#define SetPin11Low  PORTB&=~_BV(3);
// PIN3=PD3 (PortD bit 3)
#define SetPin3High  PORTD|=_BV(3);
#define SetPin3Low   PORTD&=~_BV(3);


#define SetPin9and10Low PORTB&=~(_BV(1)|_BV(2));



void setup() {
  Serial.begin(1600000); // 115200);
  
  pinMode(13,OUTPUT);
  pinMode(PIN_OC1A,OUTPUT);
  pinMode(PIN_OC1B,OUTPUT);
  pinMode(PIN_OC2A,OUTPUT);
  pinMode(PIN_OC2B,OUTPUT);

  // Reset timers 1 and 2
  TCCR1A=0;
  TCCR1B=0;
  TCCR2A=0;
  TCCR2B=0;
  TCNT1=0;
  TCNT2=0;

  // Timer 1 (8-bit) for 38K PWM IR LED output
  // ATmega 328 datasheet pp.134
  OCR1A=PWM_TIMER_THRESHOLD;
  OCR1B=0;

  // Timer 2 for measuring 1us tick.
  TCCR2A=0;
  TCCR2B=bit(CS21);
  // CS20=0,CS21=1,CS22=0 means 8x pre-scaling.  0.5us per tick.  ATmega328 datasheet pp.162
  OCR2A=0;
  OCR2B=0;
}

#define START_TIMER1 {TCNT1=PWM_TIMER_THRESHOLD-2;TCCR1A=bit(COM1A0)|bit(COM1B0);TCCR1B=bit(CS10)|bit(WGM12);}
#define STOP_TIMER1 {TCCR1A=0;TCCR1B=0;TCNT1=0;}
// ATmega 328 datasheet pp.134
// TCCR1A  COM1A0=1, COM1B0=1 means toggle OC1A and OC1B on compare match
// TCCR1B  WGM10=0,WGM11=0,WGM12=1 means CTC mode
//         CS10=1,CS11=0,CS12=0 means 1x pre-scale (no scaling).
//         CS10=0,CS11=0,CS12=0 means timer stop.



// cycle[2*i] HIGH duration in microseconds  cycle[2*i+1] LOW duration in microseconds
// cycle[2*k]==0xffff means end of the transmission.
// Last off duration is unnecessary.  Therefore set cycle[2*k-1]=0.
void SendCycleHWPWM(unsigned long cycle[])
{
  noInterrupts();

  for(int i=0; cycle[i]!=0xffff; i+=2)
  {
    TCNT2=0;
    START_TIMER1;
    register auto w=(cycle[i]<<1);
    while(TCNT2<w)
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=240;
      }
    }

    TCNT2=0;
    STOP_TIMER1;
    SetPin9and10Low;

    w=(cycle[i+1]<<1);
    while(TCNT2<w)
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=240;
      }
    }
  }
  SetPin9and10Low;

  interrupts();
}




/* Repeat every 3rd of 4-byte groups.
   The raw pulse looks to be going 100us,125us,175us.  Thanks Mr. Kobayashi of Classic PC and Retro Gaming JAPAN!
   But, approximating it with 100us 100us 200us looks to work just fine.
*/
void FM77AV30bitTo40bit(char code40[41],const char code30[])
{
  int k=0;
  for(int i=0; i<30; i+=3)
  {
    code40[k++]=code30[i];
    code40[k++]=code30[i+1];
    code40[k++]=code30[i+2];
    code40[k++]=code30[i+2];
  }
  code40[40]=0;
  if(40!=k)
  {
    Serial.println("Internal error!\n");
    Serial.println("Did I count correctly?\n");
  }
}

unsigned int FM77AVGet1stByteIn30bitEncoding(const char code30[])
{
  unsigned int sum=0;
  unsigned int bitShift=0x80;
  for(int i=4; i<12; ++i)
  {
    if('1'==code30[i])
    {
      sum+=bitShift;
    }
    bitShift>>=1;
  }
  return sum;
}

void FM77AVPut1stByteIn30bitEncoding(char code30[],unsigned int byteData)
{
  unsigned int bitShift=0x80;
  for(int i=4; i<12; ++i)
  {
    if(0!=(byteData&bitShift))
    {
      code30[i]='1';
    }
    else
    {
      code30[i]='0';
    }
    bitShift>>=1;
  }
}

void FM77AVMakeKeyReleasePattern(char code30[],const char pressPtn[])
{
  /* Thanks to Mr. Kobayashi from Classic PC & Retro Game Club JAPAN
     for calculation of the key-release code.
  */

  auto firstByte=FM77AVGet1stByteIn30bitEncoding(pressPtn);
  firstByte-=0x60;
  firstByte&=255;

  for(int i=0; i<31; ++i)
  {
    code30[i]=pressPtn[i];
  }
  FM77AVPut1stByteIn30bitEncoding(code30,firstByte);
}

void MakeOnOffCycle40(unsigned long cycle[],const char code30[])
{
  char code40[41];
  FM77AV30bitTo40bit(code40,code30);

  int k=0;
  bool irLedState=true;
  unsigned long accum=0;
  for(int i=0; i<31; ++i)
  {
    cycle[i]=0;
  }
  for(int i=0; i<40; ++i)
  {
    auto nextIrLedState=('1'==code40[i]);
    if(nextIrLedState!=irLedState)
    {
      ++k;
    }
    cycle[k]+=100;
    irLedState=nextIrLedState;
  }
  if(0==k%2)
  {
    ++k;
    cycle[k]=0;
  }
  cycle[k+1]=0xffff;
  cycle[k+2]=0xffff;
}

void MakeOnOffCycle30(unsigned long cycle[],const char code30[])
{
  // int us[3]={80,145,175};  // Drops keys.
  // int us[3]={90,135,175};  // Works!
  int us[3]={100,125,175}; // Works!  Based on Kobayashi-san.
  // int us[3]={110,115,175}; // Works!
  // int us[3]={120,105,175}; // No response
  // int us[3]={130,95,175};  // No response

  // int us[3]={150,50,200};   // No response.
  // int us[3]={125,75,200};   // No response.
  // int us[3]={110,90,200};   // Works!
  // int us[3]={108,92,200};   // Drops keys.
  // int us[3]={105,95,200};   // Drops keys.
  // int us[3]={100,100,200};  // Drops keys.
  // int us[3]={90,110,200};   // Works!
  // int us[3]={80,120,200};   // Drops keys.
  // int us[3]={70,130,200}; // No response.

  // int us[3]={100,110,190};  // Works!
  // int us[3]={100,120,180};  // Drops keys.


  int k=0;
  bool irLedState=true;
  unsigned long accum=0;
  for(int i=0; i<31; ++i)
  {
    cycle[i]=0;
  }
  for(int i=0; i<30; ++i)
  {
    auto nextIrLedState=('1'==code30[i]);
    if(nextIrLedState!=irLedState)
    {
      ++k;
    }
    cycle[k]+=us[i%3];
    irLedState=nextIrLedState;
  }
  if(0==k%2)
  {
    ++k;
    cycle[k]=0;
  }
  cycle[k+1]=0xffff;
  cycle[k+2]=0xffff;
}

void MakeOnOffCycle(unsigned long cycle[],const char code30[])
{
  MakeOnOffCycle30(cycle,code30);
}


void loop() {
  const char *code30f=    "101010110101010110110110101001";  // F
  const char *code30m=    "101010110101001010110110110101";  // M
  const char *code30full7="101010110110110101010110110101";  // Full-key 7
  const char *code30num7= "101010110101001001010101010101";  // Num-key 7
  const char *code30a=    "101010110110101001001001010101";  // A
  const char *code30v=    "101010110101010101001010101001";  // V

  unsigned long cycle40[40];
  char releaseCode30[31];

  unsigned long keyInterval=10000;

  digitalWrite(13,HIGH);
  MakeOnOffCycle(cycle40,code30f);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+40; );

  FM77AVMakeKeyReleasePattern(releaseCode30,code30f);
  MakeOnOffCycle(cycle40,releaseCode30);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+keyInterval; );


  digitalWrite(13,LOW);
  MakeOnOffCycle(cycle40,code30m);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+40; );

  FM77AVMakeKeyReleasePattern(releaseCode30,code30m);
  MakeOnOffCycle(cycle40,releaseCode30);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+keyInterval; );


  digitalWrite(13,HIGH);
  MakeOnOffCycle(cycle40,code30full7);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+40; );

  FM77AVMakeKeyReleasePattern(releaseCode30,code30full7);
  MakeOnOffCycle(cycle40,releaseCode30);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+keyInterval; );


  digitalWrite(13,LOW);
  MakeOnOffCycle(cycle40,code30num7);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+40; );

  FM77AVMakeKeyReleasePattern(releaseCode30,code30num7);
  MakeOnOffCycle(cycle40,releaseCode30);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+keyInterval; );


  digitalWrite(13,HIGH);
  MakeOnOffCycle(cycle40,code30a);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+40; );

  FM77AVMakeKeyReleasePattern(releaseCode30,code30a);
  MakeOnOffCycle(cycle40,releaseCode30);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+keyInterval; );


  digitalWrite(13,LOW);
  MakeOnOffCycle(cycle40,code30v);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+40; );

  FM77AVMakeKeyReleasePattern(releaseCode30,code30v);
  MakeOnOffCycle(cycle40,releaseCode30);
  SendCycleHWPWM(cycle40);
  for(auto t0=micros(); micros()<t0+keyInterval; );
}

IRToy v2 was able to send key strokes with all-100us pulses.  Why my Arduino cannot is still a mystery.  I'll scan it with an oscilloscope to see the difference, but for the meanwhile, I am getting a satisfactory results.

I have also tested several different pulse-width combinations and got the following results.

 80,145,175 Drops keys
 90,135,175 Works perfectly
100,125,175 Works perfectly
110,115,175 Works perfectly
120,105,175 No response at all
130, 95,175 No response at all

150, 50,200 No response at all
125, 75,200 No response at all
110, 90,200 Works perfectly
108, 92,200 Drops keys
105, 95,200 Drops keys
100,100,200 Drops keys
 90,110,200 Works perfectly
 80,120,200 Drops keys
 70,130,200 No response at all

100,110,190 Works perfectly
100,120,180 Drops keys

If I fix the third pulse width 175us, the first pulse widths 100us and 125us looks to be the median, and therefore stable.  However, if I fix the third pulse width 200us, the first two pulse widths of 100us and 100us it drops keys.  Both sides of it were stable.  Very mysterious, but I need to wait for oscillo.

Making it an Serial-Connected IR Emitter

Since it is stable, I modified my FM77AV Keyboard Emulator to support Arduino-based IR emitter.

The circuit diagram and PCB can be downloaded from here.

I first thought to implement a compatible protocol with IRToy, and I'll be done without modifying Windows-side host program, which was very wrong.  Arduino does not have a separate chip dedicated for RS232C connection, which means any incoming transmission while IRQ is masked will be lost.  IRQs must be masked while transmitting IR signals, or the program cannot control pulse width precisely.  Therefore, the host program needs to wait while Arduino is transmitting IR signals.  But, Arduino does not have CTS/RTS flow control.  Arduino needs to explicitly send a byte to the host PC to notify that the transmission is over.

If IRToy sends such an acknowledge, I can do the same in my Arduino program.  But, IRToy doesn't.  Well, I am supposed to be able to enable such acknowledge according to the manual, but it was a false advertisement.  In the end, I had to make a separate protocol for Arduino-based IR Emitter.  To identify IRToy and Arduino, I made my program to return "Axxx" (where xxx are numbers).  IRToy will reply to 'V' with 'Vxxx".  I can check if the connected device is Arduino or IRToy by looking at the first letter.II

If I identify Arduino, I don't have to stick with the IRToy protocol.  Instead of sending lengthy pulse widths that take 50 to 60 bytes, I can send 30bit in 4 bytes.


Made it a PCB.  Background is my ThinkPad which serves as a host.

But to leave a possibility to use it as a general-purpose IR emitter, it takes a byte sequence like:

0x03
ON-Cycle-High-Byte
ON-Cycle-Low-Byte
OFF-Cycle-High-Byte
OFF-Cycle-Low-Byte
ON-Cycle-High-Byte
ON-Cycle-Low-Byte
OFF-Cycle-High-Byte
OFF-Cycle-Low-Byte
    :
    :
0xff
0xff

Cycles are in us.  Once Arduino finishes sending the IR signals, it sends 'C' (0x43).  The host program can watch for this 'C' to know that the transmission is over.  Baud rate is fixed to 380K bps.  You can change in the program if you need.

Below is the source code for Arduino UNO.  It probably works with Arduino NANO.  Other Arduino have different pin out, and some have different CPU frequency.  So the code will need to be modified accordingly for those different Arduino units.

/*
38KHz 50% duty cycle Infra-Red Signal Emitter
Copyright 2019 CaptainYS (http://www.ysflight.com)  All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
   this list of conditions and the following disclaimer in the documentation 
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#define SERIAL_BPS 380000
// Confirmed to work up to 440000bps.
// Stopped working at 460000bps.
// Not tested between 440000 and 460000bps.


#define RECVBUF_SIZE 384
int nRecvBuf=0;
unsigned char recvBuf[RECVBUF_SIZE];
unsigned int cycle[RECVBUF_SIZE/2];
bool transmitMode=false;
unsigned long lastDataReceivedTime=0;
unsigned char processingCmd=0;


unsigned char pulseWidthTable[30];
const unsigned char pulseWidthSource[3]={100,125,175};


#define CMD_RESET 0
#define CMD_IRMAN_COMPATIBLE_MODE 'R'
#define CMD_VERSION 'V'
#define CMD_SELFTEST 't'
#define CMD_SAMPLERMODE 'S'
#define CMD_ENABLE_BYTECOUNT 0x24
#define CMD_ENABLE_TRANSMISSION_NOTIFY 0x25
#define CMD_ENABLE_HANDSHAKE 0x26
#define CMD_TRANSMIT_MICROSEC 0x80
#define CMD_TRANSMIT_30BIT_100US 0x81

#define NOTIFY_READY 'C'
#define NOTIFY_FAIL 'F'

// 50% duty cycle: On and off within 1/38000 sec.  Must togle every 1/76000 sec.
#define TIMER_THRESHOLD_38K_WITH_1xPRESCALE ((F_CPU/38000)/2)
// Experiment with FM77AV40 IR LED receiver:  19000Hz No response 34000-36000 drops key.  38000 perfect.  40000 drops keys.

#define PWM_TIMER_THRESHOLD TIMER_THRESHOLD_38K_WITH_1xPRESCALE

// Succeeded Xfinity remote

#define PIN_OC1A 9
#define PIN_OC1B 10
#define PIN_OC2A 11
#define PIN_OC2B 3

#define PIN_POWER 13
#define PIN_STATUS 8

// PIN9=PB1 (PortB bit 1)
#define SetPin9High  PORTB|=_BV(1);
#define SetPin9Low   PORTB&=~_BV(1);
// PIN10=PB2 (PortB bit 2)
#define SetPin10High PORTB|=_BV(2);
#define SetPin10Low  PORTB&=~_BV(2);
// PIN11=PB3 (PortB bit 3)
#define SetPin11High PORTB|=_BV(3);
#define SetPin11Low  PORTB&=~_BV(3);
// PIN3=PD3 (PortD bit 3)
#define SetPin3High  PORTD|=_BV(3);
#define SetPin3Low   PORTD&=~_BV(3);

// PIN13=PB5 PortB bit 5
#define SetPin13High PORTB|=_BV(5);
#define SetPin13Low PORTB&=~_BV(5);
// PIN8=PB0 Port B bit 0
#define SetPin8High PORTB|=_BV(0);
#define SetPin8Low PORTB&=~_BV(0);

#define SetPin9and10Low PORTB&=~(_BV(1)|_BV(2));


#define SET_CTC_MODE {TCCR1B=bit(CS10)|bit(WGM12);}
// TCCR1B  WGM10=0,WGM11=0,WGM12=1 means CTC mode
//         CS10=1,CS11=0,CS12=0 means 1x pre-scale (no scaling).
//         CS10=0,CS11=0,CS12=0 means timer stop.

#define SET_OC1A_OC1B_TOGGLE {TCCR1A=bit(COM1A0)|bit(COM1B0);}
// ATmega 328 datasheet pp.134
// TCCR1A  COM1A0=1, COM1B0=1 means toggle OC1A and OC1B on compare match

#define SET_OC1A_OC1B_LOW {TCCR1A=(bit(COM1A1)|bit(COM1B1));TCCR1C=(bit(FOC1A)|bit(FOC1B));TCCR1C=0;}
// TCCR1A=(bit(COM1A1)|bit(COM1B1));  // Clear OC1A low on compare match
// TCCR1C=(bit(FOC1A)|bit(FOC1B));    // Force match
// TCCR1C=0;                          // Do I need to clear?


// Failed Attmept:
// For each HIGH pulse, force OC1A to be HIGH, and then start timer.  This approach didn't work.
// Probable reason is that it makes the first duty cycle slightly shorter.
// This method FM77AV40 misses one in every 80 to 100 key strokes.



void setup() {
  for(int i=0; i<30; ++i)
  {
    pulseWidthTable[i]=pulseWidthSource[i%3];
  }
  
  Serial.begin(SERIAL_BPS);

  pinMode(PIN_POWER,OUTPUT);
  pinMode(PIN_STATUS,OUTPUT);

  pinMode(PIN_OC1A,OUTPUT);
  pinMode(PIN_OC1B,OUTPUT);
  pinMode(PIN_OC2A,OUTPUT);
  pinMode(PIN_OC2B,OUTPUT);

  // Reset timers 1 and 2
  TCCR1A=0;
  TCCR1B=0;
  TCCR2A=0;
  TCCR2B=0;
  TCNT1=0;
  TCNT2=0;

  // Timer 1 (8-bit) for 38K PWM IR LED output
  // ATmega 328 datasheet pp.134
  OCR1A=PWM_TIMER_THRESHOLD;
  OCR1B=0;

  SET_OC1A_OC1B_LOW;

  // Timer 2 for measuring 1us tick.
  TCCR2A=0;
  TCCR2B=bit(CS21);
  // CS20=0,CS21=1,CS22=0 means 8x pre-scaling.  0.5us per tick.  ATmega328 datasheet pp.162
  OCR2A=0;
  OCR2B=0;

  SetPin13Low;
  SetPin8Low;
  SetPin9and10Low;
}

void SendCycleHWPWM(unsigned int cycle[])
{
  noInterrupts();

  TCNT1=0;
  SET_CTC_MODE;
  SET_OC1A_OC1B_LOW;
  // Now pins 9 and 10 are under control of Timer 1, OC1A, OC1B both low.
  // Pin9=OC1A
  // Pin10=OC1B

  for(int i=0; cycle[i]!=0xffff; i+=2)
  {
    TCNT2=0;

    TCNT1=PWM_TIMER_THRESHOLD-8;
    // Timer 1 pre-scalar is 1x.
    // Need to start toggling within 8 cycles.  0.5us error.
    SET_OC1A_OC1B_TOGGLE;

    auto w=cycle[i]<<1;
    while(TCNT2<w)
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=240;
      }
    }

    TCNT2=0;
    SET_OC1A_OC1B_LOW;
    w=cycle[i+1]<<1;
    while(TCNT2<w)
    {
      if(240<=TCNT2)
      {
        TCNT2=0;
        w-=240;
      }
    }
  }

  SET_OC1A_OC1B_LOW;
  SetPin9and10Low;

  interrupts();
}

void MakeCycle(unsigned int cycle[],int nSample,unsigned char sample[])
{
  unsigned char k=0;
  switch(processingCmd)
  {
  case CMD_TRANSMIT_MICROSEC:
    {
      for(int i=0; i+1<nSample && (sample[i]!=0xff || sample[i+1]!=0xff); i+=2)
      {
        cycle[k]=(sample[i]<<8);
        cycle[k]+=sample[i+1];
        ++k;
      }
    }
    break;
  case CMD_TRANSMIT_30BIT_100US:
    {
      unsigned char samplePtr=0,sampleBit=1;
      cycle[k]=0;
      for(unsigned char i=0; i<30; ++i)
      {
        bool currentBit=((~k)&1);
        bool nextBit=(sample[samplePtr]&sampleBit);
        if(currentBit!=nextBit)
        {
          ++k;
          cycle[k]=0;
        }
        cycle[k]+=pulseWidthTable[i];

        if(128==sampleBit)
        {
          ++samplePtr;
          sampleBit=1;
        }
        else
        {
          sampleBit<<=1;
        }
      }
      ++k;
    }
    break;
  }
  cycle[k]=0;
  k+=(k&1); // Force it to be even.
  cycle[k  ]=0xffff;
  cycle[k+1]=0xffff;
}

void Transmit()
{
  SetPin8High;
  MakeCycle(cycle,nRecvBuf,recvBuf);
  SendCycleHWPWM(cycle);

  while(0==Serial.availableForWrite());
  Serial.write(NOTIFY_READY);

  transmitMode=false;

  SetPin8Low;
}

void loop() {
  bool received=false;
  while(0<Serial.available())
  {
    auto recvByte=Serial.read();
    if(true!=transmitMode)
    {
      processingCmd=recvByte;
      if(CMD_TRANSMIT_MICROSEC==recvByte ||
         CMD_TRANSMIT_30BIT_100US==recvByte)
      {
        transmitMode=true;
        nRecvBuf=0;
      }
      else if(CMD_IRMAN_COMPATIBLE_MODE==recvByte)
      {
        Serial.println("OK");
      }
      else if(CMD_VERSION==recvByte)
      {
        Serial.println("A277");
      }
      else if(CMD_SAMPLERMODE==recvByte)
      {
        Serial.println("S77");
      }
    }
    else
    {
      recvBuf[nRecvBuf++]=recvByte;
      if(CMD_TRANSMIT_30BIT_100US==processingCmd && 4<=nRecvBuf)
      {
        Transmit();
      }
      else if(RECVBUF_SIZE<=nRecvBuf)
      {
        Transmit();
      }
      else if(2<=nRecvBuf && 0xff==recvBuf[nRecvBuf-1] && 0xff==recvBuf[nRecvBuf-2])
      {
        Transmit();
      }
    }
    received=true;
  }

  unsigned long t=millis();
  if(true==received)
  {
    lastDataReceivedTime=t;
  }
  else
  {
    // Second mode of failure.
    // In transmit mode, the FIFO buffer is overwhelmed and starts losing bytes.
    // The terminator 0xffff won't be caught, however, 0x03 included in the
    // pulse-width data put it into the transmitMode again, and never recover.
    // To get out of this mode, the user needs to release the key for 100ms.
    // If no byte is received for 100ms, the program goes back to command mode.
    if(true==transmitMode)
    {
      if(t<lastDataReceivedTime ||   // Timer overflow
         100<t-lastDataReceivedTime) // 100ms no transmittion from host
      {
        transmitMode=false;
        Serial.write(NOTIFY_FAIL);
        Serial.write(NOTIFY_READY);
      }
    }
  }
}

Last Remaining Mystery

I have already using it when I type for my FM77AV40.  It is stable.  Occasionally it is disturbed by environmental IR.  Although a piece of paper does not block IR, as long as you use a thicker paper, it weakens IR coming from the external source, and prevents an error.

But, still I don't have an answer for why it does not take 100us pulses, or 100us, 100us, 200us combinations.  If I had to use 100us, 125us, 175us combination with IRToy, I'm fine.  But, when I did with IRToy, it accepted both 100us, 100us, 200us, and 100us, 125us, 175us combinations.  Why IRToy was ok with both and my Arduino is not?

I have asked a CMU Computer Club member who has been helping me in this kind of situation to help measuring the voltage output of the IR LED with an oscilloscope.  Below are the measurements from the voltage output from the data pin driving IR LEDs (pin 9).


Output for keycode 'f'

The oscilloscope tells that it was generating a perfect rectangular wave, each high cycle and high-cycle interval takes 13.2us.  Exactly what I was trying to generate.

However, when we tried to measure voltage applied on the pins of IR LEDs, the oscilloscope did not capture the wave pattern.  Our guess was because of the constant-current circuit, but we don't know the answer.

So, now I know that the data pin is generating a perfect rectangular wave, but I still do not know why IRToy works with both {100us, 100us, 200us} and {100us, 125us, 175us} combinations while my Arduino-based IR emitter works only with {100us, 125us, 175us} combination.

Comments are welcome.  Send E-Mail to: 

Back to http://www.ysflight.com