prgKbd-keyboard/firmware/keyboard.c

365 lines
14 KiB
C
Executable File

#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=0;
uint8_t flag_keyPress=0;
// 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.
/*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
// 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] = '1';
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
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
}
}
return 0;
}
static void buildReport(void){
if(newReport == 0){
if (oldKey == Key_MODE) {
//Not yet implemented
}
else if (oldKey) { // One of the 16 keys was pressed
//reportBuffer[2] = 125 + oldKey; // 1+getKey()
// Set memAddr to the first byte of the pressed key
memAddr = MEM_KEY_LENGTH*(oldKey-1);
cMode = KEYMODE_PRESS; //default to keymode_press
oldKey = 0;
continueExecution();
}
else {
// Continue code execution.
continueExecution();
}
}
}
//WICHTIG: BEIM CANCEL-COMMAND (255) einen release auslösen... oder auch nicht... lassen wirs den user konfigurieren... oder auch nicht.. hmm.... reportBuf komplett auf 0 setzen wäre das dann
void continueExecution() {
uint8_t newData = 0;
while (!newData) {
uint8_t instr = EEReadByte(memAddr);
if (instr == INSTR_BREAK) {
// Release all keys (?)
uint8_t x;
for (x=0; x < 8; x++) {
reportBuffer[x] = 0;
}
newReport = 1; // done
newData = 1; //exit loop
}
else if (instr == INSTR_KEYUP) {
cMode = KEYMODE_UP;
}
else if (instr == INSTR_KEYDOWN) {
cMode = KEYMODE_DOWN;
}
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)
else if (instr >= 232 && instr <= 239) reportBuffer[0] &= ~(1 << (instr - 232)); // MOD-Keys (dis)
// MODS-KEYS are enabled/disabled using different commands. This will make it easier when being in text-mode.
/* OLD PART FOR MOD-KEYS: Replaced by new command set
New one is independent from mode
if (cMode == KEYMODE_DOWN) { // set bit
reportBuffer[0] |= (1 << (instr - 224));
}
else if (cMode == KEYMODE_UP) { // clear bit
reportBuffer[0] &= ~(1 << (instr - 224));
}
else {
// Err (press)
}
*/
if (!flag_keyPress) memAddr++; // increase memory address
// Currently there's NO protection.The system could jump onto the next key
}
}
void keyboard(void) {
wdt_enable(WDTO_2S);
EEOpen(); // We open our eeprom
uchar i;
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_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() == 0) hasReleased = 1;
if (getKey() != oldKey && hasReleased == 1) {
hasReleased = 0;
oldKey = getKey();
newReport = 0;
}
}