Dulcimer/firmware/main.c

793 lines
34 KiB
C
Raw Permalink Normal View History

2008-07-09 20:47:12 +00:00
/**
* \file firmware/main.c
* \brief Main functions for USB-keyboard
* \author Ronald Schaten <ronald@schatenseite.de>
* \version $Id: main.c,v 1.5 2008/07/12 21:05:24 rschaten Exp $
2008-07-09 20:47:12 +00:00
*
* 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
2008-07-10 20:29:01 +00:00
* come up with own useful -- or even better: useless -- ideas. ;-)
2008-07-09 20:47:12 +00:00
*
* \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.
2008-07-09 20:47:12 +00:00
*
* \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.
*
2008-07-10 20:42:27 +00:00
* And of course I'd like to thank that friend of mine -- I doubt that he'd
* like to read his name in this place, I'll put it in if he wants me to --
* that gave me the idea for this project.
*
2008-07-09 20:47:12 +00:00
* \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>
*/
#define F_CPU 12000000L ///< we use a 12MHz crystal
#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"
/* ----------------------- hardware I/O abstraction ------------------------ */
#define PORTCOLUMNS PORTB ///< port on which we read the state of the columns
#define PINCOLUMNS PINB ///< port on which we read the state of the columns
#define DDRCOLUMNS DDRB ///< port on which we read the state of the columns
#define PORTROWS1 PORTA ///< first port connected to the matrix rows
#define PINROWS1 PINA ///< first port connected to the matrix rows
#define DDRROWS1 DDRA ///< first port connected to the matrix rows
#define PORTROWS2 PORTC ///< second port connected to the matrix rows
#define PINROWS2 PINC ///< second port connected to the matrix rows
#define DDRROWS2 DDRC ///< second port connected to the matrix rows
#define PORTLEDS PORTD ///< port on which the LEDs are connected
#define PINLEDS PIND ///< port on which the LEDs are connected
#define DDRLEDS DDRD ///< port on which the LEDs are connected
#define LEDSCROLL PIND4 ///< address of the scroll-lock LED
#define LEDCAPS PIND5 ///< address of the caps-lock LED
#define LEDNUM PIND6 ///< address of the num-lock LED
#define PORTJUMPERS PORTD ///< port for additional jumpers
#define PINJUMPERS PIND ///< port for additional jumpers
#define DDRJUMPERS DDRD ///< port for additional jumpers
#define JUMPER0 PD1 ///< address for jumper 0
#define JUMPER1 PD3 ///< address for jumper 1
#define JUMPER2 PD7 ///< address for jumper 2
/**
* Initialize hardware. Configure ports as inputs and outputs, set USB reset
* condition, start timer and blink LEDs.
*/
static void hardwareInit(void) {
// column-port is input
PORTCOLUMNS = 0xff;
DDRCOLUMNS = 0x00;
// row-ports are output
PORTROWS1 = 0xff;
DDRROWS1 = 0x00;
PORTROWS2 = 0xff;
DDRROWS2 = 0x00;
// port D contains USB (D0, D2),
// LEDs (D4, D5, D6)
// and Jumpers (D1, D3, D7),
// so we call it PORTD instead of PORTJUMPERS or PORTLEDS
PORTD = 0xfa; // 1000 1010: activate pull-ups except on USB- and LED-lines
DDRD = 0x75; // 0111 0101: all pins input except USB (-> USB reset) and LED-pins
// USB Reset by device only required on Watchdog Reset
_delay_us(11); // delay >10ms for USB reset
DDRD = 0x70; // 0111 0000 bin: remove USB reset condition
// configure timer 0 for a rate of 12M/(1024 * 256) = 45.78Hz (~22ms)
TCCR0 = 5; // timer 0 prescaler: 1024
// blink, to indicate power-on
PORTLEDS &= ~((1 << LEDNUM) | (1 << LEDCAPS) | (1 << LEDSCROLL));
_delay_ms(50);
PORTLEDS |= ((1 << LEDNUM) | (1 << LEDCAPS) | (1 << LEDSCROLL));
}
/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */
static uint8_t reportBuffer[8]; ///< buffer for HID reports
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
#define LED_NUM 0x01 ///< num LED on a boot-protocol keyboard
#define LED_CAPS 0x02 ///< caps LED on a boot-protocol keyboard
#define LED_SCROLL 0x04 ///< scroll LED on a boot-protocol keyboard
#define LED_COMPOSE 0x08 ///< compose LED on a boot-protocol keyboard
#define LED_KANA 0x10 ///< kana LED on a boot-protocol keyboard
uint8_t LEDstate = 0; ///< current state of the LEDs
/** 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)) {
LEDstate = data[0]; // Get the state of all 5 LEDs
if (LEDstate & LED_NUM) { // light up caps lock
PORTLEDS &= ~(1 << LEDNUM);
} else {
PORTLEDS |= (1 << LEDNUM);
}
if (LEDstate & LED_CAPS) { // light up caps lock
PORTLEDS &= ~(1 << LEDCAPS);
} else {
PORTLEDS |= (1 << LEDCAPS);
}
if (LEDstate & LED_SCROLL) { // light up caps lock
PORTLEDS &= ~(1 << LEDSCROLL);
} else {
PORTLEDS |= (1 << LEDSCROLL);
}
}
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;
2008-07-09 20:47:12 +00:00
while (!usbInterruptIsReady()); // wait
usbSetInterrupt(repBuffer, sizeof(repBuffer)); // send
2008-07-09 20:47:12 +00:00
}
/* ------------------------------------------------------------------------- */
uint8_t curmatrix[16]; ///< contains current state of the keyboard
/**
* The keymatrix-array contains positions of keys in the matrix. Here you can
* see which row is connected to which column when a key is pressed. This array
* probably has to be modified if this firmware is ported to a different
* keyboard.
* \sa modmatrix
*/
const uint8_t PROGMEM keymatrix[16][8] = {
// 0 1 2 3 4 5 6 7
{KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved }, // 0
{KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved }, // 1
{KEY_ESCAPE, KEY_Tab, KEY_grave, KEY_1, KEY_Q, KEY_A, KEY_Z, KEY_Reserved }, // 2
{KEY_Euro, KEY_capslock, KEY_F1, KEY_2, KEY_W, KEY_S, KEY_X, KEY_Reserved }, // 3
2008-07-09 20:47:12 +00:00
{KEY_F4, KEY_F3, KEY_F2, KEY_3, KEY_E, KEY_D, KEY_C, KEY_Reserved }, // 4
{KEY_G, KEY_T, KEY_5, KEY_4, KEY_R, KEY_F, KEY_V, KEY_B }, // 5
{KEY_F5, KEY_DELETE, KEY_F9, KEY_F10, KEY_Reserved, KEY_Reserved, KEY_Return, KEY_Spacebar }, // 6
{KEY_H, KEY_Y, KEY_6, KEY_7, KEY_U, KEY_J, KEY_M, KEY_N }, // 7
{KEY_F6, KEY_rbracket, KEY_equals, KEY_8, KEY_I, KEY_K, KEY_comma, KEY_Reserved }, // 8
{KEY_Reserved, KEY_F7, KEY_F8, KEY_9, KEY_O, KEY_L, KEY_dot, KEY_Reserved }, // 9
{KEY_apostroph, KEY_lbracket, KEY_minus, KEY_0, KEY_P, KEY_semicolon, KEY_hash, KEY_slash }, // 10
{KEY_Reserved, KEY_KP4, KEY_DeleteForward, KEY_F11, KEY_KP7, KEY_KP1, KEY_NumLock, KEY_DownArrow }, // 11
{KEY_KP0, KEY_KP5, KEY_Insert, KEY_F12, KEY_KP8, KEY_KP2, KEY_KPslash, KEY_RightArrow }, // 12
{KEY_KPcomma, KEY_KP6, KEY_PageUp, KEY_PageDown, KEY_KP9, KEY_KP3, KEY_KPasterisk, KEY_KPminus }, // 13
{KEY_UpArrow, KEY_Reserved, KEY_Home, KEY_End, KEY_KPplus, KEY_KPenter, KEY_Pause, KEY_LeftArrow }, // 14
{KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_PrintScreen, KEY_ScrollLock, KEY_Reserved, KEY_Reserved, KEY_Reserved }, // 15
};
/**
* The modmatrix-array contains positions of the modifier-keys in the matrix.
* It is built in the same way as the keymatrix-array.
* \sa keymatrix
*/
const uint8_t PROGMEM modmatrix[16][8] = { // contains positions of modifiers in the matrix
// 0 1 2 3 4 5 6 7
{MOD_NONE, MOD_NONE, MOD_CONTROL_LEFT, MOD_NONE, MOD_NONE, MOD_NONE, MOD_CONTROL_RIGHT, MOD_NONE }, // 0
{MOD_NONE, MOD_SHIFT_LEFT, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_SHIFT_RIGHT, MOD_NONE }, // 1
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 2
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 3
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 4
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 5
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 6
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 7
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 8
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 9
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 10
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 11
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 12
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 13
{MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 14
{MOD_ALT_LEFT, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_ALT_RIGHT}, // 15
};
/**
* 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.
* \parm 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);
}
}
/**
* Print the current state of the keyboard in a readable form. This function
* is used for debug-purposes only.
*/
void printMatrix(void) {
for (uint8_t i = 0; i <= 15; i++) {
char buffer[10];
/*
sprintf(buffer, "%d%d%d%d%d%d%d%d.",
(curmatrix[i] & (1 << 0) ? 1 : 0),
(curmatrix[i] & (1 << 1) ? 1 : 0),
(curmatrix[i] & (1 << 2) ? 1 : 0),
(curmatrix[i] & (1 << 3) ? 1 : 0),
(curmatrix[i] & (1 << 4) ? 1 : 0),
(curmatrix[i] & (1 << 5) ? 1 : 0),
(curmatrix[i] & (1 << 6) ? 1 : 0),
(curmatrix[i] & (1 << 7) ? 1 : 0));
*/
sprintf(buffer, "%2x", curmatrix[i]);
sendString(buffer);
if (i == 7) {
sendString(":");
} else {
sendString(".");
}
}
sendString("---");
}
/**
* Scan and debounce keypresses. This is the main worker function for normal
* keyboard operation, the code contains lot of comments. Basically, it first
* scans the keyboard state. If a change is detected, it initializes a counter
* that is decreased each time this function is called. If the counter reaches
* 1, that means that the same scan result has been scanned ten times in a row,
* so we can be pretty sure that the keys are in a certain state (as in: not
* bouncing). Then, the codes for keys and modifiers are searched from the two
* arrays, the USB-message to send the state is prepared. The return value of
* this function indicates if the message has to be sent.
* \return flag to indicate whether something has changed
*/
uint8_t scankeys(void) {
static uint8_t debounce = 5;
uint8_t retval = 0;
for (uint8_t row = 0; row <= 15; row++) {
if (row <= 7) {
DDRROWS1 = (1 << row);
PORTROWS1 = ~(1 << row);
DDRROWS2 = 0x00;
PORTROWS2 = 0xff;
} else {
DDRROWS1 = 0x00;
PORTROWS1 = 0xff;
// (15 - row) looks a bit weird, you would expect (row - 8) here.
// This is because pins on PORTC are ordered in the other direction
// than on PORTA. With (15 - row), we have the bytes in the
// resulting matrix matching the pins of the keyboard connector.
DDRROWS2 = (1 << (15 - row));
PORTROWS2 = ~(1 << (15 - row));
}
_delay_us(30);
uint8_t data = ~PINCOLUMNS;
if (data != curmatrix[row]) {
// if a change was detected
debounce = 10; // activate debounce counter
curmatrix[row] = data; // and store the result
}
}
if (debounce) {
// Count down, but avoid underflow
debounce--;
}
if (debounce == 1) {
// debounce counter expired, create report
uint8_t reportIndex = 2; // reportBuffer[0] contains modifiers
memset(reportBuffer, 0, sizeof(reportBuffer)); // clear report buffer
for (uint8_t row = 0; row <= 15; row++) { // process all rows for key-codes
uint8_t data = curmatrix[row]; // restore buffer
if (data != 0xff) { // anything on this row? - optimization
for (uint8_t col = 0; col <= 7; col++) { // check every bit on this row
uint8_t key, modifier;
if (data & (1 << col)) {
key = pgm_read_byte(&keymatrix[row][col]);
modifier = pgm_read_byte(&modmatrix[row][col]);
} else {
key = KEY_Reserved;
modifier = MOD_NONE;
}
if (key != KEY_Reserved) { // keycode should be added to report
if (reportIndex >= sizeof(reportBuffer)) { // too many keycodes
if (!retval & 0x02) { // Only fill buffer once
memset(reportBuffer+2, KEY_ErrorRollOver, sizeof(reportBuffer)-2);
retval |= 0x02; // continue decoding to get modifiers
}
} else {
reportBuffer[reportIndex] = key; // set next available entry
reportIndex++;
}
}
if (modifier != MOD_NONE) { // modifier should be added to report
reportBuffer[0] |= modifier;
}
}
}
}
retval |= 0x01; // must have been a change at some point, since debounce is done
}
return retval;
}
/* ------------------------------------------------------------------------- */
/**
* 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();
scankeys();
while (1) {
// main event loop
wdt_reset();
usbPoll();
updateNeeded = scankeys(); // 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));
}
}
return 0;
}
/* ------------------------------------------------------------------------- */