Monday, February 20, 2012

AC load switching via infrared remote control

Laziness is the mother of invention. Not wanting to get out of bed to turn off the room lights, it occurred to me to build a circuit--one that I may end up using or not--which would switch the lights off/on when it receives the proper signals from a TV remote control. Here's the original design:


After breadboarding and testing the above it hit me: Why not add more features such as setting the amount of time the light (or whatever load it may be) will remain on, or time elapsed before it automatically turns on. Adding these capabilities would merely require changes to the software. But because I would need the LEDs as indicator lights for various functions--eg. making them blink as user feedback--they have to be controlled separately from the triac/load. Since the 8-pin MCU has unused pins it was trivial assigning each LED a MCU pin of its own. Here's the final circuit. I've changed the triac gate current limiting resistor value as well:


MCU = Microchip PIC12F615 microcontroller
IRR = Osram SFH5110-38 IR receiver 38kHz
Z1 = 1N4734A 5.6V 1W zener diode
Q1 = 2N7000 n-channel enhancement MOSFET transistor
TRIAC = Teccor Q401E3 400V 1A triac

Z1, C1, C2, and D1 comprise a half-wave transformerless power supply providing approximately 5VDC. With C1 = 1uF the circuit is designed to run off 220VAC 60Hz mains, providing a theoretical maximum current of around 80mA RMS. The above may or may not work with 110VAC since at that voltage the available DC current is halved. For 110VAC 60Hz use, doubling the capacitance to 2uF will halve the capacitive reactance and thus let through twice the current. Capacitive reactance is given by:

XC = (6.28fC)-1

where
f = frequency of the AC line in Hertz
C = capacitance in farads 

Therefore, with C = 2uF XC = 1327ohms. RMS current is therefore = 110VAC/1327 = 83mA RMS

One leg of the mains is common to both DC and AC lines. This is necessary for the triac to be triggered by the DC circuit.

Because the circuit doesn't use a transformer any part of it is potentially lethal to the touch. The net labeled 5V is 5VDC with respect to the circuit ground. But it is 220VAC with respect to the other leg of the AC line and it can be as much as 110VAC with respect to earth ground, so beware! It goes without saying that errors in connecting/ soldering of the components of the power supply could result in unintended and undesirable fireworks and tripping of the building's breakers/fuses. Be damn careful when designing and building circuits involving mains voltage. I come down with OCD and anxiety disorder every time. You can't check and recheck enough times that you've done everything right.

R1 is an ordinary carbon resistor and functions as a fuse. It's supposed to blow if current through the circuit (but not the load and MT1 to MT2 of the triac) exceeds around 100mA. Given an 18-ohm 250mW resistor, current at which its dissipation is 250mW is = √(0.25/18) = 118mA. So the resistor should theoretically fry if it exceeds this value for over a fraction of a second.

Note, however, that given 220V RMS, its peak voltage = 220√2 = 311V. With C1 = 1uF XC = 2653ohms. Therefore, peak current = 311/2653ohms = 117mA. Thus the resistor experiences instantaneous dissipation of 250mW 120 times a second (two peaks per cycle for a 60Hz line). If this fuse keeps blowing for no apparent reason, lower it to 15ohms. Of course this is a poor man's version of a fuse resistor and is suboptimal and may even fail to protect the components downstream. Better than none though.

R2 and C3 were added as per suggestion of the Osram datasheet. When I was bench testing the breadboarded version which I powered using the PICkit2, I found that adding R2 and C3 did boost the performance. Apparently the PICkit2 wasn't supplying clean or sufficient power. Having used the above transformerless power supply design for about a decade in a good number of circuits I know there are regulated voltage and ripple voltage issues with it (because it's just a half-wave supply). And so adding R2 and C3 is mandatory.

Triac gate trigger is such that current flows from MT1 to gate (rather than from gate to MT1), hence the triac operates in Quadrants 2 and 3--which require much less gate trigger current. For a discussion of triac quadrants and triggering read the section "Load Switching."

