Sunday, 25 September 2011

IR controlled USB switch


USB switches are devices which allow you to share a single USB device between several computers, it contains electrical or mechanical switches that connect the USB device to the desired computer at the push of a switch (made possible thanks to USB's hotplugging capabilities).  This is great when you cannot or don't want to have to reach around and unplug the USB cable, but unfortunately most implementations still require you to reach out and push the buttons an the USB switch, and if you're not within arms' reach of the switch, you're going to have to get up and go over to it, or invest in a long stick.


[beerninja] had the great idea of adding an IR receiver to a USB switch, allowing remote toggling of the switch using any IR remote.  I decided to take a look at what I could do.



Parts
The USB switch:  This is a low cost unbranded USB switch, and is surprisingly heavy for its size - this turned out to be because instead of the brushed aluminium casing that would have been expected, the casing is actually made of steel!


The case is held together with four screws, overall the steel casing is not pretty to look at, but definitely sturdy.  Inside the board reveals that the switching is actually done with very small relays (which revealed the source of the light click when the thing switches), there are three of them here for a four port switch, indicating that these should be 2-pole relays arranged in a tree.  The part number B3GA4.5P reveals these to be Fujitsu FTR-B3 series ultra miniature signal relays


Also inside the board are three chips labeled CY7C63723, which were revealed to be Cypress Semiconductor enCoRe USB peripheral controllers.  Presumably these chips handle the switching logic, as well as accepting commands from a computer to switch ports (some software can be installed on the computer to allow switching via hotkey).  Unfortunately these are one-time programmable, so they can't be reprogrammed to do my bidding.

On a side note, the 5V supply from the host computers each pass through a diode.  This is presumably to avoid having the hosts' 5V supplies connected to each other, however the sideeffect of doing this is that the voltage is dropped to 4.3-4.5V depending on current draw, and the connected USB device is supplied at this vltage instead of 5V.  I suspect this may cause problems for certain USB devices that don't like this lower voltage.  Perhaps the designers should have either Schottky barrier diode, or DC/DC boost ICs to do this.


Forebrain Cortex M3 dev board.  Talk about overkill, these are dev boards for 32-bit ARM Cortex M3 microcontrollers with a clock speed up to 72MHz.  These are Forebrain dev boards from Universal Air Ltd.  I'll be using these to read an IR receiver, and having the ARM microcontroller send signals to the Cypress microcontrollers by simulating a button press based on the particular IR receiver received.


IR Receiver: this is a small IR receiver module that contains an infra-red photodiode along with a filter circuit to demodulate the 38kHz carrier frequency that is commonly used bye remote controls, the final output should be a clean square wave that can be read by the ARM microcontroler.

The circuit required to interface with the device is simple, needing only a resistor and a capacitor for stability.


Controlling the switch
To have Forebrain controll the switch, we can interface via the buttons.  Each button on the switch is active-low, and pulled up via a 10k resistor.

In addition to 5V and GND wires, a wire has been soldered to each button, and connected to one of Forebrain's GPIO pins.  By having Forebrain output a digital LOW (i.e. sink current), this achieves the same effect as a button press.  When not activating a button, Forebrain's pins can be set as high-inpedence inputs so that they will not interfere with a regular button press, giving the user the option of using either IR receiver or the physical buttons.


The code pulls the GPIO pin low for 350mS, simulating a button press, there is also a blanking time of 750mS since sometimes the switch sometimes takes a moment to switch over.

Some test code is written to cycle through the inputs.
#include "uafunc.h"

void switchTo(unsigned char pinNumber);
 
void setup() {
    ClockMode(IRC12);                           // Set to 12MHz operation to reduce power consumption and heat (15mA approx for 12MHz IRC mode vs 32mA approx for 72MHz mode)
    LEDInit(ALL);                               // Initialise onboard LEDs for feedback
    Port2Pull(PIN0 | PIN1 | PIN2 | PIN3, OFF);  // Turn off pull-up resistors (buttons already have these)
    Port2SetIn(PIN0 | PIN1 | PIN2 | PIN3);      // Set as tristated input
}
 
void loop() {
    // Test program - loop through the inputs
    LEDWrite(LED0);
    switchTo(0);
    Delay(6000);
    LEDWrite(LED2);
    switchTo(2);
    Delay(6000);
    LEDWrite(LED1);
    switchTo(1);
    Delay(6000);
    LEDWrite(LED3);
    switchTo(3);
    Delay(6000);
}

void switchTo(unsigned char pinNumber) {
    Port2SetOut((1 << pinNumber));      // Set to output mode
    Port2Write((1 << pinNumber), 0);    // Set to low
    Delay(350);                         // Wait for 350mS
    Port2SetIn((1 << pinNumber));       // Set to high impedence again
   
    Delay(750);                         // Blanking time of 750mS since the switch doesn't switch cleanly
}



IR input
The IR module's open collector output is connected to one of Forebrain's GPIO pins, Forebrain's internal pull-up resistor are used.


The IR protocol consists of a series of pulses of specific lengths, Forebrain uses the hardware interrupt on the pin to trigger an interrupt function on both rising and falling edges, allowing the pulses from the IR module to be captured.  A second hardware timer from one of Forebrain's timer hardware is used to count the pulse periods.

Some good resources on IR control:
http://www.ladyada.net/learn/sensors/ir.html
http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html

Once the pulses from the IR module is captured, it can be compared with stored values to see which button was pressed.  Each button on a remote control produces a unique code, similarly the timings and format of the pulses vary greatly between remote controls.  The remote used here is a UK Sky remote control, chosen since it is extremely common in the UK.

The Sky remote protocol produces somewhere around 18 on and off pulses.  The length of each pulse encodes the data, and there are two different lengths possible, allowing the pulses to be converted to and from binary.  The Forebrain code detects the length of the pulse and generates a binary representation of the whole IR sequence.

The following code combines the IR decoding code with the switching code.  The buttons 1, 2, 3 and 4 on the remote control are used to activate the respective buttons on the switch.  In addition to this, button 0 is used to set Forebrain back to reprogramming mode; this is more of a convenience than a requirement - it will allow quick and easy code updates over USB.
#include "uafunc.h"

#define MAXPULSE    100
#define MAXTIME     0xfff

#define MARKHIGH    95
#define MARKLOW     51
#define SPACEHIGH   80
#define SPACELOW    35

unsigned int pulseMark[MAXPULSE], pulseSpace[MAXPULSE], pulseTime;
unsigned char timeout, pulseCount;

void processIR(void);
void switchTo(unsigned char pinNumber);

void setup() {
    // Initialise LEDs
    LEDInit(ALL);
   
    // IR Inputs
    Timer0Init(0);
    Timer0Match0(720, INTERRUPT | RESET);   // Set up T0M0 for 10uS interrupts for counting IR pulses
    Port2SetInterrupt(PIN11, BOTH);
  
    pulseCount = 0;
    pulseTime = 0;
    timeout = 0;
   
    // Outputs
    Port2Pull(PIN0 | PIN1 | PIN2 | PIN3, OFF);  // Turn off pull-up resistors (buttons already have these)
    Port2SetIn(PIN0 | PIN1 | PIN2 | PIN3);      // Set as tristated input   
}
 
void loop() {
    Sleep();    // Sleep for low power mode
}

// *** Process IR processes the stored IR pulses and determines which button is pressed
void processIR(void) {
    unsigned int i;
    unsigned char data[8]={0};
    unsigned char dataIndex=0, byteIndex=0;
    unsigned long long * pointer;
   
    LEDOn(ALL);
   
    // *** loop through the stored pulses, and save the data into a byte array which is later compared as a 64-bit number
    for(i=0; i        // check if signal is out of bound, note: not detecting if pulseSpace < SPACELOW, this is deliberate
        if(pulseSpace[i] > SPACEHIGH * 1.2 || pulseMark[i] < MARKLOW * 0.8 || pulseMark[i] > MARKHIGH * 1.2) {
            continue;
        }
       
        // check for mark signal
        if(pulseMark[i] > (MARKLOW + MARKHIGH)/2) {
            data[byteIndex] |= 1 << dataIndex;
        }
        dataIndex ++;
        if(dataIndex >= 8) {
            dataIndex -= 8;
            byteIndex ++;
        }
       
        // check for space signal
        if(pulseSpace[i] > (SPACELOW + SPACEHIGH)/2) {
            data[byteIndex] |= 1 << dataIndex;
        }
        dataIndex ++;
        if(dataIndex >= 8) {
            dataIndex -= 8;
            byteIndex ++;
        }
       
    }
   
    pointer = (unsigned long long *)&data;  // convert byte array into 64-bit number using pointer hack
   
    // *** Check for values
    switch(*pointer) {
        case 0x00004000000001a0ULL:
            switchTo(0);
            break;
        case 0x00003000000001a0ULL:
            switchTo(1);
            break;
        case 0x00001000000001a0ULL:
            switchTo(2);
            break;
        case 0x00000c00000001a0ULL:
            switchTo(3);
            break;
        case 0x00000000000001a0ULL:
            LEDOff(ALL);
            Port2SetIn(ALL);
            Reprogram();
            break;
        default:
            LEDOff(ALL);
    }
   
}

// *** SwitchTo function outputs the required signals
void switchTo(unsigned char pinNumber) {
    unsigned short pin;
    pin = 1 << pinNumber;
   
    LEDWrite(pin);
    Port2SetOut(pin);       // Set to output mode
    Port2Write(pin, 0);     // Set to low
    Delay(350);             // Wait for 350mS
    Port2SetIn(pin);        // Set to high impedence again
   
    Delay(750);             // Blanking time of 750mS since the switch doesn't switch cleanly
}

// *** Timer interrupt counts the pulse lengths, if no IR activity after a certain time, it resets the data
void Timer0Interrupt0(void) {
    unsigned int i;
    pulseTime++;
    if(pulseTime > MAXTIME || pulseCount >= MAXPULSE) { // timeout set at 0xfff * 10uS = 40.95ms
        if(pulseCount > 0) processIR();
        pulseCount = 0;
        pulseTime = 0;
        timeout = 1;
        for(i=0; i            pulseMark[i] = 0;
            pulseSpace[i] = 0;
        }
    }
}

// *** GPIO interrupt saves the pulse length data to be checked later
void Port2Pin11Interrupt(void) {
    if(Port2Read(PIN11) == 0) { // High to Low transition
        pulseSpace[pulseCount] = pulseTime;
        if(timeout == 0) pulseCount++;  // don't save data just after timeout
    }
    else {                      // Low to High transition
        pulseMark[pulseCount] = pulseTime;
    }
    pulseTime = 0;
    timeout = 0;
}

Build
All the wires are soldered to Forebrain, and everything is fitted into the switch itself.  A hole is drilled on the front of the case for the IR receiver.  Annoyingly the steel frame made it difficult to drill the hole.



And all done!  One IR remote controlled USB switch for those wanting to share USB devices between consoles or media centres.


For those wanting to recreate this, the code for Forebrain is available here, and to find out more about Forebrain, go here.

5 comments:

  1. Great stuff - stumbled upon this blog from Google. This is EXACTLY what I need for my home entertainment setup - I find it hugely surprising (and frustrating) there doesn't appear to be anything like this on the market!

    Nice work - is it for sale? ;-)

    ReplyDelete
  2. Way awesome -- I totally need this! You aren't by any chance taking repeat requests are ya? Otherwise what IR receiver would you recommend?

    ReplyDelete
  3. Nice but isn't a USB host usually a PC or server of some kind? and don't most of these peripheral switches have bundled software that can switch them? Well then why not just use the PC's IR sensor and eventghost (or similar software) to trigger the bundled driver to do the switch. No HW or SW engineering.

    ReplyDelete
  4. Any chance of re-posting the source code and maybe making a schematic/parts list for the IR circuit? The current link to the source code is dead and I think this would be fun to recreate.

    ReplyDelete
  5. Thanks to Yuan at Universal Air, the Link is: http://www.universalair.co.uk/old/sites/default/files/guides/forebrain/examples/ir/irusbswitch/IRUSBswitch.zip

    ReplyDelete