Page 1 of 1

USBASP / ISP Programmer as V-USB

Posted: Fri Aug 15, 2014 12:14 pm
by Ripper121
I have ordered 2 USBASP sticks.
This is the schematic:
Image

Can I use this device as V-USB device?
Can you help me to Implement a 4 Button HID Keyboard?

Ripper121

Re: USBASP / ISP Programmer as V-USB

Posted: Fri Aug 15, 2014 10:48 pm
by blargg
Yes, it can be used for V-USB (and in fact the USBasp firmware itself uses V-USB). You can easily reprogram the second one with the first. It would be great for a four-button keyboard, as it has at least four I/O pins on the end connector: PB2, PB3, PB4, and PB5:

Code: Select all

          ------
MOSI PB3 | 1  2 | +5V
     N/C | 3  4 | PD1 TXD (via 1K resistor)
/RES PB2   5  6 | PD0 RXD
 SCK PB5 | 7  8 | GND
MISO PB4 | 9 10 | GND
          ------

Male end on programmer


Once you get them I'd be glad to help out, as I love using USBasp sticks as inexpensive V-USB development boards.

Re: USBASP / ISP Programmer as V-USB

Posted: Sat Aug 16, 2014 12:04 am
by Ripper121
Yes my way of thinking. Cheapest hardware i found was 1,72€.
And this version has 5 (6 when i use the reset also) free pins and with 6pins = 6 bit there are 63 Keys possible.

If you can test it with a USBASP stick can you send me pleas the code?
I can begin to develope and test i hope next week when i recive the parts.

my Mail is: Ripper121(at)gmail.com for future contact

Re: USBASP / ISP Programmer as V-USB

Posted: Sat Aug 16, 2014 2:13 am
by blargg
I modified HIDKeys to work with the usbasp and four switches connected to PB2-PB5 and took pictures of the steps. In the pictures I show how an inexpensive set of "dupont" male-male cables makes it nice to use with a breadboard.

My steps: connect one programmer to another, bridge JP2 on the one being programmed (not connected to USB). Connect other to USB. make flash to put HIDKeys on. Disconnect reprogrammed USBasp. Connect switches (one between PB2 and GND, next PB3 and GND, etc.). Verify wiring. Connect to PC. Type ABCD.

Oh and once all this is working and you're comfortable with it, you can put the USBaspLoader bootloader on the device and be able to reflash it without having to connect the programmer.

Re: USBASP / ISP Programmer as V-USB

Posted: Sat Aug 16, 2014 11:52 am
by ripper121
With what for software do you upload it?
Do you have change any Fusebits or settings to flash it?
Can you write me pleas a mail?
Is this the 12MHz quarz on the stick and a Atmega8 L?

Ripper121[at]googlemail.com

I need exactly what you have done. But it should work with 8 keys.

Re: USBASP / ISP Programmer as V-USB

Posted: Sat Aug 16, 2014 8:49 pm
by blargg
I use avrdude to flash it. I didn't change any fusebits since naturally USBasp needs the same settings as almost any V-USB program would. It is a 12MHz quartz crystal with atmega8A. That is' not an atmega8L shouldn't matter for this use.

Can you help me to Implement a 4 Button HID Keyboard?


But it should work with 8 keys.


Oh, change in requirements to double the number of keys I see. :) For double the number, a scanning line would probably be best. I can add that to the code as I'm experimenting with it further today.

I'll contact you in e-mail once you get your boards and have something definite to work with. I'm sharing the rest here in the forum so others can benefit too.

Re: USBASP / ISP Programmer as V-USB

Posted: Sat Aug 16, 2014 8:56 pm
by ripper121
Ok big thanks.
Can't wait to test it.

Re: USBASP / ISP Programmer as V-USB

Posted: Tue Aug 26, 2014 6:50 pm
by ripper121
This is my schematic.

Image

This is my source:
Download

Code: Select all

/* Name: main.c
 * Project: HID-Test
 * Author: Christian Starkjohann
 * Creation Date: 2006-02-02
 * Tabsize: 4
 * Copyright: (c) 2006 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: GNU GPL v2 (see License.txt) or proprietary (CommercialLicense.txt)
 * This Revision: $Id$
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>

#include "usbdrv.h"
#include "oddebug.h"

/* ----------------------- hardware I/O abstraction ------------------------ */