Given the zener voltage of 5.6V and D1 forward voltage VF of 0.7V @20mA (see graphs for 1N400x), our VDD = 5.6 - 0.7 = 4.9VDC. According to the Teccor datasheet maximum required gate current for quadrants 2 and 3 operation is 10mA while maximum gate voltage VGT is 1.3V. Deducting VGT from VDD we're left with 4.9 - 1.3 = 3.6V. There's also a voltage drop across Q1. According to the graphs in the 2N7000 specs sheet the drain-to-source resistance RDS at a gate-to-source voltage of 5.0V and gate current of 10mA is almost 1.5ohms. Using Ohm's Law the drain-to-source voltage VDS = 1.5ohms x 10mA = 15mV. This is negligible, and even if quadrupled VDS would still be just around 60mV. So we can disregard the Q1 voltage drop and take 3.6V as the voltage across triac gate resistor RT. To obtain 10mA of gate current we apply good ol' Ohm's Law again: 3.6V / 10mA = 360ohms. Closest standard 5% resistor value is either 390 or 330ohms. We pick the lower value to make sure there's enough current to meet the maximum requirement of 10mA. With 330ohms the triac gate current = 3.6 / 330 = 10.9mA. Resistor power dissipation  = I2R = 0.01092(330) =39mW. Ostensibly this means we can use a 1/8-watt resistor. But the 10mA current we obtained is just a theoretical value. Its true value depends largely on the triac's VGT which will probably be much less than the maximum quoted 1.3V. Now supposing that VGT = 0 and our 5% tolerance 330-ohm resistor has in fact a value = 330*95% = 313 ohms. Gate current would then be 4.9V / 313ohms = 16mA. Resistor power dissipation would thus be = 0.0162313 = 80mW. This is still some 35% less than 1/8W. Therefore, we can in fact use a 1/8-watt resistor.

Currently the only buttons on the TV remote control which will make the circuit do anything are the keys for digits 1 and 0. Pressing "1" turns load on, while "0" switches it off. Upon power up the load is forced to turn on. In the case of the room light, this means that flipping the wall switch on will turn the light on--as we would want it to. The light can then be turned off via this switch or through the use of the remote control.

The firmware I have thus far is tailored for the Philips RC-5 infrared communications protocol (visit that link for an explanation for the rationale/basis of the signal decoding firmware below). I'm planning to make the circuit respond to both the RC-5 and Sony SIRC protocol, letting the firmware determine which of the protocols it's receiving (and rejecting other protocols) and deciding of course whether the received codes correspond to any of those which it should respond to and take action. As I said above, I want to extend the range of capabilities of this circuit and add stuff like timer features. So it's back to the drawing board deciding which buttons will serve as timer functions and writing the appropriate software. And as has probably already popped into your head, we wouldn't want our lights or whatever load to turn on/off when we intended to change channels on the TV! Such techniques as using two-button codes (e.g. the "Menu" and "1" buttons need to be pressed in rapid succession--i.e., within a prescribed time frame--to turn the load on) or keeping the button pressed for one second or so (e.g., command is received by the circuit, say, 5 to 10 times before action is taken) may be employed.  

/*

AC LOAD SWITCHING USING A TV REMOTE CONTROL
February 2012

processor = PIC12F615
compiler = mikroC v5.0.0
configuration word = power up timer, brownout reset, and WDT enabled; all else disabled. Internal Oscillator = 4MHz

*/


#define  load                GPIO.f2             // 2N7000 sinks triac gate current
#define  ledg                GPIO.f4             // green led  -- multifunction status led, not just on indicator
#define  ledr                GPIO.f1             // red led    -- multifunction status led, not just off indicator

#define  irr                 GPIO.f5             // Osram SFH5110-38 infrared receiver
#define  tris_irr            TRISIO.f5           // for use with setting the appropriate pin as input
#define  ioc_irr             IOC.f5              // for use with enable interrupt on change for irr

// RC5 decoding defines

#define  _full               1
#define  _half               0

// one full bit period for RC5 = 1.778ms; one half bit period = 0.889ms
// given clock = 4MHz and TMR0 initial value = 0 and timer0 prescale = 1:16, TMR0 overflows every 256 x 16 / 1MHz = 4.096ms
// 4.096ms / TMR0 count = 4.096 / 256 = 0.016ms per TMR0 count
// 1.778 / 0.016ms = 111   this is the TMR0 count when time elapsed is 1.778ms
#define  _fullbit            111                                     // full bit period of RC5
#define  _tolerance          25                                      // tolerance in percent, to be added/subtracted to/from half and full bit periods to create range of acceptable pulse widths
                                                                     // !! THIS HAS TO BE INTEGER NOT FLOATING POINT !!
                                                                     // !! MAXIMUM VALUE = 33%, ELSE halfbit_uplim WILL BE GREATER THAN fullbit_uplim

