/* * ppk_usb * * Copyright (C) 2014 cy384 * All rights reserved. * * This software may be modified and distributed under the terms * of the BSD license. See the LICENSE file for details. * * MODIFIED by Christian Lysholm 2021 * Modified for BLE support using a tinyPICO ESP32 module. * Added battery monitoring and reporting via RGB LED * Moved keyboard booting until peripheral is detected * */ // Arduino BLE HID adapter for the Palm Portable Keyboard // If this include causes an error Arduino, just comment it out //#include #include #include //#include #include //#include // set to 3 for III hardware, or 5 for V hardware #define PPK_VERSION 500 // set to 1 to enable debug mode, which notes to the arduino console at 9600 #define PPK_DEBUG 0 #if PPK_VERSION == 3 #define VCC_PIN 2 #define RX_PIN 8 #define RTS_PIN 4 #define DCD_PIN 5 #define GND_PIN 6 #endif #if PPK_VERSION == 5 #define VCC_PIN 7 #define RX_PIN 8 #define RTS_PIN 5 #define DCD_PIN 4 #define GND_PIN 2 #endif #if PPK_VERSION == 500 #define VCC_PIN 4 #define RX_PIN 27 //External 10k pulldown resistor added #define RTS_PIN 26 #define DCD_PIN 25 #define GND_PIN 18 //UNUSED #define DET_PIN 22 //Peripheral detect, held low by KB #endif #define PULLDOWN_PIN 23 // set this to any unused pin #define TX_PIN 19 #if (PPK_VERSION != 3) && (PPK_VERSION != 5) && (PPK_VERSION != 500) #error #error #error you did not set your ppk version! #error read the instructions or read the code! #error #error #endif // convenience masks #define UPDOWN_MASK 0b10000000 #define X_MASK 0b00000111 #define Y_MASK 0b01111000 #define MAP_MASK 0b01111111 // wait this many milliseconds before making sure keyboard is still awake #define TIMEOUT 500000 #define BAT_CHECK_TIME 300000 //5 minutes #define HEARTBEAT_TIME 5000 //5 seconds // macro for testing if a char is printable ASCII #define PRINTABLE_CHAR(x) ((x >= 32) && (x <= 126)) uint32_t heartbeat_color; SoftwareSerial keyboard_serial(RX_PIN, TX_PIN, true); // RX, TX, inverted TinyPICO tp = TinyPICO(); int check_battery(void); int batLevel = check_battery(); BleKeyboard ble_kb("PICO_PPK", "LysTech", batLevel); char key_map[128] = { 0 }; char fn_key_map[128] = { 0 }; char last_byte = 0; char key_byte = 0; int fn_key_down = 0; int boot_state = 0; //keyboard boot state (0/1) unsigned long last_comm = 0; unsigned long last_bat = 0; unsigned long last_heartbeat = 0; unsigned long heartbeat_length = 750; //milliseconds void config_keymap() { // y0 row key_map[0b00000000] = '1'; key_map[0b00000001] = '2'; key_map[0b00000010] = '3'; key_map[0b00000011] = 'z'; key_map[0b00000100] = '4'; key_map[0b00000101] = '5'; key_map[0b00000110] = '6'; key_map[0b00000111] = '7'; // y1 row key_map[0b00001000] = KEY_LEFT_GUI; // "CMMD" or "Cmd" key_map[0b00001001] = 'q'; key_map[0b00001010] = 'w'; key_map[0b00001011] = 'e'; key_map[0b00001100] = 'r'; key_map[0b00001101] = 't'; key_map[0b00001110] = 'y'; key_map[0b00001111] = '`'; // y2 row key_map[0b00010000] = 'x'; key_map[0b00010001] = 'a'; key_map[0b00010010] = 's'; key_map[0b00010011] = 'd'; key_map[0b00010100] = 'f'; key_map[0b00010101] = 'g'; key_map[0b00010110] = 'h'; key_map[0b00010111] = ' '; // "Space 1" // y3 row key_map[0b00011000] = KEY_CAPS_LOCK; key_map[0b00011001] = KEY_TAB; key_map[0b00011010] = KEY_LEFT_CTRL; key_map[0b00011011] = 0; key_map[0b00011100] = 0; key_map[0b00011101] = 0; key_map[0b00011110] = 0; key_map[0b00011111] = 0; // y4 row key_map[0b00100000] = 0; key_map[0b00100001] = 0; key_map[0b00100010] = 0; // Fn key key_map[0b00100011] = KEY_LEFT_ALT; key_map[0b00100100] = 0; key_map[0b00100101] = 0; key_map[0b00100110] = 0; key_map[0b00100111] = 0; // y5 row key_map[0b00101000] = 0; key_map[0b00101001] = 0; key_map[0b00101010] = 0; key_map[0b00101011] = 0; key_map[0b00101100] = 'c'; key_map[0b00101101] = 'v'; key_map[0b00101110] = 'b'; key_map[0b00101111] = 'n'; // y6 row key_map[0b00110000] = '-'; key_map[0b00110001] = '='; key_map[0b00110010] = KEY_BACKSPACE; key_map[0b00110011] = 0; // "Special Function One" key_map[0b00110100] = '8'; key_map[0b00110101] = '9'; key_map[0b00110110] = '0'; key_map[0b00110111] = ' '; // "Space 2" // y7 row key_map[0b00111000] = '['; key_map[0b00111001] = ']'; key_map[0b00111010] = '\\'; key_map[0b00111011] = 0; // "Special Function Two" key_map[0b00111100] = 'u'; key_map[0b00111101] = 'i'; key_map[0b00111110] = 'o'; key_map[0b00111111] = 'p'; // y8 row key_map[0b01000000] = '\''; key_map[0b01000001] = KEY_RETURN; key_map[0b01000010] = 0; // "Special Function Three" key_map[0b01000011] = 0; key_map[0b01000100] = 'j'; key_map[0b01000101] = 'k'; key_map[0b01000110] = 'l'; key_map[0b01000111] = ';'; // y9 row key_map[0b01001000] = '/'; key_map[0b01001001] = KEY_UP_ARROW; key_map[0b01001010] = 0; // "Special Function Four" key_map[0b01001011] = 0; key_map[0b01001100] = 'm'; key_map[0b01001101] = ','; key_map[0b01001110] = '.'; key_map[0b01001111] = 0; // "DONE" or "Done" // y10 row key_map[0b01010000] = KEY_DELETE; key_map[0b01010001] = KEY_LEFT_ARROW; key_map[0b01010010] = KEY_DOWN_ARROW; key_map[0b01010011] = KEY_RIGHT_ARROW; key_map[0b01010100] = 0; key_map[0b01010101] = 0; key_map[0b01010110] = 0; key_map[0b01010111] = 0; // y11 row key_map[0b01011000] = KEY_LEFT_SHIFT; key_map[0b01011001] = KEY_RIGHT_SHIFT; key_map[0b01011010] = 0; key_map[0b01011011] = 0; key_map[0b01011100] = 0; key_map[0b01011101] = 0; key_map[0b01011110] = 0; key_map[0b01011111] = 0; } void config_fnkeymap() { fn_key_map[0b01010001] = KEY_HOME; // left arrow fn_key_map[0b01010010] = KEY_PAGE_DOWN; // down arrow fn_key_map[0b01010011] = KEY_END; // right arrow fn_key_map[0b01001001] = KEY_PAGE_UP; // up arrow fn_key_map[0b00011001] = KEY_ESC; // tab key fn_key_map[0b00000000] = KEY_F1; // 1 fn_key_map[0b00000001] = KEY_F2; // 2 fn_key_map[0b00000010] = KEY_F3; // 3 fn_key_map[0b00000100] = KEY_F4; // 4 fn_key_map[0b00000101] = KEY_F5; // 5 fn_key_map[0b00000110] = KEY_F6; // 6 fn_key_map[0b00000111] = KEY_F7; // 7 fn_key_map[0b00110100] = KEY_F8; // 8 fn_key_map[0b00110101] = KEY_F9; // 9 fn_key_map[0b00110110] = KEY_F10; // 0 fn_key_map[0b00110000] = KEY_F11; // - fn_key_map[0b00110001] = KEY_F12; // = } void print_byte_bin(char bin_byte) { Serial.print("0b"); for (int i = 7; i > -1; i--) Serial.print(int((bin_byte & (1 << i)) >> i)); } void print_keychange(char key_byte, char key_code, int key_up) { if (key_up) Serial.print("released: "); else Serial.print("pressed: "); print_byte_bin(key_byte); Serial.print(" mapped to "); if (key_code) { print_byte_bin(key_code); if(PRINTABLE_CHAR(key_code)) { Serial.print(" ("); Serial.print(key_code); Serial.print(")"); } else { Serial.print(" (unprintable)"); } } // Fn has no keycode, special case it else if (key_byte == 34) { Serial.print("Fn"); } else { Serial.print("nothing"); } Serial.println(""); } void boot_keyboard() { if (PPK_DEBUG) { // delay for a bit to allow for opening serial monitor etc. for (int i = 0; i < 15; delay(1000 + i++)) Serial.print("."); Serial.println("beginning keyboard boot sequence"); } tp.DotStar_SetPower(true); tp.DotStar_SetPixelColor(0x0000FF); pinMode(VCC_PIN, OUTPUT); pinMode(GND_PIN, OUTPUT); pinMode(PULLDOWN_PIN, OUTPUT); pinMode(RX_PIN, INPUT); //Add external Pulldown pinMode(DCD_PIN, INPUT); pinMode(RTS_PIN, INPUT); digitalWrite(VCC_PIN, LOW); digitalWrite(GND_PIN, LOW); digitalWrite(PULLDOWN_PIN, LOW); digitalWrite(VCC_PIN, HIGH); keyboard_serial.begin(9600); keyboard_serial.listen(); if (PPK_DEBUG) Serial.print("waiting for keyboard response..."); while(digitalRead(DCD_PIN) != HIGH) {;}; if (PPK_DEBUG) Serial.println(" done"); if (PPK_DEBUG) Serial.print("finishing handshake..."); if (digitalRead(RTS_PIN) == LOW) { delay(10); pinMode(RTS_PIN, OUTPUT); digitalWrite(RTS_PIN, HIGH); } else { pinMode(RTS_PIN, OUTPUT); digitalWrite(RTS_PIN, HIGH); digitalWrite(RTS_PIN, LOW); delay(10); digitalWrite(RTS_PIN, HIGH); } delay(5); if (PPK_DEBUG) Serial.println(" done"); if (PPK_DEBUG) Serial.print("waiting for keyboard serial ID..."); while (keyboard_serial.available() < 2) {;}; if (PPK_DEBUG) Serial.println(" done"); int byte1 = keyboard_serial.read(); int byte2 = keyboard_serial.read(); if (!((byte1 == 0xFA) && (byte2 == 0xFD))) { if (PPK_DEBUG) Serial.println("got wrong bytes? giving up here"); tp.DotStar_SetPower(true); tp.DotStar_SetPixelColor(0xFF00FF); while (1) {;}; } last_comm = millis(); tp.DotStar_SetPower(true); tp.DotStar_SetPixelColor(0x00FFFF); } int check_battery() { //batVolt has voltage levels every 5% of charge level. Adjust as necessary. Slightly better than a straight linear interp of voltage float batVolt_lookup[] = {3.27, 3.61, 3.69, 3.71, 3.73, 3.75, 3.77, 3.79, 3.80, 3.82, 3.84, 3.85, 3.87, 3.91, 3.95, 3.98, 4.02, 4.08, 4.11, 4.15, 4.20}; float batVolt = 0.0; //volts int batLevel = -1; //0 to 100 (percent as int) batVolt = tp.GetBatteryVoltage(); for (int n=0; n<=21; n++) { if (batVolt < batVolt_lookup[n]) { batLevel = 5*n; break; } } if (batLevel < 0) { //You shouldn't be here, batVolt was not found in range of lookup values. batLevel = 100; } return batLevel; } void check_connection() { if(digitalRead(DET_PIN)) { boot_state = 0; ble_kb.end(); delay(500); tp.DotStar_SetPower(true); tp.DotStar_SetPixelColor(0x0000FF); } } void setup() { if (PPK_DEBUG) { Serial.begin(9600); Serial.print("compiled in debug mode with PPK_VERSION "); Serial.println(PPK_VERSION); } config_keymap(); config_fnkeymap(); pinMode(DET_PIN, INPUT_PULLUP); boot_keyboard(); ble_kb.begin(); tp.DotStar_SetPower(true); //tp.DotStar_SetBrightness(64); //25% brightness last_bat = BAT_CHECK_TIME; if (PPK_DEBUG) Serial.println("setup completed"); } void loop() { if (keyboard_serial.available()) { for (int i = keyboard_serial.available(); i > 0; i--) { key_byte = keyboard_serial.read(); int key_up = key_byte & UPDOWN_MASK; int key_x = 0 + (key_byte & X_MASK); int key_y = 0 + ((key_byte & Y_MASK) >> 3); char key_code = 0; if (fn_key_down && fn_key_map[key_byte & MAP_MASK]) { key_code += fn_key_map[key_byte & MAP_MASK]; } else { key_code += key_map[key_byte & MAP_MASK]; } // keyboard duplicates the final key-up byte if (key_byte == last_byte) { ble_kb.releaseAll(); } else { if (PPK_DEBUG) print_keychange(key_byte & MAP_MASK, key_code, key_up); if (key_code != 0) { if (key_up) { ble_kb.release(key_code); } else { ble_kb.press(key_code); } } else { // special case the Fn key if ((key_byte & MAP_MASK) == 34) { fn_key_down = !key_up; } } } last_byte = key_byte; last_comm = millis(); } } else { // reboot if no recent comms, otherwise keyboard falls asleep if ((millis() - last_comm) > TIMEOUT) { if (PPK_DEBUG) Serial.println("rebooting keyboard for timeout"); digitalWrite(VCC_PIN, LOW); boot_keyboard(); } } if ((millis() - last_bat) > BAT_CHECK_TIME) { batLevel = check_battery(); ble_kb.setBatteryLevel(batLevel); if (batLevel > 30) //30-100% { //set green heartbeat heartbeat_color = 0x00FF00; } else if (batLevel >= 15)//15-30% { //set yellow/orange heartbeat heartbeat_color = 0xFF8000; } else // <15% { //set red heartbeat heartbeat_color = 0xFF0000; } tp.DotStar_SetPower(false); last_bat = millis(); } if ((millis() - last_heartbeat) > (HEARTBEAT_TIME + heartbeat_length)) { tp.DotStar_Clear(); tp.DotStar_SetPower(false); last_heartbeat = millis(); } else if ((millis() - last_heartbeat) > HEARTBEAT_TIME) { tp.DotStar_SetPower(true); tp.DotStar_SetPixelColor(heartbeat_color); //check_connection(); } }