/* pin assignments:
PB0   Key 1
PB1   Key 2
PB2   Key 3
PB3   Key 4
PB4   Key 5
PB5 Key 6

PC0   Key 7
PC1   Key 8
PC2   Key 9
PC3   Key 10
PC4   Key 11
PC5   Key 12

PD0   USB-
PD1   debug tx
PD2   USB+ (int0)
PD3   Key 13
PD4   Key 14
PD5   Key 15
PD6   Key 16
PD7   Key 17
*/


uint8_t keyscan( void )
{      
   
      DDRB &= ~( (1<<PB4) );  // PB4 als Eingaenge
      PORTB |= ( (1<<PB4) );  // PB4 Pull-Up einschalten
      DDRB |= (1 << PB5); // Ausgang
      PORTB &= ~(1 << PB5); //Low
      
      if (!(PIND & (1<<PIND1))){
         if (!(PINB & (1<<PINB4))) {            
            return 1;   
         }
      }      
      if (!(PIND & (1<<PIND0))) {
         if (!(PINB & (1<<PINB4))) {            
            return 2;   
         }
      }      
      if (!(PINB & (1<<PINB2))) {            
         if (!(PINB & (1<<PINB4))) {            
            return 3;   
         }   
      }      
      if (!(PINB & (1<<PINB3))) {            
         if (!(PINB & (1<<PINB4))) {            
            return 4;   
         }         
      }
      
      DDRB &= ~( (1<<PB5) );  // PB5 als Eingaenge
      PORTB |= ( (1<<PB5) );  // PB5 Pull-Up einschalten
      DDRB |= (1 << PB4); // Ausgang
      PORTB &= ~(1 << PB4); //Low

      if (!(PIND & (1<<PIND1))) {
         if (!(PINB & (1<<PINB5))) {            
            return 5;   
         }
      }      
      if (!(PIND & (1<<PIND0))) {
         if (!(PINB & (1<<PINB5))) {            
            return 6;   
         }   
      }      
      if (!(PINB & (1<<PINB2))) {      
         if (!(PINB & (1<<PINB5))) {            
            return 7;   
         }   
      }      
      if (!(PINB & (1<<PINB3))) {
         if (!(PINB & (1<<PINB5))) {            
            return 8;   
         }         
      }
      return 0;
}


static void hardwareInit(void)
{
   DDRC |= (1 << PC0); // Ausgang
   PORTC &= ~(1 << PC0);  //PB1 High
   DDRC |= (1 << PC1); // Ausgang
   PORTC &= ~(1 << PC1);  //PB1 High
   DDRB &= ~( (1<<PB2) );  // PB2 als Eingaenge
   PORTB |= ( (1<<PB2) );  // PB2 Pull-Up einschalten
   DDRB &= ~( (1<<PB3) );  // PB3 als Eingaenge
   PORTB |= ( (1<<PB3) );  // PB3 Pull-Up einschalten
   DDRD &= ~( (1<<PD0) );  // PD0 als Eingaenge
   PORTD |= ( (1<<PD0) );  // PD0 Pull-Up einschalten
   DDRD &= ~( (1<<PD1) );  // PD1 als Eingaenge
   PORTD |= ( (1<<PD1) );  // PD1 Pull-Up einschalten   
   
   /* configure timer 0 for a rate of 12M/(1024 * 256) = 45.78 Hz (~22ms) */
   TCCR0 = 5;      /* timer 0 prescaler: 1024 */
}

/* ------------------------------------------------------------------------- */

#define NUM_KEYS    8

/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */

static uchar    reportBuffer[2];    /* buffer for HID reports */
static uchar    idleRate;           /* in 4 ms units */

const PROGMEM char usbHidReportDescriptor[35] = {   /* USB report descriptor */
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    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)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    0xc0                           // END_COLLECTION
};
/* We use a simplifed keyboard report descriptor which does not support the
 * boot protocol. We don't allow setting status LEDs and we only allow one
 * simultaneous key press (except modifiers). We can therefore use short
 * 2 byte input reports.
 * The report descriptor has been created with usb.org's "HID Descriptor Tool"
 * which can be downloaded from http://www.usb.org/developers/hidpage/.
 * Redundant entries (such as LOGICAL_MINIMUM and USAGE_PAGE) have been omitted
 * for the second INPUT item.
 */