#define  _halfbit            _fullbit / 2                            // half bit period of RC5

#define  halfbit_lolim       _halfbit - (_halfbit * _tolerance / 100) // minimum pulse duration for half bit period
#define  halfbit_uplim       _halfbit + (_halfbit * _tolerance / 100) // maximum pulse duration for half bit period
#define  fullbit_lolim       _fullbit - (_fullbit * _tolerance / 100) // minimum pulse duration for full bit period
#define  fullbit_uplim       _fullbit + (_fullbit * _tolerance / 100) // maximum pulse duration for full bit period


#define  int1                bit
#define  int8                unsigned char
#define  int16               unsigned int
#define  int32               unsigned long

#define  on                  1
#define  off                 0

#define  input               1         // for TRISx
#define  output              0         // for TRISx

#define  analog              1         // for ANSELx
#define  digital             0         // for ANSELx


// ===========================================================================================
//       Global Variables
// ===========================================================================================

int8     PULSEWIDTH;         // contains the measured pulse width (to get the real value in millisec multiply by 16)
int8     RC5BITS;            // contains the bit number (excluding the start bit) of the RC5 word currently being received
int8     RC5FCSYS;           // contains the RC5 Field bit, Control Bit, and the 5-bit System Address
int8     RC5COMM;            // contains the RC5 6-bit Command code
int8     RC5COMMPREV;        // contains the previous RC5 6-bit Command code
int8     TEMP;               // temporary memory

bit      rc5_on;             // 1 = irr output has been high for >4millisec and then goes low; this indicates a RC5 start bit
                             // 0 = irr output has been low for >4millisec and then goes high. This is an abnormal state and is an error
                             // rc5_on is also reset to 0 when all bits of word have been received
bit      halfper_prev;       // 1 = the last IOC was a half bit period, 0 = the last IOC occured after a full bit period
bit      key_pressed;        // 1 = button has been pressed and valid RC5 word stored

bit      rc5_read_error;     // 1 = read error, pulse width of either a zero or one bit is outside the acceptable limits; the data packet should be discarded
bit      bit_period;         // _full = 1 = one full bit period, _half = 0 = half bit period
bit      curr_bit;           // contains the latest decoded value of the bit of the RC5 word being received
bit      con_bit_prev;        // contains the control bit of the previous decoded RC5 word

// used to monitor if a key has been pressed and if yes is the key being held down or is a new press
// _key_none = no key pressed
// _key_new = new key pressed. It may be the same button but it was released and pressed again, ie., the Control bit has toggled
// _key_same = IR signal from the same key. Button is depressed and has not been released.
enum {_key_none, _key_new, _key_same} KEY = _key_none;


// ===========================================================================================
//       Functions
// ===========================================================================================

void IniReg()
{
  ANSEL = digital;
  TRISIO = output;
  GPIO = 0;
  
  tris_irr = input;

  OPTION_REG = 0b10000011;   // prescaler assigned to timer0
                             // prescale = 1:16
                             // timer0 uses internal clock instruction cycle
                             // weak pull ups disabled

  // enable interrupt on change
  // intialize pin connnected to ir receiver to interrupt on input change
  INTCON.GPIE = on;
  ioc_irr = on;

  INTCON.GIE = 1;
  INTCON.PEIE = 1;

  key_pressed = 0;
  load = on;
  ledg = on;
  ledr = off;
} // void IniReg()



// this function determines whether the key pressed is a:
// 1. _key_none = no key press has been detected
// 2. _key_new = a different key has been pressed or the same key has been released and pressed again
// 3. _key_same = the same key is still held down
// If no key is press is detected for > _idle_time then firmware goes into standby mode and display is turned off
void ProcessKey()
{
  if (key_pressed)
  {
    key_pressed = 0;
    if ((RC5COMM == RC5COMMPREV) && (RC5FCSYS.f5 == con_bit_prev))
      KEY = _key_same;
    else           // if current key is not the same as previous key or if current control bit not the same as previous
      KEY = _key_new;

    RC5COMMPREV = RC5COMM;
    con_bit_prev = RC5FCSYS.f5;
  } // if (key_pressed)
  else
    KEY = _key_none;
} // void ProcessKey()


