diff --git a/firmware/Makefile b/firmware/Makefile index a1bfa76..3c46aa4 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -10,9 +10,10 @@ AVRDUDE = avrdude -p atmega32 -c usbasp # Options: -HWOBJECTS = modelibmmodelm.o +#HWOBJECTS = modelibmmodelm.o #HWOBJECTS = modelmayhem.o #HWOBJECTS = modelsuntype5.o +HWOBJECTS = modelibmhost.o COMPILE = avr-gcc -Wall -Os -Iusbdrv -I. -mmcu=atmega32 -DF_CPU=12000000L $(DEFINES) diff --git a/firmware/modelibmhost.c b/firmware/modelibmhost.c new file mode 100644 index 0000000..93ace23 --- /dev/null +++ b/firmware/modelibmhost.c @@ -0,0 +1,313 @@ +/** + * \file firmware/modelibmmodelm.c + * \brief Hardware specific part for IBM Host keyboard + * \author Ronald Schaten + * \version $Id: modelibmmodelm.c 173 2009-02-14 21:11:43Z rschaten $ + * + * License: GNU GPL v2 (see License.txt) + */ + +#include +#include +#include +#include +#include + +#include "keycodes.h" +#include "tools.h" +#include "modelinterface.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 PORTROWS3 PORTD ///< third port connected to the matrix rows +#define PINROWS3 PIND ///< third port connected to the matrix rows +#define DDRROWS3 DDRD ///< third port connected to the matrix rows + +uint8_t curmatrix[20]; ///< contains current state of the keyboard +uint8_t oldmatrix[20]; ///< contains old state of the keyboard +uint8_t ghostmatrix[20]; ///< contains pressed keys that belong to a ghost-key situation + +void hardwareInit(void) { + // column-port is input + PORTCOLUMNS = 0xff; + DDRCOLUMNS = 0x00; + + // row-ports are output + PORTROWS1 = 0xff; + DDRROWS1 = 0x00; + PORTROWS2 = 0xff; + DDRROWS2 = 0x00; + PORTROWS3 |= ((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + DDRROWS3 &= ~((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + + // port D contains USB (D0, D2), + // and keyboard rows (D4, D5, D6, D7). + // so we call it PORTD instead of PORTJUMPERS or PORTLEDS + PORTD &= ~((1 << PIND0) | (1 << PIND2)); // deactivate pull-ups on USB-lines + DDRD |= ((1 << PIND0) | (1 << PIND2)); // set reset USB condition. + // USB reset by device only required on watchdog reset + _delay_us(11); // delay >10us for USB reset + DDRD &= ~((1 << PIND0) | (1 << PIND2)); // remove USB reset condition + + // configure timer 0 for a rate of 12M/(1024 * 256) = 45.78Hz (~22ms) + TCCR0 = 5; // timer 0 prescaler: 1024 +} + +/** + * 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 <= 19; 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) || (i == 15)) { + sendString(":"); + } else { + sendString("."); + } + } + sendString("---"); +} + +void toggle(void) { + // not used in this model/version +} + +void setSpeed(uint8_t speed) { + // not used in this model/version +} + +void setLeds(uint8_t LEDstate) { + // do nothing, since we don't have fancy lights on this hardware +} + +/** + * 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[20][8] = { + // 0 / 0x01 1 / 0x02 2 / 0x04 3 / 0x08 4 / 0x10 5 / 0x20 6 / 0x40 7 / 0x80 + {KEY_KPenter, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_RightArrow, KEY_Application }, // 0 + {KEY_KPcomma, KEY_KP3, KEY_Reserved, KEY_KP9, KEY_Reserved, KEY_KPslash, KEY_KP6, KEY_Reserved }, // 1 + {KEY_KP0, KEY_KP2, KEY_Reserved, KEY_KP8, KEY_Home, KEY_KPequals, KEY_KP5, KEY_Reserved }, // 2 + {KEY_Reserved, KEY_Reserved, KEY_End, KEY_PageDown, KEY_Insert, KEY_PageUp, KEY_DeleteForward, KEY_UpArrow }, // 3 + {KEY_Reserved, KEY_Return, KEY_Reserved, KEY_KP7, KEY_DELETE, KEY_KPBackspace, KEY_KP4, KEY_DownArrow }, // 4 + {KEY_slash, KEY_hash, KEY_lbracket, KEY_P, KEY_minus, KEY_0, KEY_semicolon, KEY_apostroph }, // 5 + {KEY_Reserved, KEY_dot, KEY_Reserved, KEY_O, KEY_Reserved, KEY_9, KEY_L, KEY_Reserved }, // 6 + {KEY_Reserved, KEY_comma, KEY_rbracket, KEY_I, KEY_equals, KEY_8, KEY_K, KEY_Reserved }, // 7 + {KEY_F12, KEY_F11, KEY_F9, KEY_F8, KEY_F6, KEY_F5, KEY_F3, KEY_F2 }, // 8 + {KEY_F24, KEY_F10, KEY_F21, KEY_F7, KEY_F18, KEY_F4, KEY_F15, KEY_F1 }, // 9 + {KEY_F23, KEY_F22, KEY_F20, KEY_F19, KEY_F17, KEY_F16, KEY_F14, KEY_F13 }, // 10 + {KEY_N, KEY_M, KEY_Z, KEY_U, KEY_6, KEY_7, KEY_J, KEY_H }, // 11 + {KEY_B, KEY_V, KEY_T, KEY_R, KEY_5, KEY_4, KEY_F, KEY_G }, // 12 + {KEY_Reserved, KEY_C, KEY_Reserved, KEY_E, KEY_Reserved, KEY_3, KEY_D, KEY_Reserved }, // 13 + {KEY_Reserved, KEY_X, KEY_Reserved, KEY_W, KEY_Reserved, KEY_2, KEY_S, KEY_Reserved }, // 14 + {KEY_Euro, KEY_Y, KEY_Reserved, KEY_Q, KEY_grave, KEY_1, KEY_A, KEY_Reserved }, // 15 + {KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved, KEY_Reserved }, // 16 + {KEY_Reserved, KEY_capslock, KEY_Copy, KEY_Tab, KEY_Again, KEY_Reserved, KEY_Paste, KEY_Find }, // 17 + {KEY_Spacebar, KEY_KP1, KEY_Execute, KEY_Undo, KEY_Stop, KEY_Menu, KEY_Select, KEY_Cut }, // 18 + {KEY_Reserved, KEY_LeftArrow, KEY_Reserved, KEY_KPminus, KEY_Reserved, KEY_KPasterisk, KEY_KPplus, KEY_Reserved }, // 19 +}; + +/** + * 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[20][8] = { // contains positions of modifiers in the matrix + // 0 1 2 3 4 5 6 7 + {MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 0 + {MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, 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_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 15 + {MOD_SHIFT_LEFT, MOD_SHIFT_RIGHT, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_ALT_RIGHT }, // 16 + {MOD_CONTROL_RIGHT, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 17 + {MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE }, // 18 + {MOD_ALT_LEFT, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_NONE, MOD_CONTROL_LEFT }, // 19 +}; + +/** + * Checks if more than one bit in data is set. + * \param data value to check + * \return true if more than one bit is set + */ +static uint8_t bitcount2(uint16_t data) { + data &= (data - 1); + return data != 0; +} + +/** + * check if reportBuffer contains the key + * \param buffer buffer to check + * \param key key to search + * \return 1 if buffer contains key, 0 otherwise + */ +static uint8_t bufferContains(uint8_t* buffer, uint8_t key) { + for (uint8_t i = 2; i < sizeof(buffer); i++) { + if (buffer[i] == key) { + return 1; + } + } + return 0; +} + +/** + * 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(uint8_t* reportBuffer, uint8_t* oldReportBuffer, uint8_t sizeOfReportBuffer) { + static uint8_t debounce = 5; + uint8_t retval = 0; + for (uint8_t row = 0; row <= 19; row++) { + if (row <= 7) { + DDRROWS1 = (1 << row); + PORTROWS1 = ~(1 << row); + DDRROWS2 = 0x00; + PORTROWS2 = 0xff; + PORTROWS3 |= ((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + DDRROWS3 &= ~((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + } else if (row <= 15) { + 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)); + PORTROWS3 |= ((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + DDRROWS3 &= ~((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + } else { + DDRROWS1 = 0x00; + PORTROWS1 = 0xff; + DDRROWS2 = 0x00; + PORTROWS2 = 0xff; + // As if the case above wasn't difficult enough, on PORTD we have + // to make sure that the scanning doesn't affect USB + // communications, which occur on PIND0 and PIND2. + PORTROWS3 |= ((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + DDRROWS3 &= ~((1 << PIND4) | (1 << PIND5) | (1 << PIND6) | (1 << PIND7)); + DDRROWS3 |= (1 << (19 - row + 4)); + PORTROWS3 &= ~(1 << (19 - row + 4)); + } + _delay_us(30); + uint8_t data = ~PINCOLUMNS; + // check if we have to prevent ghost-keys + uint16_t rows = (PINROWS1 << 8) | PINROWS2; // TODO + if (bitcount2(~rows) && bitcount2(data)) { + // ghost-key situation detected + ghostmatrix[row] = data; + } else { + ghostmatrix[row] = 0x00; + } + 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) { + /* + if (memcmp(oldmatrix, curmatrix, sizeof(curmatrix)) != 0) { + printMatrix(); + memcpy(oldmatrix, curmatrix, sizeof(curmatrix)); + return 0; + } + */ + // debounce counter expired, create report + uint8_t reportIndex = 2; // reportBuffer[0] contains modifiers + memset(reportBuffer, 0, sizeOfReportBuffer); // clear report buffer + for (uint8_t row = 0; row <= 19; 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, isghostkey; + if (data & (1 << col)) { + key = pgm_read_byte(&keymatrix[row][col]); + modifier = pgm_read_byte(&modmatrix[row][col]); + isghostkey = (ghostmatrix[row] & (1 << col)) != 0; + } else { + key = KEY_Reserved; + modifier = MOD_NONE; + isghostkey = 0x00; + } + if (key != KEY_Reserved) { // keycode should be added to report + if (reportIndex >= sizeOfReportBuffer) { // too many keycodes + if (!retval & 0x02) { // Only fill buffer once + memset(reportBuffer+2, KEY_ErrorRollOver, sizeOfReportBuffer-2); + retval |= 0x02; // continue decoding to get modifiers + } + } else { + if (isghostkey) { + // we're in a ghost-key situation + if (bufferContains(oldReportBuffer, key)) { + // this key has been pressed before, so we still send it + reportBuffer[reportIndex] = key; // set next available entry + reportIndex++; + } + } 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; +} +