/* Keyboard usage values, see usb.org's HID-usage-tables document, chapter
 * 10 Keyboard/Keypad Page for more codes.
 */
#define MOD_CONTROL_LEFT    (1<<0)
#define MOD_SHIFT_LEFT      (1<<1)
#define MOD_ALT_LEFT        (1<<2)
#define MOD_GUI_LEFT        (1<<3)
#define MOD_CONTROL_RIGHT   (1<<4)
#define MOD_SHIFT_RIGHT     (1<<5)
#define MOD_ALT_RIGHT       (1<<6)
#define MOD_GUI_RIGHT       (1<<7)

#define KEY_A       4
#define KEY_B       5
#define KEY_C       6
#define KEY_D       7
#define KEY_E       8
#define KEY_F       9
#define KEY_G       10
#define KEY_H       11
#define KEY_I       12
#define KEY_J       13
#define KEY_K       14
#define KEY_L       15
#define KEY_M       16
#define KEY_N       17
#define KEY_O       18
#define KEY_P       19
#define KEY_Q       20
#define KEY_R       21
#define KEY_S       22
#define KEY_T       23
#define KEY_U       24
#define KEY_V       25
#define KEY_W       26
#define KEY_X       27
#define KEY_Y       28
#define KEY_Z       29
#define KEY_1       30
#define KEY_2       31
#define KEY_3       32
#define KEY_4       33
#define KEY_5       34
#define KEY_6       35
#define KEY_7       36
#define KEY_8       37
#define KEY_9       38
#define KEY_0       39

#define KEY_F1      58
#define KEY_F2      59
#define KEY_F3      60
#define KEY_F4      61
#define KEY_F5      62
#define KEY_F6      63
#define KEY_F7      64
#define KEY_F8      65
#define KEY_F9      66
#define KEY_F10     67
#define KEY_F11     68
#define KEY_F12     69

static const uchar  keyReport[NUM_KEYS + 1][2] PROGMEM = {
/* none */  {0, 0},                     /* no key pressed */
/*  1 */    {MOD_SHIFT_LEFT, KEY_A},
/*  2 */    {MOD_SHIFT_LEFT, KEY_B},
/*  3 */    {MOD_SHIFT_LEFT, KEY_C},
/*  4 */    {MOD_SHIFT_LEFT, KEY_D},
/*  5 */    {MOD_SHIFT_LEFT, KEY_E},
/*  6 */    {MOD_SHIFT_LEFT, KEY_F},
/*  7 */    {MOD_SHIFT_LEFT, KEY_G},
/*  8 */    {MOD_SHIFT_LEFT, KEY_H},
};

static void buildReport(uchar key)
{
/* This (not so elegant) cast saves us 10 bytes of program memory */
    *(int *)reportBuffer = pgm_read_word(keyReport[key]);
}

uchar   usbFunctionSetup(uchar data[8])
{
usbRequest_t    *rq = (void *)data;

    usbMsgPtr = reportBuffer;
    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* class request type */
        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
            /* we only have one report type, so don't look at wValue */
            buildReport(keyscan());
            return sizeof(reportBuffer);
        }else if(rq->bRequest == USBRQ_HID_GET_IDLE){
            usbMsgPtr = &idleRate;
            return 1;
        }else if(rq->bRequest == USBRQ_HID_SET_IDLE){
            idleRate = rq->wValue.bytes[1];
        }
    }else{
        /* no vendor specific requests implemented */
    }
   return 0;
}

/* ------------------------------------------------------------------------- */

int   main(void)
{
uchar   key, lastKey = 0, keyDidChange = 0;
uchar   idleCounter = 0;

   wdt_enable(WDTO_2S);
    hardwareInit();
   odDebugInit();
   usbInit();
   sei();
    DBG1(0x00, 0, 0);
   for(;;){   /* main event loop */
      wdt_reset();
      usbPoll();
        key = keyscan();      
         if(lastKey != key){
            lastKey = key;
            keyDidChange = 1;
         }
         if(TIFR & (1<<TOV0)){   /* 22 ms timer */
            TIFR = 1<<TOV0;
            if(idleRate != 0){
               if(idleCounter > 4){
                  idleCounter -= 5;   /* 22 ms in units of 4 ms */
               }else{
                  idleCounter = idleRate;
                  keyDidChange = 1;
               }
            }
         }
         if(keyDidChange && usbInterruptIsReady()){
            keyDidChange = 0;
            /* use last key and not current key status in order to avoid lost
               changes in key status. */
            buildReport(lastKey);
            usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
         }
   }
   return 0;
}