void LoadControl()
{
  switch (KEY)
  {
    case _key_new:
      switch (RC5COMM)
      {
        case 0:
          load = 0;
          ledg = off;
          ledr = on;
          break;
        
        case 1:
          load = 1;
          ledg = on;
          ledr = off;
          break;

        default:
          break;
      } // switch (RC5COMM)
      break;
    
    case _key_same:
      break;

    case _key_none:
      break;

  } // switch (KEY)
} // void LoadControl()


void interrupt()
{
  // interrupt on change
  if (INTCON.GPIF)
  {
    PULSEWIDTH = TMR0;
    TMR0 = 0;
    TEMP = GPIO;            // a read of GPIO is necessary before GPIF can be cleared
    INTCON.GPIF = 0;

    if (rc5_on && !rc5_read_error)
    {
      if (PULSEWIDTH >= halfbit_lolim && PULSEWIDTH <= halfbit_uplim)
        bit_period = _half;
      else if (PULSEWIDTH >= fullbit_lolim && PULSEWIDTH <= fullbit_uplim)
        bit_period = _full;
      else
        rc5_read_error = 1;

      if (bit_period == _half && halfper_prev == 0)
      {
        halfper_prev = 1;    // last IOC occured after a full bit period, so this current half bit period needs to be paird with the half bit period
      }
      else // if (bit_period == _full || halfper_prev == 1)
      {
        halfper_prev = 0;
        if (irr)             // rising edge, indicates logic zero
          curr_bit = 0;
        else                 // falling edge, indicates logic one
          curr_bit = 1;

        if (RC5BITS <= 6)
        {
          RC5FCSYS <<= 1;
          RC5FCSYS.f0 = curr_bit;
        }
        else
        {
          RC5COMM <<= 1;
          RC5COMM.f0 = curr_bit;
        }
        if (++RC5BITS >=13)
        {
          rc5_on = 0;
          key_pressed = 1;
        }
      } // else
    } // if (rc5_on && !rc5_read_error)

    // if timer0 interrupt flag is set then ir_rx output was low/high for >4ms before the IOC that just occurred
    if (INTCON.T0IF)
    {
      INTCON.T0IF = 0;
      RC5BITS = 0;
      RC5FCSYS = 0;
      RC5COMM = 0;
      rc5_read_error = 0;
      rc5_on = 0;
      if (!irr)                        // falling edge; pulse was high for >4ms so this is considered the start bit of RC5 word
        rc5_on = 1;
    } // if (INTCON.T0IF)
  } // if (INTCON.GPIF)
} // void interrupt()


void main()
{
  IniReg();

  while(1)
  {
    asm{clrwdt}
    ProcessKey();
    LoadControl();
  }
}


Decided to print a dual board artwork--just copied and pasted the original pcb layout. Each board is 2x2". Since the effort and time that goes into developing and etching one presensitized panel is practically the same whether it's 1x1" or 4x6" might as well make a dual board. Going for four is too much, specially since this is a prototype. I scored and snapped the panel into two boards after drilling all the holes.


The following pcb board has been tested to work. I used a 23-watt compact fluorescent lamp as the load. Unfortunately, it doesn't perform as flawlessly as the breadboarded version. Commands sent to the board are not always picked up properly and so the load doesn't get switched immediately upon key press. I suspect a power supply issue. I'm going to use a 220VAC-to-220VAC isolation transformer to power the board and probe the circuit with a DMM.



You will notice that the LEDs are pointing up while the adjacent infrared receiver is facing 90 degrees away. I'll bend either the sensor or the LEDs to make them face the same way after I've decided which way this board is going to be mounted and where the IR receiver needs to point.

1 comment:

  1. That is more perfect if we can modify the software for a PIC having EEPROM memory (ig: pic12F675) in order to keep the status when electricity does fail. Thank you!

    ReplyDelete