Tuesday, April 10, 2018

ESP8266 IR Transmitter

With the IR Receive out of the way, the next step was to add the IR Send. I have used IR LEDs in the past and they were not very strong. But now that there are IR LEDs for purposes other than remote control, the choices open up. There are several very high power IR LEDs out there rated at 1W and 3W. They are primarily used for illumination for IR cameras. As my circuit is not battery powered, I couldn’t care less about efficiency. So I went for a no name 3W IR LED bought on ebay for AU$1.25. The only thing to look out for is the wavelength – it should be 940nm.

Hardware

The circuit runs the LED at 200mA – only in bursts. But for a monster LED like this, even without a heatsink, this current could be on for ever and it won’t feel a thing. It is good to know that if I make a mistake and have the GPIO left on, it won’t blow up the LED. I need a buffer transistor as the GPIO of the ESP8266 is rated at 12 mA and can’t pump out 200mA. The supply is still a low 5V, there are voltage drops across the transistor and LED, so the value of the series resistor can be quite tricky. To get around this, I am using a constant current circuit using a couple of diodes in series for the reference voltage.

It would be good to have a few indicator LEDs showing the mode the circuit is in. Or you could just put in a WS2812B and have multiple colours indicating the mode. Which is what I did. By default, it has a magenta heartbeat blink. When in Learn mode, it is cyan. When it transmits, it is flickering green. I also had it flickering red on receive but I was worried about interference with the receive circuitry and code so I disabled that part.

ESP8266 Code

Once again, I turned to the sample NEC code on the web. I used the routine to generate a 40KHz carrier frequency using the mgos_usleep() call. The routine will send out the carrier for the given duration in µs.

static void irsend_carrier_40kHz(int us) {
 us = ((us + 12) / 25);
 // make signal of circa 40 kHz of circa 1/3 duty
 for (; us >= 0; --us) {
  mgos_gpio_write(IR_OUT_PIN, PIN_ON);
  mgos_usleep(8);
  mgos_gpio_write(IR_OUT_PIN, PIN_OFF);
  mgos_usleep(16);
 }
}

The parameters for the IR signal are received by the CGI handler and unpacked into a structure shown below. The data to be sent and the repeat count is also part of the URL and is unpacked into the same structure.

struct ir_signal {
 // data to be sent. not part of the protocol
 long data;
 // no. of times to repeat signal, not part of the protocol
 int repCount;

 // Protocol parameters

 // mark is presence of carrier signal
 // space is lack of carrier signal

 // coding scheme
 // M - pulse (or mark) encoding
 // S - space encoding
 // P - phase encoding
 // B - both mark and space (used in some rf protocols)
 // Q - quad encoding (used by foxtel remotes)
 char coding;
 // no. of bits of data
 int dataBits;
 // duration of header mark and space
 int headM, headS;
 // set of timings
 // interpretation depends on coding scheme
 int timings[5];
 // long pause, stop mark and space used in some remotes
 int pauseS;
 int stopM, stopS;
 // end mark used in space encoding and pause/stop signals
 int endM;
 // gap between subsequent signal trains
 int gap;
 // type of repeat in case of long press
 // R - repeat same cose
 // E - repeat stop/pause only
 // F- weird foxtel repeat
 char repeat;
 // non-standard gap used in foxtel
 int foxtelGap;
 // mask to change a bit in data in repeats
 long foxtelMask;
};

More calls to the mgos_usleep() took care of other IR timings.

static void sendSingleIR(struct ir_signal *irs) {
 int inx = 0;
 int data = irs->data;

 if (irs->headM > 0) {
  // header
  irsend_carrier_40kHz(irs->headM);
  mgos_usleep(irs->headS);
 }

 if (irs->coding == 'Q') {
  for (inx = 0; inx < irs->dataBits; inx += 2) {
   int bit = (data & 3);
   irsend_carrier_40kHz(irs->timings[0]);
   if (bit == 0) {
    mgos_usleep(irs->timings[1]);
   }
   else if (bit == 1) {
    mgos_usleep(irs->timings[2]);
   }
   else if (bit == 2) {
    mgos_usleep(irs->timings[3]);
   }
   else if (bit == 3) {
    mgos_usleep(irs->timings[4]);
   }
   data >>= 2;
  }
 }
 else {
  for (inx = 0; inx < irs->dataBits; inx++) {
   int bit = (data & 1);
   if (irs->coding == 'M') {
    // irs->timings M0, M1, S
    if (bit == 0) {
     irsend_carrier_40kHz(irs->timings[0]);
    }
    else {
     irsend_carrier_40kHz(irs->timings[1]);
    }
    mgos_usleep(irs->timings[2]);
   }
   else if (irs->coding == 'S') {
    // irs->timings M, S0, S1
    irsend_carrier_40kHz(irs->timings[0]);
    if (bit == 0) {
     mgos_usleep(irs->timings[1]);
    }
    else {
     mgos_usleep(irs->timings[2]);
    }
   }
   else if (irs->coding == 'P') {
    if (bit = 0) {
     mgos_usleep(irs->timings[0]);
     irsend_carrier_40kHz(irs->timings[0]);
    }
    else {
     irsend_carrier_40kHz(irs->timings[0]);
     mgos_usleep(irs->timings[0]);
    }
   }
   data >>= 1;
  }
 }
 if (irs->pauseS > 0) {
  // pause
  mgos_usleep(irs->pauseS);
 }
 if (irs->stopM > 0) {
  // stop
  irsend_carrier_40kHz(irs->stopM);
  mgos_usleep(irs->stopS);
 }
 if (irs->endM > 0) {
  // end
  irsend_carrier_40kHz(irs->endM);
 }
 if (irs->gap > 0) {
  mgos_usleep(irs->gap);
 }
}

I use the same web interface shown in the previous post. The various parameters can either be filled in or just ‘learnt’ from an existing remote. Once the data to be sent is also entered or learnt, just click Send. Handy hint: you can check the operation of the IR LED by looking at it through a mobile phone camera.

No comments:

Post a Comment