/* ------------------------------------------------------------------------- */


But it dont work for me, i cant read out the Key Matrix :(.

Re: USBASP / ISP Programmer as V-USB

Posted: Wed Aug 27, 2014 2:33 am
by blargg
Three problems fixed and your code works for me. First, you mistakenly used PINxx as the bit number almost everywhere:

Code: Select all

      if (!(PIND & (1<<PIND0))) { // error
      
      if (!(PIND & (1<<PD0))) { // correct
      
      if (!(PIND & (1<<0))) { // what I use


The Pxx constants for port bits aren't very useful IMO. I just use the bit number. True, the Pxx constants shield code from Atmel some day putting bit 0 of a port on a different bit in the register, but that seems too remote to guard against.

The next problem is a minor logic error:

Code: Select all

   uint8_t keyscan( void )
   {     
      PORTB |= ( (1<<PB4) );  // PB4 Pull-Up einschalten
      [...]
      PORTB &= ~(1 << PB5); //Low

      if (!(PIND & (1<<PD1))){
         if (!(PINB & (1<<PB4))) {   
            return 1;   
         }
      }     


You check PB4 for low when you've got it pulled up. I thought you meant to check PB5, but that would be pointless since you know PB5 is low. I'm thinking maybe this is a check for multiple keys pressed (ghosting) so you don't rept a phantom key press. In that case, maybe you wanted if(PINB & (1<<PB4)).

Finally, put a delay after setting the scan lines, to give them time to reach their new levels. This is especially true if you're checking for phantom keys by using the one pulled up, as that will take a little time to reach high level. 200 microseconds works for me:

Code: Select all

   uint8_t keyscan( void )
   {     
      PORTB |= ( (1<<PB4) );  // PB4 Pull-Up einschalten
      [...]
      PORTB &= ~(1 << PB5); //Low

      _delay_us( 200 ); // added
      
      if (!(PIND & (1<<PD1))){
         if ((PINB & (1<<PB4))) {   
            return 1;   
         }
      }     


One final change, as the original code doesn't do any debouncing. Only scan the keys when USB is ready for new updates. This puts an 8ms delay after key changes and avoids most bouncing:

Code: Select all

   for(;;){   /* main event loop */
      wdt_reset();
      usbPoll();
      if(TIFR & (1<<TOV0)){   /* 22 ms timer */
         TIFR = 1<<TOV0;
         if(idleRate != 0){
            if(idleCounter > 4){
               idleCounter -= 5;   /* 22 ms in units of 4 ms */
            }else{
               idleCounter = idleRate;
               keyDidChange = 1;
            }
         }
      }
      if(usbInterruptIsReady()){ // only scan if USB is ready
           key = keyscan();      
         if(lastKey != key){
            lastKey = key;
            keyDidChange = 1;
         }
         if(keyDidChange){
            keyDidChange = 0;
            /* use last key and not current key status in order to avoid lost
               changes in key status. */
            buildReport(lastKey);
            usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
         }
      }
   }


Complete main.c:

Code: Select all

/* Name: main.c
 * Project: HID-Test
 * Author: Christian Starkjohann
 * Creation Date: 2006-02-02
 * Tabsize: 4
 * Copyright: (c) 2006 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: GNU GPL v2 (see License.txt) or proprietary (CommercialLicense.txt)
 * This Revision: $Id$
 */

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

#include "usbdrv.h"
#include "oddebug.h"

/* ----------------------- hardware I/O abstraction ------------------------ */

/* pin assignments:
PB0   Key 1
PB1   Key 2
PB2   Key 3
PB3   Key 4
PB4   Key 5
PB5 Key 6

PC0   Key 7
PC1   Key 8
PC2   Key 9
PC3   Key 10
PC4   Key 11
PC5   Key 12

PD0   USB-
PD1   debug tx
PD2   USB+ (int0)
PD3   Key 13
PD4   Key 14
PD5   Key 15
PD6   Key 16
PD7   Key 17
*/


uint8_t keyscan( void )
{
      DDRB &= ~( (1<<PB4) );  // PB4 als Eingaenge
      PORTB |= ( (1<<PB4) );  // PB4 Pull-Up einschalten
      DDRB |= (1 << PB5); // Ausgang
      PORTB &= ~(1 << PB5); //Low
      
      _delay_us( 200 );
      
      if (!(PIND & (1<<PD1))){
         if ((PINB & (1<<PB4))) {            
            return 1;   
         }
      }      
      if (!(PIND & (1<<PD0))) {
         if ((PINB & (1<<PB4))) {            
            return 2;   
         }
      }      
      if (!(PINB & (1<<PB2))) {            
         if ((PINB & (1<<PB4))) {            
            return 3;   
         }   
      }      
      if (!(PINB & (1<<PB3))) {            
         if ((PINB & (1<<PB4))) {            
            return 4;   
         }         
      }
      
      DDRB &= ~( (1<<PB5) );  // PB5 als Eingaenge
      PORTB |= ( (1<<PB5) );  // PB5 Pull-Up einschalten
      DDRB |= (1 << PB4); // Ausgang
      PORTB &= ~(1 << PB4); //Low

      _delay_us( 200 );
      
      if (!(PIND & (1<<PD1))) {
         if ((PINB & (1<<PB5))) {            
            return 5;   
         }
      }      
      if (!(PIND & (1<<PD0))) {
         if ((PINB & (1<<PB5))) {            
            return 6;   
         }   
      }      
      if (!(PINB & (1<<PB2))) {      
         if ((PINB & (1<<PB5))) {            
            return 7;   
         }   
      }      
      if (!(PINB & (1<<PB3))) {
         if ((PINB & (1<<PB5))) {            
            return 8;   
         }         
      }
      return 0;
}


static void hardwareInit(void)
{
   DDRC |= (1 << PC0); // Ausgang
   PORTC &= ~(1 << PC0);  //PB1 High
   DDRC |= (1 << PC1); // Ausgang
   PORTC &= ~(1 << PC1);  //PB1 High
   DDRB &= ~( (1<<PB2) );  // PB2 als Eingaenge
   PORTB |= ( (1<<PB2) );  // PB2 Pull-Up einschalten
   DDRB &= ~( (1<<PB3) );  // PB3 als Eingaenge
   PORTB |= ( (1<<PB3) );  // PB3 Pull-Up einschalten
   DDRD &= ~( (1<<PD0) );  // PD0 als Eingaenge
   PORTD |= ( (1<<PD0) );  // PD0 Pull-Up einschalten
   DDRD &= ~( (1<<PD1) );  // PD1 als Eingaenge
   PORTD |= ( (1<<PD1) );  // PD1 Pull-Up einschalten   
   
   /* configure timer 0 for a rate of 12M/(1024 * 256) = 45.78 Hz (~22ms) */
   TCCR0 = 5;      /* timer 0 prescaler: 1024 */
}

/* ------------------------------------------------------------------------- */

#define NUM_KEYS    8

/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */

static uchar    reportBuffer[2];    /* buffer for HID reports */
static uchar    idleRate;           /* in 4 ms units */

const PROGMEM char usbHidReportDescriptor[35] = {   /* USB report descriptor */
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    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)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    0xc0                           // END_COLLECTION
};
/* We use a simplifed keyboard report descriptor which does not support the
 * boot protocol. We don't allow setting status LEDs and we only allow one
 * simultaneous key press (except modifiers). We can therefore use short
 * 2 byte input reports.
 * The report descriptor has been created with usb.org's "HID Descriptor Tool"
 * which can be downloaded from http://www.usb.org/developers/hidpage/.
 * Redundant entries (such as LOGICAL_MINIMUM and USAGE_PAGE) have been omitted
 * for the second INPUT item.
 */

/* Keyboard usage values, see usb.org's HID-usage-tables document, chapter
 * 10 Keyboard/Keypad Page for more codes.
 */
#define MOD_CONTROL_LEFT    (1<<0)
#define MOD_SHIFT_LEFT      (1<<1)
#define MOD_ALT_LEFT        (1<<2)
#define MOD_GUI_LEFT        (1<<3)
#define MOD_CONTROL_RIGHT   (1<<4)
#define MOD_SHIFT_RIGHT     (1<<5)
#define MOD_ALT_RIGHT       (1<<6)
#define MOD_GUI_RIGHT       (1<<7)

#define KEY_A       4
#define KEY_B       5
#define KEY_C       6
#define KEY_D       7
#define KEY_E       8
#define KEY_F       9
#define KEY_G       10
#define KEY_H       11
#define KEY_I       12
#define KEY_J       13
#define KEY_K       14
#define KEY_L       15
#define KEY_M       16
#define KEY_N       17
#define KEY_O       18
#define KEY_P       19
#define KEY_Q       20
#define KEY_R       21
#define KEY_S       22
#define KEY_T       23
#define KEY_U       24
#define KEY_V       25
#define KEY_W       26
#define KEY_X       27
#define KEY_Y       28
#define KEY_Z       29
#define KEY_1       30
#define KEY_2       31
#define KEY_3       32
#define KEY_4       33
#define KEY_5       34
#define KEY_6       35
#define KEY_7       36
#define KEY_8       37
#define KEY_9       38
#define KEY_0       39

#define KEY_F1      58
#define KEY_F2      59
#define KEY_F3      60
#define KEY_F4      61
#define KEY_F5      62
#define KEY_F6      63
#define KEY_F7      64
#define KEY_F8      65
#define KEY_F9      66
#define KEY_F10     67
#define KEY_F11     68
#define KEY_F12     69

static const uchar  keyReport[NUM_KEYS + 1][2] PROGMEM = {
/* none */  {0, 0},                     /* no key pressed */
/*  1 */    {MOD_SHIFT_LEFT, KEY_A},
/*  2 */    {MOD_SHIFT_LEFT, KEY_B},
/*  3 */    {MOD_SHIFT_LEFT, KEY_C},
/*  4 */    {MOD_SHIFT_LEFT, KEY_D},
/*  5 */    {MOD_SHIFT_LEFT, KEY_E},
/*  6 */    {MOD_SHIFT_LEFT, KEY_F},
/*  7 */    {MOD_SHIFT_LEFT, KEY_G},
/*  8 */    {MOD_SHIFT_LEFT, KEY_H},
};

static void buildReport(uchar key)
{
/* This (not so elegant) cast saves us 10 bytes of program memory */
    *(int *)reportBuffer = pgm_read_word(keyReport[key]);
}

uchar   usbFunctionSetup(uchar data[8])
{
usbRequest_t    *rq = (void *)data;

    usbMsgPtr = reportBuffer;
    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){    /* class request type */
        if(rq->bRequest == USBRQ_HID_GET_REPORT){  /* wValue: ReportType (highbyte), ReportID (lowbyte) */
            /* we only have one report type, so don't look at wValue */
            buildReport(keyscan());
            return sizeof(reportBuffer);
        }else if(rq->bRequest == USBRQ_HID_GET_IDLE){
            usbMsgPtr = &idleRate;
            return 1;
        }else if(rq->bRequest == USBRQ_HID_SET_IDLE){
            idleRate = rq->wValue.bytes[1];
        }
    }else{
        /* no vendor specific requests implemented */
    }
   return 0;
}

/* ------------------------------------------------------------------------- */

int   main(void)
{
uchar   key, lastKey = 0, keyDidChange = 0;
uchar   idleCounter = 0;

   wdt_enable(WDTO_2S);
    hardwareInit();
   odDebugInit();
   usbInit();
   sei();
    DBG1(0x00, 0, 0);
   for(;;){   /* main event loop */
      wdt_reset();
      usbPoll();
      if(TIFR & (1<<TOV0)){   /* 22 ms timer */
         TIFR = 1<<TOV0;
         if(idleRate != 0){
            if(idleCounter > 4){
               idleCounter -= 5;   /* 22 ms in units of 4 ms */
            }else{
               idleCounter = idleRate;
               keyDidChange = 1;
            }
         }
      }
      if(usbInterruptIsReady()){
           key = keyscan();      
         if(lastKey != key){
            lastKey = key;
            keyDidChange = 1;
         }
         if(keyDidChange){
            keyDidChange = 0;
            /* use last key and not current key status in order to avoid lost
               changes in key status. */
            buildReport(lastKey);
            usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
         }
      }
   }
   return 0;
}

/* ------------------------------------------------------------------------- */