USB remote control receiver for PowerDVD

Monday, 7th June 2010

I enjoy watching films and mainly do so sitting at my desktop PC. This has taught me that cheap office chairs are not the most comfortable things to sit on for extended periods of time, especially when the next room contains a comfortable bean bag and a good place to stick a screen. A gap between the two rooms allows me to pass cables from one to the other, and after purchasing a 10m DVI-D cable and a USB extension lead on eBay I had both picture and sound sorted out (I use a USB sound "card"). This left me with one final problem: how to control the PC through a wall.

One possibility would be to extend the lead on my keyboard, but its media buttons light up (bothersome in a darkened room) and some of the keyboard shortcuts in PowerDVD (such as Ctrl+P for the popup menu when watching Blu-ray discs) are tricky to hit in the dark. Given my fondness for infra-red remote controls building a remote control receiver would seem like both an interesting and useful way to spend a weekend.

USB remote control receiver prototype using an ATmega168

Rather than build something that relied on some Windows software to translate received remote control signals into keystrokes I decided to use the free V-USB library to construct something that showed up in Windows as a standard USB keyboard. One of the sample V-USB projects is a USB keyboard, which made getting started much easier! The above photograph shows the initial prototype, based around an ATmega168. The tall three-legged component sticking up out of the board is a TSOP2438, which is an infra-red receiver and demodulator. This is tuned to the 38kHz carrier employed by most remote controls and outputs a logic low or logic high depending on the presence or absence of such a signal. The ATmega168 is programmed to time the incoming signal and passes this timing information to a collection of routines that attempt to decode it. I have currently two decoders, one for the NEC protocol and another for SIRCS — information about some common protocols can be found on this website.

The choice of these two protocols is down to the remote controls I have around me. The one that offered me the most useful buttons was the PlayStation 2 DVD remote control (SIRCS), though this is missing some useful controls, such as volume and the red, green, yellow and blue buttons. To remedy this I went and bought a cheap universal remote control from Clas Ohlson. After hunting through several of the modes I settled on the Clas Ohlson DVD one (0815) as most of the buttons work in this mode (the only unshifted one that doesn't is the record button, and I can live without it). In this mode the remote control uses the NEC protocol.

USB remote control receiver prototype using an ATtiny84

To turn the receiver into something more conveniently sized I decided to switch from the 28-pin ATmega168 to the 14-pin ATtiny84, shown in the above photograph. The compiled program was already small enough to fit into the reduced memory, and the only modification I had to make was to amend two timing routines to share the same timer peripheral as the ATtiny84 only has two timers, not the three I'd been using on the ATmega168.

I also opted to add a switch to the design. One problem with supporting both Blu-ray and DVD is that the way you navigate menus is quite different between the two; Blu-ray discs use a simple popup menu (Ctrl+P) which appears on top of the film, whereas DVDs seem to offer a number of different menu commands — the two most common ones being "Title menu" (no shortcut) and "Root menu" (J). PowerDVD also lets you choose from a list of DVD menus in a context menu with one shortcut (L). I set the button on the receiver to switch between "Blu-ray" and "DVD" modes; in Blu-ray mode, the menu button sends Ctrl+P and in DVD mode the menu button sends L.

USB remote control receiver assembled on stripboard

I bought an enclosure that is, in retrospect, a little too small. The above photograph shows the receiver assembled on stripboard with a fairly cramped layout. Fortunately there was sufficient room to include pin headers on the board, which will allow me to plug in a programmer to modify the software should I need to in the future. The LED on the front serves as simple user feedback — it flashes whenever it receives a valid command and sends a keystroke back to the PC. When the mode is toggled between Blu-ray and DVD menus it flashes to indicate the new mode — a long flash followed by a single short one for Blu-ray, a long flash followed by two short ones for DVD.

USB remote control receiver circuit in its enclosure

Overall, I'm quite happy with the way it turned out. It works well enough for my needs, though as those needs only extend as far as PowerDVD and a particular remote control it's rather basic and much more could be done with the hardware. I have uploaded the source code and a schematic for the project to my website as it currently stands for those who are interested.

Finished USB remote control receiver

USB joypads and text on your TV courtesy of an ATmega168

Saturday, 14th November 2009

Nearly a month since my last update - my, how time flies when you're having fun (or a heavy workload).

I ended up building myself a cheap and cheerful SI Prog programmer for AVR development. After installing the development tools, scanning through the documentation and writing the microcontroller equivalent of Hello, World (flashing an LED on and off) I needed to find a suitable project. The first one was getting to grips with V-USB, a software USB implementation for AVRs. All you need for this are a couple of I/O pins, a few configuration file changes to set your USB device's vendor ID, product ID and device class, and a few lines of C code to actually implement your device. I attached six tactile switches to an ATmega168 and made the most uncomfortable USB joypad I've ever used. I managed two levels of Sonic the Hedgehog before my thumbs admitted defeat, but it's nice to know that building USB devices is very easy with an AVR.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <avr/sleep.h>

#include "usbdrv.h"

/* Joystick port bits */
#define JOY_1     (1<<0)
#define JOY_2     (1<<1)
#define JOY_UP    (1<<2)
#define JOY_DOWN  (1<<3)
#define JOY_LEFT  (1<<4)
#define JOY_RIGHT (1<<5)

/* USB HID report descriptor */
PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
    0x05, 0x01,        // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,        // USAGE (Game Pad)
    0xa1, 0x01,        // COLLECTION (Application)
    0x09, 0x01,        //   USAGE (Pointer)
    0xa1, 0x00,        //   COLLECTION (Physical)
    0x09, 0x30,        //     USAGE (X)
    0x09, 0x31,        //     USAGE (Y)
    0x15, 0x00,        //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,  //     LOGICAL_MAXIMUM (255)
    0x75, 0x08,        //   REPORT_SIZE (8)
    0x95, 0x02,        //   REPORT_COUNT (2)
    0x81, 0x02,        //   INPUT (Data,Var,Abs)
    0xc0,              // END_COLLECTION
    0x05, 0x09,        // USAGE_PAGE (Button)
    0x19, 0x01,        //   USAGE_MINIMUM (Button 1)
    0x29, 0x02,        //   USAGE_MAXIMUM (Button 2)
    0x15, 0x00,        //   LOGICAL_MINIMUM (0)
    0x25, 0x01,        //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,        // REPORT_SIZE (1)
    0x95, 0x08,        // REPORT_COUNT (8)
    0x81, 0x02,        // INPUT (Data,Var,Abs)
    0xc0               // END_COLLECTION
};

