prgKbd-keyboard/firmware/keyboard.c

414 lines
15 KiB
C

#include "keyboard.h"
#include <avr/io.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include "usbdrv.h"
#include "lib/prgKeyboard.h"
static uchar reportBuffer[8] = {0,0,0,0,0,0,0,0}; /* buffer for HID reports */
static uchar idleRate; /* in 4 ms units */
static uchar newReport = 1; /* current report */
uint16_t memAddr = 0;
uint8_t cMode = 0; // 0->Keyup 1->Keydown
uint8_t oldKey=0;
uint8_t hasReleased=1;
uint8_t flag_keyPress=0;
uint8_t keyAction=0;
uint8_t keyType[16]; // Will contain whether the key has to be handled as a ...
#define KEYTYPE_TEXT 0 // Text-Button (-> only button presses) or whether we need to pay attention to
#define KEYTYPE_KEY 1 // push and release events
// MEMORY ARCHITECTURE (INTERNAL)
#define MEM_SERIAL_OFFSET 0
#define MEM_SERIAL_LENGTH 16
// MEM. ARCH. (EXTERNAL)
#define MEM_KEY_LENGTH 512 // 512B * 16 -> 8KByte (64Kbit)
// COMMANDS:
#define CMD_PING 0
#define CMD_SWVERSION 1
#define CMD_EEWRITE 2
#define CMD_EEREAD 3
#define CMD_EEOPEN 4 // Can be used if errors occour while writing to the device.
#define CMD_RESET 5 // WDT RESET
#define CMD_SERIAL 6 // DEV-Serial PRGKBD0010911001 --> This data sits in the INETRNAL eeprom
// 6B PRGKBD
// 3B HW_REV
// 4B HW_Date (MMYY)
// 3B ID
#define CMD_IEEWRITE 7 //internal eeprom
#define CMD_IEEREAD 8 // int. eep.
#define CMD_EXEC 9 // execute from addr.
#define CMD_INITKEYS 10 // Auto-detect keytypes
/*Keys[] 0-133 codes.pdf Page 53 (10. Table 12)
ModKeys 224-231 codes.pdf Page 59 (10. Table 12)
- - - - - - - - - - - - - - - - - - - - - - - - -*/
#define INSTR_KEYDOWN 254
#define INSTR_KEYUP 253
#define INSTR_KEYPRESS 252
#define INSTR_BREAK 255
#define KEYMODE_UP 1
#define KEYMODE_DOWN 0
#define KEYMODE_PRESS 2 //push and release
#define KEYACTION_DOWN 0
#define KEYACTION_UP 1
// Very slow programming?
// -> Disable verifying the entered data in CMD_EEWRITE
/* Reportbuffer format:
0 Modifier byte
1 reserved
2 keycode array (0)
3 keycode array (1)
4 keycode array (2)
5 keycode array (3)
6 keycode array (4)
7 keycode array (5)
<< This is the standard usb-keyboard reportbuffer. It allows for 6 simultaneous keypresses to be detected (excl. modifier keys). In this application we only use 1, so the last 5 bytes in this buffer will always remain 0. >>
<< I decided not to optimize this in order to make it easy to add extra keys that can be pressed simultaneously>>
Modifier byte: 8 bits, each individual bit represents one of the modifier keys.
bit0 LEFT CTRL (1<<0)
bit1 LEFT SHIFT (1<<1)
bit2 LEFT ALT (1<<2)
bit3 LEFT GUI (1<<3)
bit4 RIGHT CTRL (1<<4)
bit5 RIGHT SHIFT (1<<5)
bit6 RIGHT ALT (1<<6)
bit7 RIGHT GUI (1<<7)
an example of a reportBuffer for a CTRL+ALT+Delete keypress:
{((1<<0)+(1<<2)),0,76,0,0,0,0,0}
the first byte holds both the LEFT CTRL and LEFT modifier keys the 3rd byte holds the delete key (== decimal 76)
*/
/* ------------------------------------------------------------------------- */
// MOD-Key defines
#define LEFT_CTRL (1<<0)
#define LEFT_SHIFT (1<<1)
#define LEFT_ALT (1<<2)
#define LEFT_GUI (1<<3)
#define RIGHT_CTRL (1<<4)
#define RIGHT_SHIFT (1<<5)
#define RIGHT_ALT (1<<6)
#define RIGHT_GUI (1<<7)
const PROGMEM char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = { //
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard) 6
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) ** Modifier Byte **
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs) ** Reserved Byte **
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) ** LED Report **
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs) ** LED Report Padding **
0x95, 0x06, // REPORT_COUNT (6) ** here we define the maximum number of simultaneous keystrokes we can detect **
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x84, // LOGICAL_MAXIMUM (101)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x84, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs) ** Key arrays (6 bytes) **
0xc0 // END_COLLECTION
};
/* -------------------------------------------------------------------------------- */
/* ------------------------ interface to USB driver ------------------------ */
/* -------------------------------------------------------------------------------- */
uchar usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (void *)data;
static uchar reportBuffer[24];
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();
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{
// Here we're gonna add the functions to program the keyboard
if(rq->bRequest == CMD_PING) {
reportBuffer[0] = rq->wValue.bytes[0];
return 1;
}
else if(rq->bRequest == CMD_SWVERSION) {
reportBuffer[0] = '0';
reportBuffer[1] = '3';
reportBuffer[2] = 'B';
return 3;
}
else if(rq->bRequest == CMD_EEWRITE) {
EEWriteByte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8), rq->wIndex.bytes[0]);
//reportBuffer[0] = EEReadByte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8)); // WE MIGHT HAVE 2 REMOVE THIS BECAUSE OF PERFORMANCE ISSUES -- Reading back too quickly causes an error (-1)
reportBuffer[0] = rq->wIndex.bytes[0];
return 1;
}
else if(rq->bRequest == CMD_EEREAD) {
reportBuffer[0] = EEReadByte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8));
reportBuffer[1] = EEReadByte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8)); // We read twice to ensure the right value was loaded and transmitted.
return 2;
}
else if(rq->bRequest == CMD_EEOPEN) {
EEOpen();
reportBuffer[0] = 1;
return 1;
}
else if(rq->bRequest == CMD_RESET) {
wdt_enable(WDTO_15MS); // faster reboot
while (1); // Wait 'til the watchdog resets our system
}
else if(rq->bRequest == CMD_SERIAL) { // CURRENTLY NOT WORKING!
uint8_t x;
for (x=0; x < MEM_SERIAL_LENGTH; x++) {
reportBuffer[x] = eeprom_read_byte(x+MEM_SERIAL_OFFSET);
}
return MEM_SERIAL_LENGTH;
}
else if(rq->bRequest == CMD_IEEWRITE) {
LED_PORT ^= (1 << LED_GREEN);
eeprom_write_byte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8), rq->wIndex.bytes[0]);
reportBuffer[0] = eeprom_read_byte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8)); // (WE MIGHT HAVE 2 REMOVE THIS BECAUSE OF PERFORMANCE ISSUES)
return 1;
}
else if(rq->bRequest == CMD_IEEREAD) {
reportBuffer[0] = eeprom_read_byte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8));
reportBuffer[1] = eeprom_read_byte(rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8)); // Check
return 2;
}
else if(rq->bRequest == CMD_EXEC) {
memAddr = rq->wValue.bytes[0] + (rq->wValue.bytes[1] << 8); // jump to addr.
cMode = KEYMODE_PRESS; //default to keymode_press
oldKey = 0;
newReport = 0; //activate continued exec
continueExecution(); // exec NOW
}
else if (rq->bRequest == CMD_INITKEYS) {
detectKeyType();
reportBuffer[0] = 1;
return 1;
}
}
return 0;
}
static void buildReport(void){
if(newReport == 0) continueExecution();
}
void continueExecution() {
uint8_t newData = 0;
while (!newData) {
uint8_t instr = EEReadByte(memAddr);
if (instr == INSTR_BREAK) {
// Release all keys (?)
if (keyType[oldKey-1] == KEYTYPE_TEXT) {
uint8_t x;
for (x=0; x < 8; x++) {
reportBuffer[x] = 0;
}
}
newReport = 1; // done
newData = 1; //exit loop
}
else if (instr == INSTR_KEYUP) {
if ((keyType[oldKey-1] == KEYTYPE_TEXT) || keyAction == KEYACTION_DOWN) cMode = KEYMODE_UP;
else cMode = KEYMODE_DOWN; // When the key on the keyboard was released, we play everything back invertedly.
}
else if (instr == INSTR_KEYDOWN) {
if ((keyType[oldKey-1] == KEYTYPE_TEXT) || keyAction == KEYACTION_DOWN) cMode = KEYMODE_DOWN;
else cMode = KEYMODE_UP; // See four lines above.
}
else if (instr == INSTR_KEYPRESS) {
cMode = KEYMODE_PRESS;
}
else if (instr > 0 && instr <= 133) { //normal keypress
uint8_t x=0;
for (x=2; x < 8; x++) { //Scan through all possible keys
if (cMode == KEYMODE_DOWN) {
if (reportBuffer[x] == 0) { //we need 2 find an unused key.
reportBuffer[x] = instr; // press key
break; //leave loop
}
}
else if (cMode == KEYMODE_UP) {
if (reportBuffer[x] == instr) { //we found our key
reportBuffer[x] = 0; //release it
break; //leave loop
}
}
else { //press
if (!flag_keyPress) {
if (reportBuffer[x] == 0) { //we found an empty key
reportBuffer[x] = instr; //press it
flag_keyPress = 1;
break; //leave loop
}
}
else {
if (reportBuffer[x] == instr) { //we found our key
reportBuffer[x] = 0; //release it
flag_keyPress = 0; //next cmd
break; //leave loop
}
}
}
}
newData = 1; //exit loop
}
else if (instr >= 224 && instr <= 231) {
//reportBuffer[0] |= (1 << (instr - 224)); // MOD-Keys (en)
if ((keyType[oldKey-1] == KEYTYPE_TEXT) || keyAction == KEYACTION_DOWN) reportBuffer[0] |= (1 << (instr - 224)); // MOD-Keys (en)
else reportBuffer[0] &= ~(1 << (instr - 224)); // MOD-Keys (dis)
}
else if (instr >= 232 && instr <= 239) {
reportBuffer[0] &= ~(1 << (instr - 232)); // MOD-Keys (dis)
if ((keyType[oldKey-1] == KEYTYPE_TEXT) || keyAction == KEYACTION_DOWN) reportBuffer[0] &= ~(1 << (instr - 232)); // MOD-Keys (dis)
else reportBuffer[0] |= (1 << (instr - 232)); // MOD-Keys (en)
}
if (!flag_keyPress) memAddr++; // increase memory address
// Currently there's NO protection.The system could jump onto the next key
// Can be simply fixed by using a modulo-operation on the current address and the MEM_KEY_LENGTH
}
}
void detectKeyType() {
uint8_t keyId;
uint16_t x;
for (keyId=0;keyId < 16; ++keyId) {
LED_PORT ^= (1 << LED_RED);
for (x=0; x < MEM_KEY_LENGTH; ++x) {
wdt_reset();
uint8_t instr = EEReadByte(keyId*MEM_KEY_LENGTH+x);
if (instr == INSTR_KEYDOWN || instr == INSTR_KEYUP) { // If KEYPRESS is used at any time, it is a text-command
keyType[keyId] = KEYTYPE_KEY;
break;
}
else if (instr == INSTR_BREAK) { // If only KEYDOWN and KEYUP are used KEYDOWN it is a standard key
keyType[keyId] = KEYTYPE_TEXT;
break;
}
}
}
}
void keyboard(void) {
LED_PORT |= (1 << LED_RED);
_delay_ms(250); // Make sure the cable is securely plugged in.
LED_PORT &= ~(1 << LED_RED);
_delay_ms(250);
wdt_enable(WDTO_2S);
EEOpen(); // We open our eeprom
uchar i;
// First, we scan through the codes so we can determine the key types.
detectKeyType();
cli();
usbInit();
usbDeviceDisconnect(); /* enforce re-enumeration, do this while interrupts are disabled! */
i = 0;
while(--i){ /* fake USB disconnect for > 250 ms */
wdt_reset();
_delay_ms(1);
}
usbDeviceConnect();
sei(); // Interrupt enable
LED_PORT &= ~(1 << LED_RED);
LED_PORT |= (1 << LED_GREEN);
for(;;){ /* main event loop */
wdt_reset();
usbPoll();
checkBtn();
if(usbInterruptIsReady()) {
LED_PORT |= (1 << LED_RED); // Switch red LED on
if (newReport == 0){ /* we can send another report */
buildReport();
usbSetInterrupt(reportBuffer, sizeof(reportBuffer));
}
}
else LED_PORT &= ~(1 << LED_RED); // Only for nice visual effects again :)
}
}
void checkBtn() {
if (newReport == 0) return; // ignore all keypresses if there's a current key in progress
if (getKey() != oldKey && getKey() == 0) {
keyAction = KEYACTION_UP;
if (keyType[oldKey-1] == KEYTYPE_KEY && hasReleased == 0) {
newReport = 0;
hasReleased = 1;
memAddr = MEM_KEY_LENGTH*(oldKey-1);
cMode = KEYMODE_PRESS; //default to keymode_press
}
else {
oldKey=0;
hasReleased = 1;
}
_delay_ms(10); // Debounce switches
}
else if (getKey() != oldKey && hasReleased == 1) {
keyAction = KEYACTION_DOWN;
hasReleased = 0;
oldKey = getKey();
newReport = 0;
memAddr = MEM_KEY_LENGTH*(oldKey-1);
cMode = KEYMODE_PRESS; //default to keymode_press
}
}