• entries
222
606
• views
591098

# USB joypads and text on your TV courtesy of an ATmega168

1810 views

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 #include #include #include #include #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.

In the end, I ended up writing my own library. It currently has the following specifications:
• 32x16 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 6x8 pixels each.
• Total screen resolution: 192x128.
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.

Have you seen this: http://www.sparkfun.com/commerce/product_info.php?products_id=8541? It's a cheapish VGA graphics controller with a simple serial interface. Sparkfun (What I've linked to) sell a version soldered to a PCB with an SRAM chip all integrated together with DIL breakout pins so you could easily plug it into a breadboard. Won't give you TV output though as far as I know.

Quote:
 Original post by Monder Have you seen this: http://www.sparkfun.com/commerce/product_info.php?products_id=8541? It's a cheapish VGA graphics controller with a simple serial interface. Sparkfun (What I've linked to) sell a version soldered to a PCB with an SRAM chip all integrated together with DIL breakout pins so you could easily plug it into a breadboard. Won't give you TV output though as far as I know.
No, I hadn't seen it and it looks quite practical and very powerful. Thanks! Certainly one to bear in mind, but I'm not sure I'd agree with "cheapish" - excluding the RCA jack, you could pick up all the parts for the ATmega168 solution for under £5, including P&P.

I've just found what looks like the same product from a british supplier as well: http://www.coolcomponents.co.uk/catalog/product_info.php?cPath=45&products_id=179. It might cost a little more, but you don't have to add on the extras of getting it across the Atlantic and VAT/Duty charges :)

Quote:
 Original post by Monder It might cost a little more, but you don't have to add on the extras of getting it across the Atlantic and VAT/Duty charges :)
I looked into Parallax Propellers a while back, but hastily closed the browser window when I saw they were going to charge \$80 to ship to the UK! Cool Components looks like a decent site, I've added it to my bookmarks. I buy most of my components from Bitsbox these days; they may not have the range that the bigger companies may have, but no minimum order and a fixed postage price of £1.50 makes it worth it!

## Create an account

Register a new account