static uchar reportBuffer[3];    /* Buffer for HID reports */
static uchar idleRate;           /* 4 ms units */

uchar usbFunctionSetup(uchar data[8]) {
    usbRequest_t  *rq = (void*)data;
    usbMsgPtr = reportBuffer;
    if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {
        switch (rq->bRequest) {
            case USBRQ_HID_GET_REPORT:
                return sizeof(reportBuffer);
            case USBRQ_HID_GET_IDLE:
                usbMsgPtr = &idleRate;
                return 1;
            case USBRQ_HID_SET_IDLE:
                idleRate = rq->wValue.bytes[1];
                break;
        }
    }
    return 0;
}

ISR(TIMER0_OVF_vect) {

    /* Fetch input */
    uchar input = ~PINC;
    
    /* X-axis */
    switch (input & (JOY_LEFT | JOY_RIGHT)) {
        case JOY_LEFT:
            reportBuffer[0] = 0;
            break;
        case JOY_RIGHT:
            reportBuffer[0] = 255;
            break;
        default:
            reportBuffer[0] = 128;
            break;
    }

    /* Y-axis */
    switch (input & (JOY_UP | JOY_DOWN)) {
        case JOY_UP:
            reportBuffer[1] = 0;
            break;
        case JOY_DOWN:
            reportBuffer[1] = 255;
            break;
        default:
            reportBuffer[1] = 128;
            break;
    }

    /* Buttons */
    reportBuffer[2] = input & (JOY_1 | JOY_2);

    usbPoll();
    usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
};

int main(void) {

    usbInit();              /* Initialise USB. */

    PORTC = 0b00111111;     /* Pull high PORTC0..PORTC5 */
    
    TCCR0B = 0b00000101;    /* CS2..CS0 = 101:  prescaler = /1024 */
    TIMSK0 |= (1 << TOIE0); /* Enable timer 0 overflow interrupt. */
    sei();                  /* Enable global interrupts. */
    
    for (;;) {
        /* Infinite loop */
    }
}

I should only really call usbSetInterrupt when a button or axis has changed, rather than every loop, but the above code works as is.

