554 lines
21 KiB
C
554 lines
21 KiB
C
/**
|
|
* \file firmware/main.c
|
|
* \brief Main functions for USB-keyboard
|
|
* \author Ronald Schaten <ronald@schatenseite.de>
|
|
* \version $Id: main.c,v 1.6 2008/07/15 05:16:41 rschaten Exp $
|
|
*
|
|
* License: GNU GPL v2 (see License.txt)
|
|
*/
|
|
|
|
/**
|
|
* \mainpage Dulcimer
|
|
*
|
|
* \section sec_intro Introduction
|
|
*
|
|
* A computer keyboard can be a very personal utensil. Especially if it is an
|
|
* extraordinary well built one, like for example the IBM Model M. The Model M
|
|
* design dates back to 1984, but it still has many fans even nowadays. It came
|
|
* with the usual keyboard connectors. First the old 5-pin one, later a PS/2
|
|
* plug. Unfortunately is that, at least to my knowledge, they never released a
|
|
* version with USB.
|
|
*
|
|
* A friend of mine knew that I already had built other USB-devices, and one of
|
|
* them even acted as a keyboard (it isn't really a keyboard, but that's a
|
|
* different story... ;-) ). He is a big fan of the Model M, so he asked if I
|
|
* could put new life in one of his old keyboards, which had a broken circuit
|
|
* inside. And this is the result...
|
|
*
|
|
* \subsection sec_technique Hard- and Software
|
|
*
|
|
* The main part of a computer keyboard circuit is the key matrix. You can
|
|
* imagine it as a number of keys, placed on a raster of horizontal (rows) and
|
|
* vertical (columns) wires. In the case of a Model M keyboard, we have a
|
|
* matrix of 8x16 lines. Eight columns in 16 rows, or the other way around,
|
|
* depending on how you look at it. Each key is connected to one column and one
|
|
* row. If you press the key, it will connect the column and the row on it's
|
|
* crossing of the lines.
|
|
*
|
|
* Connected to this matrix is a keyboard controller. That's a chip with a
|
|
* number of I/O-lines to detect the state of the matrix, and on the other side
|
|
* an interface that enables it to talk to the computer. Oh, and not to forget:
|
|
* it also has three output lines to drive the LEDs for Num-, Caps- and
|
|
* Scroll-lock.
|
|
*
|
|
* What I did in this project is, that I dumped the keyboard controller chip
|
|
* and its circuit, and replaced it by an ATmega32 and my own circuit. The
|
|
* ATmega scans the matrix for keyboard activity, controls the LEDs and talks
|
|
* to the computer.
|
|
*
|
|
* For further convenience, I added a boot-loader. With that, it is possible to
|
|
* update the keyboard's firmware without disassembling it, and without the
|
|
* need for a dedicated programmer.
|
|
*
|
|
* \subsection sec_hardware Other hardware?
|
|
*
|
|
* As mentioned, the controller in this project is just connected to an
|
|
* ordinary keyboard matrix. You can find this kind of matrix in all kinds of
|
|
* keyboards, from key-telephones over good old hardware like the Commodore
|
|
* C=64 or the Schneider CPC, keyboards with non-PC-connectors like those made
|
|
* by Sun, to modern hardware that could need a few more features.
|
|
*
|
|
* Till now, I just made a PCB layout for the IBM Model M, but I intend to
|
|
* modify at least a Sun keyboard. In order to do that, I expect having to
|
|
* refactor the key-scanning, since the key-matrix is not 16x8. The positions
|
|
* of the keys on the matrix will be different, I'll have to re-engineer that.
|
|
* And of course, I'll have to make another PCB.
|
|
*
|
|
* \subsection sec_features Features
|
|
*
|
|
* At the moment, the keyboard should be able to do everything that the average
|
|
* off-the-shelf-keyboard can do. But there are many features that are
|
|
* possible, regarding the fact that the ATmega32 is absolutely bored till now.
|
|
* You can think of 'magic keystrokes' that turn some hidden features on or
|
|
* off, like for example:
|
|
* - send complete phrases on one keystroke
|
|
* - 'autofire' feature on keys that don't repeat normally, for example Alt+F4
|
|
* - change keyboard layout without reconfiguring the computer
|
|
* - turn bouncing keys on or off, to annoy other people using your computer
|
|
* - random caps lock function
|
|
* - use arrow keys as mouse, without having to include a special driver in
|
|
* the OS.
|
|
*
|
|
* With a little tweaking on the hardware side, there should be even more
|
|
* possibilities:
|
|
* - turn the oldtimer-keyboard into a supermodern wireless bluetooth one
|
|
* - implement keylogger-funktionality, using for example an SD-card
|
|
* - include an USB-hub into the keyboard
|
|
*
|
|
* If you are just a little like me, it won't take you much brainstorming to
|
|
* come up with own useful -- or even better: useless -- ideas. ;-)
|
|
*
|
|
* \section sec_install Building and installing
|
|
*
|
|
* Both, the bootloader and firmware are simply built with "make". You may need
|
|
* to customize both makefiles to fit to your system. If you don't want to add
|
|
* new features, you don't need to build the software yourself. You can use the
|
|
* hex-files included in this package.
|
|
*
|
|
* \subsection sec_boot Bootloader
|
|
*
|
|
* I used the USBaspLoader from Objective Development, the same guys that wrote
|
|
* the AVR-USB-driver: http://www.obdev.at/products/avrusb/usbasploader.html
|
|
*
|
|
* The reason why I chose this over some other available USB-bootloaders is,
|
|
* that this one emulates a common ISP-programmer that is supported by avrdude.
|
|
* In this way, the same program can be used to program the chip that is used
|
|
* without a bootloader.
|
|
*
|
|
* To prepare the ATmega32, you have to connect it to your computer with the
|
|
* ISP-programmer of your choice and modify the makefile according to that.
|
|
* Then you enter the bootloader-directory and enter the following line:
|
|
*
|
|
* \code
|
|
* make fuse && make flash && make lock
|
|
* \endcode
|
|
*
|
|
* With 'fuse' you prepare the fuse-bits of your AVR, 'flash' transfers the
|
|
* bootloader to the device and 'lock' prevents you from overwriting the
|
|
* bootloader. Don't fear the locking: you can always reset it with your
|
|
* ordinary programmer. In fact, it is disabled in the moment you use your
|
|
* ordinary programmer to reflash the device, even without any special
|
|
* parameters. The locking only affects the bootloader behavior.
|
|
*
|
|
* Afterwards you can put the programmer back into the toolbox, you won't need
|
|
* it from here on.
|
|
*
|
|
* When you plug in the device while holding the minus-key on the number-keypad
|
|
* pressed, the keyboard indicates that it would like to get a new firmware by
|
|
* showing a running light on the LEDs. That firmware will be flashed over the
|
|
* normal USB-cable that the keyboard is connected with.
|
|
*
|
|
* \subsection sec_fw Firmware
|
|
*
|
|
* If you intend to recompile the firmware yourself, you will need avr-gcc and
|
|
* avr-libc (a C-library for the AVR controller). Please read the instructions
|
|
* at http://www.nongnu.org/avr-libc/user-manual/install_tools.html for how to
|
|
* install the GNU toolchain (avr-gcc, assembler, linker etc.) and avr-libc.
|
|
*
|
|
* Once you have the GNU toolchain for AVR microcontrollers installed, you can
|
|
* run "make" in the subdirectory "firmware".
|
|
*
|
|
* Afterwards -- or if you decided not to compile the firmware yourself -- you
|
|
* can flash it to the device:
|
|
*
|
|
* \code
|
|
* make program
|
|
* \endcode
|
|
*
|
|
* Remember that you have to start the bootloader at first: unplug the
|
|
* keyboard, hold the minus-key on the number-keypad pressed and replug it. If
|
|
* the modified keyboard is the only one within reach: good luck! ;-)
|
|
*
|
|
* \section sec_usage Usage
|
|
*
|
|
* Connect the keyboard to the USB-port. All LED should flash up to indicate
|
|
* that the device is initialized.
|
|
*
|
|
* Then you can use the keyboard as always. If additional features get
|
|
* implemented, you will be able to use them in their respective ways.
|
|
*
|
|
* \section sec_drawbacks Drawbacks
|
|
*
|
|
* I don't know if and how keyboard manufacturers face the problem of ghost
|
|
* keys, I didn't take special measurements for this. I hope that the engineers
|
|
* at IBM distributed the keys on the matrix in a way that minimizes this
|
|
* problem. Don't misunderstand: I haven't experienced that on this keyboard,
|
|
* but I know that it's a common problem on key-matrixes.
|
|
*
|
|
* \section sec_files Files in the distribution
|
|
*
|
|
* - \e Readme.txt: Documentation, created from the htmldoc-directory.
|
|
* - \e firmware: Source code of the controller firmware.
|
|
* - \e firmware/usbdrv: USB driver -- See Readme.txt in this directory for
|
|
* info.
|
|
* - \e bootloader: The USBaspLoader, properly configured for this project. I
|
|
* only modified the bootloaderconfig.h and the Makefile.
|
|
* - \e USBaspLoader.2008-02-05.tar.gz: The unmodified bootloader sources, for
|
|
* reference.
|
|
* - \e circuit: Circuit diagrams in PDF and KiCAD format. KiCAD is a free
|
|
* schematic- and layout-tool, you can learn more about it at its homepage:
|
|
* http://www.lis.inpg.fr/realise_au_lis/kicad/
|
|
* - \e License.txt: Public license for all contents of this project, except
|
|
* for the USB driver. Look in firmware/usbdrv/License.txt for further info.
|
|
* - \e Changelog.txt: Logfile documenting changes in soft-, firm- and
|
|
* hardware.
|
|
* - \e refman.pdf: Full documentation of the software.
|
|
*
|
|
* \section sec_thanks Thanks!
|
|
*
|
|
* I'd like to thank <b>Objective Development</b> for the possibility to use
|
|
* their driver for my project. In fact, this project wouldn't exist without
|
|
* the driver.
|
|
*
|
|
* I took great inspiration from <b>Spaceman Spiff</b>'s c64key, this software
|
|
* is based on his ideas.
|
|
*
|
|
* Further credits go to <b>xleave</b>, who etched the PCB for me, and also
|
|
* answered many stupid questions about electronics I had during the last few
|
|
* years.
|
|
*
|
|
* And of course I'd like to thank <b>FaUl</b> of the Chaostreff Dortmund who
|
|
* gave me the idea for this project.
|
|
*
|
|
* \section sec_license About the license
|
|
*
|
|
* My work - all contents except for the USB driver - is licensed under the GNU
|
|
* General Public License (GPL). A copy of the GPL is included in License.txt.
|
|
* The driver itself is licensed under a special license by Objective
|
|
* Development. See firmware/usbdrv/License.txt for further info.
|
|
*
|
|
* <b>(c) 2008 by Ronald Schaten - http://www.schatenseite.de</b>
|
|
*/
|
|
|
|
#include <avr/io.h>
|
|
#include <avr/interrupt.h>
|
|
#include <avr/pgmspace.h>
|
|
#include <avr/wdt.h>
|
|
#include <util/delay.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "usbdrv.h"
|
|
#include "keycodes.h"
|
|
#include "tools.h"
|
|
#include "modelinterface.h"
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* ----------------------------- USB interface ----------------------------- */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
static uint8_t reportBuffer[8]; ///< buffer for HID reports
|
|
static uint8_t oldReportBuffer[8]; ///< buffer for the last sent HID report
|
|
static uint8_t idleRate; ///< in 4ms units
|
|
static uint8_t protocolVer = 1; ///< 0 = boot protocol, 1 = report protocol
|
|
uint8_t expectReport = 0; ///< flag to indicate if we expect an USB-report
|
|
|
|
/** USB report descriptor (length is defined in usbconfig.h). The report
|
|
* descriptor has been created with usb.org's "HID Descriptor Tool" which can
|
|
* be downloaded from http://www.usb.org/developers/hidpage/ (it's an .exe, but
|
|
* it even runs under Wine).
|
|
*/
|
|
char PROGMEM usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
|
|
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)
|
|
0x81, 0x03, // INPUT (Cnst,Var,Abs)
|
|
0x95, 0x05, // REPORT_COUNT (5)
|
|
0x75, 0x01, // REPORT_SIZE (1)
|
|
0x05, 0x08, // USAGE_PAGE (LEDs)
|
|
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
|
|
0x29, 0x05, // USAGE_MAXIMUM (Kana)
|
|
0x91, 0x02, // OUTPUT (Data,Var,Abs)
|
|
0x95, 0x01, // REPORT_COUNT (1)
|
|
0x75, 0x03, // REPORT_SIZE (3)
|
|
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
|
|
0x95, 0x06, // REPORT_COUNT (6)
|
|
0x75, 0x08, // REPORT_SIZE (8)
|
|
0x15, 0x00, // LOGICAL_MINIMUM (0)
|
|
0x25, 0x65, // LOGICAL_MAXIMUM (101)
|
|
0x05, 0x07, // USAGE_PAGE (Keyboard)
|
|
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
|
|
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
|
|
0x81, 0x00, // INPUT (Data,Ary,Abs)
|
|
0xc0 // END_COLLECTION
|
|
};
|
|
|
|
/**
|
|
* This function is called whenever we receive a setup request via USB.
|
|
* \param data[8] eight bytes of data we received
|
|
* \return number of bytes to use, or 0xff if usbFunctionWrite() should be
|
|
* called
|
|
*/
|
|
uint8_t usbFunctionSetup(uint8_t 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
|
|
return sizeof(reportBuffer);
|
|
} else if (rq->bRequest == USBRQ_HID_SET_REPORT) {
|
|
if (rq->wLength.word == 1) {
|
|
// We expect one byte reports
|
|
expectReport = 1;
|
|
return 0xff; // Call usbFunctionWrite with data
|
|
}
|
|
} 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 if (rq->bRequest == USBRQ_HID_GET_PROTOCOL) {
|
|
if (rq->wValue.bytes[1] < 1) {
|
|
protocolVer = rq->wValue.bytes[1];
|
|
}
|
|
} else if(rq->bRequest == USBRQ_HID_SET_PROTOCOL) {
|
|
usbMsgPtr = &protocolVer;
|
|
return 1;
|
|
}
|
|
} else {
|
|
// no vendor specific requests implemented
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* The write function is called when LEDs should be set. Normally, we get only
|
|
* one byte that contains info about the LED states.
|
|
* \param data pointer to received data
|
|
* \param len number ob bytes received
|
|
* \return 0x01
|
|
*/
|
|
uint8_t usbFunctionWrite(uchar *data, uchar len) {
|
|
if (expectReport && (len == 1)) {
|
|
setLeds(data[0]);
|
|
}
|
|
expectReport = 0;
|
|
return 0x01;
|
|
}
|
|
|
|
/**
|
|
* Send a single report to the computer. This function is not used during
|
|
* normal typing, it is only used to send non-pressed keys to simulate input.
|
|
* \param mode modifier-byte
|
|
* \param key key-code
|
|
*/
|
|
void usbSendReport(uint8_t mode, uint8_t key) {
|
|
// buffer for HID reports. we use a private one, so nobody gets disturbed
|
|
uint8_t repBuffer[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
repBuffer[0] = mode;
|
|
repBuffer[2] = key;
|
|
while (!usbInterruptIsReady()); // wait
|
|
usbSetInterrupt(repBuffer, sizeof(repBuffer)); // send
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* This structure can be used as a container for a single 'key'. It consists of
|
|
* the key-code and the modifier-code.
|
|
*/
|
|
typedef struct {
|
|
uint8_t mode;
|
|
uint8_t key;
|
|
} Key;
|
|
|
|
/**
|
|
* Convert an ASCII-character to the corresponding key-code and modifier-code
|
|
* combination.
|
|
* \param character ASCII-character to convert
|
|
* \return structure containing the combination
|
|
*/
|
|
Key charToKey(char character) {
|
|
Key key;
|
|
// initialize with reserved values
|
|
key.mode = MOD_NONE;
|
|
key.key = KEY_Reserved;
|
|
if ((character >= 'a') && (character <= 'z')) {
|
|
// a..z
|
|
key.key = (character - 'a') + 0x04;
|
|
} else if ((character >= 'A') && (character <= 'Z')) {
|
|
// A..Z
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = (character - 'A') + 0x04;
|
|
} else if ((character >= '1') && (character <= '9')) {
|
|
// 1..9
|
|
key.key = (character - '1') + 0x1E;
|
|
}
|
|
// we can't map the other characters directly, so we switch...
|
|
switch (character) {
|
|
case '0':
|
|
key.key = KEY_0; break;
|
|
case '!':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_1; break;
|
|
/*
|
|
case '@':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_2; break;
|
|
case '#':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_3; break;
|
|
*/
|
|
case '$':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_4; break;
|
|
case '%':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_5; break;
|
|
case '^':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_6; break;
|
|
case '&':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_7; break;
|
|
case '*':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_8; break;
|
|
case '(':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_9; break;
|
|
case ')':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_0; break;
|
|
case ' ':
|
|
key.key = KEY_Spacebar; break;
|
|
case '-':
|
|
key.key = KEY_minus; break;
|
|
case '_':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_minus; break;
|
|
case '=':
|
|
key.key = KEY_equals; break;
|
|
case '+':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_equals; break;
|
|
case '[':
|
|
key.key = KEY_lbracket; break;
|
|
case '{':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_lbracket; break;
|
|
case ']':
|
|
key.key = KEY_rbracket; break;
|
|
case '}':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_rbracket; break;
|
|
case '\\':
|
|
key.key = KEY_backslash; break;
|
|
case '|':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_backslash; break;
|
|
case '#':
|
|
key.key = KEY_hash; break;
|
|
case '@':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_hash; break;
|
|
case ';':
|
|
key.key = KEY_semicolon; break;
|
|
case ':':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_semicolon; break;
|
|
case '\'':
|
|
key.key = KEY_apostroph; break;
|
|
case '"':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_apostroph; break;
|
|
case '`':
|
|
key.key = KEY_grave; break;
|
|
case '~':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_grave; break;
|
|
case ',':
|
|
key.key = KEY_comma; break;
|
|
case '<':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_comma; break;
|
|
case '.':
|
|
key.key = KEY_dot; break;
|
|
case '>':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_dot; break;
|
|
case '/':
|
|
key.key = KEY_slash; break;
|
|
case '?':
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_slash; break;
|
|
}
|
|
if (key.key == KEY_Reserved) {
|
|
// still reserved? WTF? return question mark...
|
|
key.mode = MOD_SHIFT_LEFT;
|
|
key.key = KEY_slash;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* Send a key to the computer, followed by the release of all keys. This can be
|
|
* used repetitively to send a string.
|
|
* \param keytosend key structure to send
|
|
*/
|
|
void sendKey(Key keytosend) {
|
|
usbSendReport(keytosend.mode, keytosend.key);
|
|
usbSendReport(0, 0);
|
|
}
|
|
|
|
/**
|
|
* Send a string to the computer. This function converts each character of an
|
|
* ASCII-string to a key-structure and uses sendKey() to send it.
|
|
* \param string string to send
|
|
*/
|
|
void sendString(char* string) {
|
|
for (uint8_t i = 0; i < strlen(string); i++) {
|
|
Key key = charToKey(string[i]);
|
|
sendKey(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main function, containing the main loop that manages timer- and
|
|
* USB-functionality.
|
|
* /return the obligatory integer that nobody cares about...
|
|
*/
|
|
int main(void) {
|
|
uint8_t updateNeeded = 0;
|
|
uint8_t idleCounter = 0;
|
|
wdt_enable(WDTO_2S);
|
|
hardwareInit();
|
|
usbInit();
|
|
sei();
|
|
|
|
memset(oldReportBuffer, 0, sizeof(oldReportBuffer)); // clear old report buffer
|
|
|
|
scankeys(reportBuffer, oldReportBuffer, sizeof(reportBuffer));
|
|
while (1) {
|
|
// main event loop
|
|
wdt_reset();
|
|
usbPoll();
|
|
|
|
updateNeeded = scankeys(reportBuffer, oldReportBuffer, sizeof(reportBuffer)); // changes?
|
|
|
|
// check timer if we need periodic reports
|
|
if (TIFR & (1 << TOV0)) {
|
|
TIFR = (1 << TOV0); // reset flag
|
|
if (idleRate != 0) { // do we need periodic reports?
|
|
if(idleCounter > 4){ // yes, but not yet
|
|
idleCounter -= 5; // 22ms in units of 4ms
|
|
} else { // yes, it is time now
|
|
updateNeeded = 1;
|
|
idleCounter = idleRate;
|
|
}
|
|
}
|
|
}
|
|
// if an update is needed, send the report
|
|
if (updateNeeded && usbInterruptIsReady()) {
|
|
updateNeeded = 0;
|
|
usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
|
|
memcpy(oldReportBuffer, reportBuffer, sizeof(oldReportBuffer));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|