One thing that always bothers me when it comes to electronic projects is the difficulty of providing text output. LCDs are generally quite expensive and low resolution, and typically require a great many pins to drive them. Video display processor chips are difficult to find, and appear to require quite complex external circuitry (the best thing I've found thus far are some TMS9918 chips being sold as spares for MSX computers). Having briefly experimented with generating PAL video signals in software before, I thought I'd try the two-resistor approach to getting PAL video output on an ATmega168.

I had a hunt around and found AVGA, which is close to what I wanted - video output from an AVR using cheap hardware. However, it outputs RGB directly, and I don't own a TV or RGB converter so couldn't use that - all I have is a VGA box (accepting composite or S-Video input) and a TV capture card (also only accepting composite or S-Video input). AVGA does work with VGA monitors, but I'd like to keep the hardware interface simple - just two resistors, ideally.

tvText demo screen

In the end, I ended up writing my own library. It currently has the following specifications:

  • 32×16 characters: 512 bytes (half of the total SRAM on the ATmega168) are used to store the text buffer.
  • Full 256 characters at a resolution of 6×8 pixels each.
  • Total screen resolution: 192×128.

The library is interrupt-driven, and uses the sixteen-bit TIMER1 to schedule events. This means that the AVR is only busy generating video signals when it absolutely has to, leaving some CPU time to the user program. When outputting at full quality, the AVR appears to be capable of running user code at 3.3 MIPS, but by skipping alternate scanlines (each scanline is scanned twice anyway, so this mainly just makes the display appear darker) the AVR appears to be running user code at 9.9 MIPS. (I say "appears" as my calculation has been to execute a busy loop that would normally take one second on the AVR running at its normal 20 MIPS then seeing how long it takes with the video output driver enabled).

The above video demonstrates some of the currently rather limited features of the library. The text console handles a subset of the BBC Micro VDU commands - I'd like to support as many of its features as possible. The code behind the BASIC-like part of the demo is simply written like this:

#include "tvtext/tvtext.h"

void type_string_P(const char* s) {
    char c;
    while ((c = pgm_read_byte(s++))) {
        tvtext_putc(c);
        delay_ms(100);
    }
}

int main(void) {

    tvtext_init();

    tvtext_clear();
    tvtext_puts_P(PSTR("AVR Computer 1K\r\n\nATmega 168\r\n\nBASIC\r\n\n>"));
    delay_ms(2000);
    type_string_P(PSTR("10 PRINT \"AVR Rules! \";\r\n"));
    tvtext_putc('>');
    delay_ms(500);
    type_string_P(PSTR("20 GOTO 10\r\n"));
    tvtext_putc('>');
    delay_ms(500);
    type_string_P(PSTR("RUN"));
    delay_ms(1000);
    tvtext_puts_P(PSTR("\r\n"));

    for (int i = 0; i <= 200; ++i) {
        tvtext_puts_P(PSTR("AVR Rules! "));
        delay_ms(20);
    }

    tvtext_puts_P(PSTR("\r\nEscape at line 10\r\n>"));
    delay_ms(1000);
    type_string_P(PSTR("CHAIN \"DEMO\""));
    delay_ms(1000);
    
    // ...

}

All of the high-level console code - text output, viewport scrolling, cursor positioning &c - has been written in C, so should be relatively easy to be customised. The output driver itself has been written in assembly as timing is critically important.

With a few more features and a bit of tidying up I hope that people would find this a useful library. I'd certainly like to get a blinking cursor working within the driver, and if I add support for a reduced 128-character version I could save quite a bit of ROM space and add support for "coloured" - inverted, that is - text. NTSC support would also be quite useful.

VMusic2 - USB for the 83+

Monday, 21st May 2007

The TI-83+ lacks something the 84+ series has - a USB port.

VMusic2.jpg

Enter the VMusic2. This low-cost (£25) module offers a USB host controller with a simple serial interface that can be used to read/write FAT-formatted USB mass storage devices. It can also play MP3 files straight from the drive!

PICAXE-28X1.jpg

This is all very well, but the TI doesn't have a standard serial port either. To handle communications between the two, therefore, is a PICAXE-28X1 microcontroller.

The TI can then run a program that communicates using its standard linking protocol.

Browser.gif

I've posted a thread on MaxCoderz with more information about the project. For those interested in the VMusic2 device, here's a datasheet and here are the commands.

Yes, I know I should probably get a life. I blame the solder fumes.

Subscribe to an RSS feed that only contains items with the USB tag.

FirstLast RSSSearchBrowse by dateIndexTags