From e17d6c2f8e854b91a5c03fd0deab21d4957c698a Mon Sep 17 00:00:00 2001 From: Luka Void Date: Mon, 25 Nov 2024 10:35:59 +0100 Subject: [PATCH 01/19] - Refactor macro recording and execution logic to enhance performance, record and replay original delays. - Other improvements to EEPROM macro saving - Correct some comments --- Amiga500-USB-Keyboard-Leonardo.ino | 268 ++++++++++++++++------------- 1 file changed, 148 insertions(+), 120 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 5790c28..ed5ec71 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -13,29 +13,35 @@ #include #include -// Preprocessor flag to enable or disable debug mode -// Debug mode provides verbose console output at every step. -#define DEBUG_MODE 0 +// Preprocessor flag to enable or disable multimedia keys (Consumer device) +// Multimedia keys are mapped to the Amiga 500 keyboard and can be used to control media playback. +// NOTE: Do not modify Arduino core files; all necessary descriptors are added dynamically in setup() if this is enabled. +#define ENABLE_MULTIMEDIA_KEYS 1 // Preprocessor flag to enable or disable joystick support // Joystick support is only required if you are going to attach DB9 connectors and use legacy Amiga joysticks with the controller. +// NOTE: Do not modify Arduino core files; all necessary descriptors are added dynamically in setup() if this is enabled. #define ENABLE_JOYSTICKS 0 -#define ENABLE_MULTIMEDIA_KEYS 1 - #define PERSISTENT_MACRO 1 // Save macros to EEPROM -#define MAX_MACRO_LENGTH 24 // Maximum number of key reports in a macro -#define MACRO_SLOTS 5 -#define MACRO_DELAY 100 // ms between reports in macro playback -#define CONCURENT_MACROS 1 // Allow multiple macros to be played at the same time -#define MACRO_SAVE_VERSION 2 +#define MAX_MACRO_LENGTH 32 // Maximum number of key events in a macro (6 bytes per event) +#define MACRO_SLOTS 5 // Number of macro slots available, each slot is (6 bytes * MAX_MACRO_LENGTH) + 1 byte for length + // current defaults fit in 1kb eeprom for persistent macros + // If default values are changed and the size of eeprom is exceed, + // macros and the keyboard will still work if enough sram + // but macros will not be persistent anymore + // because the macro save to eeprom function will gracefully cancel +#define MACRO_DELAY 2 // ms between macro processing non blocking +#define CONCURENT_MACROS 1 // How many macros can be played at the same time +#define MACRO_SAVE_VERSION 4 // Version of the saved macros -#define PROGRAMMATIC_KEYS_RELEASE 2 // delay between press and release on programmatic keys (send keystrokes, macros...) +#define PROGRAMMATIC_KEYS_RELEASE 2 // delay between press and release on programmatic keys (sent keystrokes) #if PERSISTENT_MACRO #include #define EEPROM_START_ADDRESS 0 +#define SIZE_OF_EEPROM 1024 // 1kb #endif // Define bit masks for keyboard and joystick inputs @@ -47,6 +53,19 @@ #define BITMASK_JOY2 0b11110011 // IO A0..A5 #endif +// Preprocessor flag to enable or disable debug mode +// Debug mode provides some console output. +#define DEBUG_MODE 0 + +enum UsedHIDDescriptors +{ + HID_ID_KEYBOARD = 2, // Default HID ID for keyboard (from Keyboard.h) + HID_ID_JOYSTICK1, // Custom HID ID for Joystick 1 descriptor, added in setup() + HID_ID_JOYSTICK2, // Custom HID ID for Joystick 2 descriptor, added in setup() + HID_ID_MULTIMEDIA // Custom HID ID for Consumer device descriptor, added in setup() +}; +// NOTE: Do not modify Arduino core files; all necessary descriptors are added dynamically in setup() + enum AmigaKeys { // ROM Default (USA0) and USA1 Console Key Mapping // 0x00 - 0x0F @@ -313,11 +332,18 @@ enum KeyboardState WAIT_RES }; -// Macro structure +// Macro structures +struct MacroKeyEvent +{ + uint8_t keyCode; // 1 byte + bool isPressed; // 1 byte + uint32_t delay; // 4 bytes +}; + struct Macro { - KeyReport keyReports[MAX_MACRO_LENGTH]; - uint8_t length; + MacroKeyEvent keyEvents[MAX_MACRO_LENGTH]; // 6 bytes * MAX_MACRO_LENGTH + uint8_t length; // 1 byte }; struct MacroPlayStatus @@ -325,6 +351,7 @@ struct MacroPlayStatus bool playing; bool loop; uint8_t macroIndex; + uint32_t playStartTime; }; struct MultimediaKeyReport @@ -333,7 +360,7 @@ struct MultimediaKeyReport uint8_t keys; }; -MultimediaKeyReport multimediaKeyReport = {5, 0}; +MultimediaKeyReport multimediaKeyReport = {HID_ID_MULTIMEDIA, 0}; // Global variables KeyReport keyReport; @@ -358,7 +385,7 @@ const uint8_t joystick1Descriptor[] PROGMEM = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x05, // USAGE (Game Pad) 0xA1, 0x01, // COLLECTION (Application) - 0x85, 0x03, // REPORT_ID (3) + 0x85, HID_ID_JOYSTICK1, // REPORT_ID 0x09, 0x01, // USAGE (Pointer) 0xA1, 0x00, // COLLECTION (Physical) 0x09, 0x30, // USAGE (X) @@ -384,7 +411,7 @@ const uint8_t joystick2Descriptor[] PROGMEM = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x05, // USAGE (Game Pad) 0xA1, 0x01, // COLLECTION (Application) - 0x85, 0x04, // REPORT_ID (4) + 0x85, HID_ID_JOYSTICK2, // REPORT_ID 0x09, 0x01, // USAGE (Pointer) 0xA1, 0x00, // COLLECTION (Physical) 0x09, 0x30, // USAGE (X) @@ -416,7 +443,7 @@ const uint8_t multimediaDescriptor[] PROGMEM = { 0x05, 0x0C, // USAGE_PAGE (Consumer Devices) 0x09, 0x01, // USAGE (Consumer Control) 0xA1, 0x01, // COLLECTION (Application) - 0x85, 0x05, // REPORT_ID (5) + 0x85, HID_ID_MULTIMEDIA, // REPORT_ID (5) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) @@ -474,16 +501,30 @@ void saveMacrosToEEPROM() #if DEBUG_MODE Serial.println("Saving macros to EEPROM"); #endif + size_t size_of_macros = sizeof(macros); + + //check that it doesn't exceed 1kb of eeprom + //we add size of size_t, 1 bytes for the save version ad 4 bytes for the checksum + if (size_of_macros + sizeof(size_t) + sizeof(uint8_t) + sizeof(uint32_t) > SIZE_OF_EEPROM) + { + #if DEBUG_MODE + Serial.println("Size of macros exceeds 1kb of EEPROM, not saving"); + #endif + return; + } + int address = EEPROM_START_ADDRESS; uint8_t save_version = MACRO_SAVE_VERSION; EEPROM.put(address, save_version); address += sizeof(save_version); + EEPROM.put(address, size_of_macros); + address += sizeof(size_of_macros); for (int i = 0; i < MACRO_SLOTS; i++) { for (int j = 0; j < MAX_MACRO_LENGTH; j++) { - EEPROM.put(address, macros[i].keyReports[j]); - address += sizeof(KeyReport); + EEPROM.put(address, macros[i].keyEvents[j]); + address += sizeof(MacroKeyEvent); } EEPROM.put(address, macros[i].length); address += sizeof(macros[i].length); @@ -506,11 +547,22 @@ bool loadMacrosFromEEPROM() uint8_t save_version; EEPROM.get(address, save_version); address += sizeof(save_version); + size_t size_of_macros; + EEPROM.get(address, size_of_macros); + address += sizeof(size_of_macros); if (save_version != MACRO_SAVE_VERSION) { -#if DEBUG_MODE + #if DEBUG_MODE Serial.println("Save version mismatch, clearing macros"); -#endif + #endif + cleanMacros(); + return false; + } + if (size_of_macros != sizeof(macros)) + { + #if DEBUG_MODE + Serial.println("Macro length mismatch, clearing macros"); + #endif cleanMacros(); return false; } @@ -518,8 +570,8 @@ bool loadMacrosFromEEPROM() { for (int j = 0; j < MAX_MACRO_LENGTH; j++) { - EEPROM.get(address, macros[i].keyReports[j]); - address += sizeof(KeyReport); + EEPROM.get(address, macros[i].keyEvents[j]); + address += sizeof(MacroKeyEvent); } EEPROM.get(address, macros[i].length); address += sizeof(macros[i].length); @@ -606,7 +658,7 @@ void handleJoystick1() uint8_t currentJoyState = ~PIND & BITMASK_JOY1; if (currentJoyState != previousJoy1State) { - HID().SendReport(3, ¤tJoyState, 1); + HID().SendReport(HID_ID_JOYSTICK1, ¤tJoyState, 1); previousJoy1State = currentJoyState; } } @@ -616,7 +668,7 @@ void handleJoystick2() uint8_t currentJoyState = ~PINF & BITMASK_JOY2; if (currentJoyState != previousJoy2State) { - HID().SendReport(4, ¤tJoyState, 1); + HID().SendReport(HID_ID_JOYSTICK2, ¤tJoyState, 1); previousJoy2State = currentJoyState; } } @@ -720,7 +772,7 @@ void sendReport() { if (memcmp(&keyReport, &prevkeyReport, sizeof(KeyReport)) != 0) { - HID().SendReport(2, &keyReport, sizeof(KeyReport)); + HID().SendReport(HID_ID_KEYBOARD, &keyReport, sizeof(KeyReport)); memcpy(&prevkeyReport, &keyReport, sizeof(KeyReport)); } } @@ -780,15 +832,14 @@ void processKeyCode() { noInterrupts(); // Disable interrupts to enter critical section recordingMacroSlot = macroSlotFromKeyCode(currentKeyCode); - memset(macros[recordingMacroSlot].keyReports, 0, sizeof(macros[recordingMacroSlot].keyReports)); // Clear macro slot - macros[recordingMacroSlot].length = 0; + memset(¯os[recordingMacroSlot], 0, sizeof(macros[recordingMacroSlot])); // Clear macro slot recordingMacroIndex = 0; recordingSlot = true; interrupts(); // Enable interrupts to exit critical section -#if DEBUG_MODE - Serial.print("Recording slot selected: "); - Serial.println(currentMacroSlot, HEX); -#endif + #if DEBUG_MODE + Serial.print("Recording slot selected: "); + Serial.println(currentMacroSlot, HEX); + #endif } return; } @@ -907,10 +958,11 @@ bool isAmigaModifierKey(uint8_t keyCode) void keyPress(uint8_t keyCode) { -#if DEBUG_MODE - Serial.print("Key press: "); - Serial.println(keyCode, HEX); -#endif + record_key(keyCode,true); // if macro recording on, record key + #if DEBUG_MODE + Serial.print("Key press: "); + Serial.println(keyCode, HEX); + #endif uint8_t hidCode = keyTable[keyCode]; if (isAmigaModifierKey(keyCode)) { @@ -931,15 +983,15 @@ void keyPress(uint8_t keyCode) #if DEBUG_MODE printKeyReport(); #endif - record_last_report(); } void keyRelease(uint8_t keyCode) { -#if DEBUG_MODE - Serial.print("Key release: "); - Serial.println(keyCode, HEX); -#endif + record_key(keyCode,false); // if macro recording on, record key + #if DEBUG_MODE + Serial.print("Key release: "); + Serial.println(keyCode, HEX); + #endif uint8_t hidCode = keyTable[keyCode]; if (isAmigaModifierKey(keyCode)) { @@ -1023,6 +1075,12 @@ void stopRecording() noInterrupts(); // Disable interrupts to enter critical section recording = false; recordingSlot = false; + //normalize delays in the macro + const uint32_t firstDelay = macros[recordingMacroSlot].keyEvents[0].delay; + for (int i = 0; i < macros[recordingMacroSlot].length; i++) + { + macros[recordingMacroSlot].keyEvents[i].delay -= firstDelay; + } // Save macros to EEPROM saveMacrosToEEPROM(); interrupts(); // Enable interrupts to exit critical section @@ -1031,11 +1089,7 @@ void stopRecording() void cleanMacros() { - for (int i = 0; i < MACRO_SLOTS; i++) - { - memset(macros[i].keyReports, 0, sizeof(macros[i].keyReports)); - macros[i].length = 0; - } + memset(macros, 0x00, sizeof(macros)); } void resetMacros() @@ -1054,7 +1108,7 @@ void resetMacros() void playMacroSlot(uint8_t slot) { noInterrupts(); // Disable interrupts to enter critical section - if (!macroPlayStatus[slot].playing && nMacrosPlaying() < CONCURENT_MACROS) + if (!recording && !macroPlayStatus[slot].playing && nMacrosPlaying() < CONCURENT_MACROS) { #if DEBUG_MODE Serial.print("Play macro slot: "); @@ -1069,6 +1123,7 @@ void playMacroSlot(uint8_t slot) { macroPlayStatus[slot].loop = false; } + macroPlayStatus[slot].playStartTime = millis(); macroPlayStatus[slot].playing = true; } else @@ -1081,6 +1136,7 @@ void playMacroSlot(uint8_t slot) macroPlayStatus[slot].playing = false; macroPlayStatus[slot].loop = false; macroPlayStatus[slot].macroIndex = 0; + macroPlayStatus[slot].playStartTime = 0; } interrupts(); // Enable interrupts to exit critical section } @@ -1121,6 +1177,7 @@ void stopAllMacros() macroPlayStatus[i].playing = false; macroPlayStatus[i].loop = false; macroPlayStatus[i].macroIndex = 0; + macroPlayStatus[i].playStartTime = 0; } } releaseAll(); @@ -1128,84 +1185,53 @@ void stopAllMacros() void playMacro() { + + if(recording){ + return; + } + static uint32_t lastMacroTime = 0; + const uint32_t currentTime = millis(); - if (millis() - lastMacroTime >= MACRO_DELAY) + if (currentTime - lastMacroTime >= MACRO_DELAY) { for (uint8_t macro_slot = 0; macro_slot < MACRO_SLOTS; macro_slot++) { // Check if the macro is currently playing if (macroPlayStatus[macro_slot].playing) { - if (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length) - { - // Add keys from the macro report - for (uint8_t i = 0; i < 6; i++) - { - uint8_t macroKey = macros[macro_slot].keyReports[macroPlayStatus[macro_slot].macroIndex].keys[i]; - if (macroKey != 0) - { - // Find an empty slot in the merged report - for (uint8_t j = 0; j < 6; j++) - { - if (keyReport.keys[j] == 0) - { - keyReport.keys[j] = macroKey; - break; - } - } - } - } - - // Merge modifiers - keyReport.modifiers |= macros[macro_slot].keyReports[macroPlayStatus[macro_slot].macroIndex].modifiers; - - // Send the merged key report - sendReport(); - delay(PROGRAMMATIC_KEYS_RELEASE); - - // Remove the keys added by the macro - for (uint8_t i = 0; i < 6; i++) - { - uint8_t macroKey = macros[macro_slot].keyReports[macroPlayStatus[macro_slot].macroIndex].keys[i]; - if (macroKey != 0) - { - // Find and remove the key from the merged report - for (uint8_t j = 0; j < 6; j++) - { - if (keyReport.keys[j] == macroKey) - { - keyReport.keys[j] = 0; - break; - } - } - } - } - - // Remove the modifiers added by the macro - keyReport.modifiers &= ~macros[macro_slot].keyReports[macroPlayStatus[macro_slot].macroIndex].modifiers; - - // Send the updated report to release the keys - sendReport(); - // Move to the next report in the macro - macroPlayStatus[macro_slot].macroIndex++; - } - - // Check if the macro has completed - if (macroPlayStatus[macro_slot].macroIndex >= macros[macro_slot].length) - { - if (macroPlayStatus[macro_slot].loop) - { - // Reset to the beginning of the macro if looping - macroPlayStatus[macro_slot].macroIndex = 0; - } - else - { - // Stop playing if not looping - macroPlayStatus[macro_slot].playing = false; - macroPlayStatus[macro_slot].macroIndex = 0; - } - } + if (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length) + { + //Process Key Event if delay has passed + if (currentTime - macroPlayStatus[macro_slot].playStartTime >= macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay) { + if(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].isPressed){ + keyPress(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); + } + else{ + keyRelease(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); + } + // Move to the next report in the macro + macroPlayStatus[macro_slot].macroIndex++; + } + } + + // Check if the macro has completed + if (macroPlayStatus[macro_slot].macroIndex >= macros[macro_slot].length) + { + if (macroPlayStatus[macro_slot].loop) + { + // Reset to the beginning of the macro if looping + macroPlayStatus[macro_slot].macroIndex = 0; + macroPlayStatus[macro_slot].playStartTime = millis(); + } + else + { + // Stop playing if not looping + macroPlayStatus[macro_slot].playing = false; + macroPlayStatus[macro_slot].macroIndex = 0; + macroPlayStatus[macro_slot].playStartTime = 0; + } + } } } @@ -1214,7 +1240,7 @@ void playMacro() } } -void record_last_report() +void record_key(uint8_t keycode, bool isPressed) { if (recording && recordingSlot && recordingMacroIndex < MAX_MACRO_LENGTH) { @@ -1223,7 +1249,9 @@ void record_last_report() Serial.print("Recording key report at index: "); Serial.println(macroIndex); #endif - memcpy(¯os[recordingMacroSlot].keyReports[recordingMacroIndex], &keyReport, sizeof(KeyReport)); + macros[recordingMacroSlot].keyEvents[recordingMacroIndex].isPressed = isPressed; + macros[recordingMacroSlot].keyEvents[recordingMacroIndex].keyCode = keycode; + macros[recordingMacroSlot].keyEvents[recordingMacroIndex].delay = millis(); recordingMacroIndex++; macros[recordingMacroSlot].length = recordingMacroIndex; // Check if the last index was recorded From 4b409fa3beafc4578d9333ada0f0e1fa2ff44239 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Mon, 25 Nov 2024 10:57:08 +0100 Subject: [PATCH 02/19] Update license information and add repository links in comments --- Amiga500-USB-Keyboard-Leonardo.ino | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index ed5ec71..c97010d 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -1,13 +1,17 @@ /* + * SPDX-License-Identifier: MIT * Amiga 500 Keyboard to USB HID Converter Arduino Leonardo - * This rewrite/update and version (c) 2024 by Luka "Void" MIT License + * Copyright (c) 2024 by Luka "Void" MIT License * GitHub: https://github.com/arvvoid/ * Contact: luka@lukavoid.xyz + * Repository: https://github.com/arvvoid/Amiga500-USB-Keyboard-Leonardo * Original code and inspiration by olaf, Steve_Reaver (taken from https://forum.arduino.cc/index.php?topic=139358.15) * * This sketch converts an original Amiga 500 keyboard to a standard USB HID * keyboard using an Arduino Leonardo. It includes support for joystick inputs * and special function keys. + * + * Readme: https://github.com/arvvoid/Amiga500-USB-Keyboard-Leonardo/blob/main/README.md */ #include From 23133eabbf5e00c35435664076deb4c04a5ad5d1 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Mon, 25 Nov 2024 11:02:29 +0100 Subject: [PATCH 03/19] Update README.md to reflect changes in macro slot capacity and playback timing --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cbc7253..95b64f1 100644 --- a/README.md +++ b/README.md @@ -147,8 +147,8 @@ This section explains how to use the macro recording and playback functionality ### Macro Slots -There are 5 macro slots available, each capable of storing up to 24 key reports. The macros are stored in EEPROM, so they persist across power cycles. -24 to keep withing the EEPROM 1kb size of the Leonardo. If you disable persistent macros flag you can go up to 45 per slot on the Leonardo but macros will not persist power cycles. +There are 5 macro slots available, each capable of storing up to 32 key events. The macros are stored in EEPROM, so they persist across power cycles. +32 to keep withing the EEPROM 1kb size of the Leonardo. If you disable persistent macros flag you can larger macros per slot but macros will not persist power cycles. ### **Recording a Macro** @@ -167,7 +167,7 @@ There are 5 macro slots available, each capable of storing up to 24 key reports. 1. **Play a Macro**: - Press **Help + F6** to **Help + F10** to play the macro stored in the corresponding slot (slots 1 to 5). - - The macro will replay the recorded key presses at fixed intervals. + - The macro will replay the recorded key presses/releases at original timing. 2. **Activate Looping** (BETA): - Press **Help + F5** to toggle looping mode for macros. @@ -176,7 +176,6 @@ There are 5 macro slots available, each capable of storing up to 24 key reports. - You can deactivate looping mode to allow other macros to play just once. - To stop a looping macro, press the corresponding slot key again (e.g., **Help + F6**) while the macro is playing. - **Note:** Running too many loops with many key presses may interfere with normal keyboard functionality and introduce delays to standard key presses. Use with caution. - - **Note:** This feature is work in progress 3. **Stop All Macros**: - Press **Help + Backspace** to stop all currently playing macros. @@ -200,7 +199,7 @@ There are 5 macro slots available, each capable of storing up to 24 key reports. ### Notes - Macros are stored in EEPROM, so they will persist across power cycles. -- Each macro slot can store up to 24 key reports. +- Each macro slot can store up to 32 key events. - The recording will stop automatically if the macro slot is full. # Build and Upload Guide @@ -396,7 +395,6 @@ The **Arduino IDE** provides a graphical interface for writing, compiling, and u ## TODO -- [ ] Implement a processing queue for managing keypresses efficiently. - [ ] Add an optional Piezo Buzzer to the Leonardo for audio feedback, providing better user experience during macro recording. - [ ] Multiple layouts. - [ ] Remap any key on the fly From 9fb14640805d8cfd016dbe90c710ead3ff87edfa Mon Sep 17 00:00:00 2001 From: Luka Void Date: Mon, 25 Nov 2024 11:05:00 +0100 Subject: [PATCH 04/19] Increase concurrent macro playback capacity from 1 to 2 --- Amiga500-USB-Keyboard-Leonardo.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index c97010d..804378d 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -37,7 +37,7 @@ // but macros will not be persistent anymore // because the macro save to eeprom function will gracefully cancel #define MACRO_DELAY 2 // ms between macro processing non blocking -#define CONCURENT_MACROS 1 // How many macros can be played at the same time +#define CONCURENT_MACROS 2 // How many macros can be played at the same time #define MACRO_SAVE_VERSION 4 // Version of the saved macros #define PROGRAMMATIC_KEYS_RELEASE 2 // delay between press and release on programmatic keys (sent keystrokes) From 62f9bcade824109bfa4a5de1c370d03bce7b839f Mon Sep 17 00:00:00 2001 From: Luka Void Date: Tue, 26 Nov 2024 09:51:45 +0100 Subject: [PATCH 05/19] Minor fixes and autoformat --- Amiga500-USB-Keyboard-Leonardo.ino | 282 +++++++++++++++-------------- 1 file changed, 144 insertions(+), 138 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 804378d..d76585c 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -10,7 +10,7 @@ * This sketch converts an original Amiga 500 keyboard to a standard USB HID * keyboard using an Arduino Leonardo. It includes support for joystick inputs * and special function keys. - * + * * Readme: https://github.com/arvvoid/Amiga500-USB-Keyboard-Leonardo/blob/main/README.md */ @@ -29,15 +29,15 @@ #define PERSISTENT_MACRO 1 // Save macros to EEPROM -#define MAX_MACRO_LENGTH 32 // Maximum number of key events in a macro (6 bytes per event) -#define MACRO_SLOTS 5 // Number of macro slots available, each slot is (6 bytes * MAX_MACRO_LENGTH) + 1 byte for length - // current defaults fit in 1kb eeprom for persistent macros - // If default values are changed and the size of eeprom is exceed, - // macros and the keyboard will still work if enough sram - // but macros will not be persistent anymore - // because the macro save to eeprom function will gracefully cancel -#define MACRO_DELAY 2 // ms between macro processing non blocking -#define CONCURENT_MACROS 2 // How many macros can be played at the same time +#define MAX_MACRO_LENGTH 32 // Maximum number of key events in a macro (6 bytes per event) +#define MACRO_SLOTS 5 // Number of macro slots available, each slot is (6 bytes * MAX_MACRO_LENGTH) + 1 byte for length + // current defaults fit in 1kb eeprom for persistent macros + // If default values are changed and the size of eeprom is exceed, + // macros and the keyboard will still work if enough sram + // but macros will not be persistent anymore + // because the macro save to eeprom function will gracefully cancel +#define MACRO_DELAY 1 // ms between macro processing non blocking +#define CONCURENT_MACROS 2 // How many macros can be played at the same time #define MACRO_SAVE_VERSION 4 // Version of the saved macros #define PROGRAMMATIC_KEYS_RELEASE 2 // delay between press and release on programmatic keys (sent keystrokes) @@ -63,10 +63,10 @@ enum UsedHIDDescriptors { - HID_ID_KEYBOARD = 2, // Default HID ID for keyboard (from Keyboard.h) - HID_ID_JOYSTICK1, // Custom HID ID for Joystick 1 descriptor, added in setup() - HID_ID_JOYSTICK2, // Custom HID ID for Joystick 2 descriptor, added in setup() - HID_ID_MULTIMEDIA // Custom HID ID for Consumer device descriptor, added in setup() + HID_ID_KEYBOARD = 2, // Default HID ID for keyboard (from Keyboard.h) + HID_ID_JOYSTICK1, // Custom HID ID for Joystick 1 descriptor, added in setup() + HID_ID_JOYSTICK2, // Custom HID ID for Joystick 2 descriptor, added in setup() + HID_ID_MULTIMEDIA // Custom HID ID for Consumer device descriptor, added in setup() }; // NOTE: Do not modify Arduino core files; all necessary descriptors are added dynamically in setup() @@ -346,8 +346,8 @@ struct MacroKeyEvent struct Macro { - MacroKeyEvent keyEvents[MAX_MACRO_LENGTH]; // 6 bytes * MAX_MACRO_LENGTH - uint8_t length; // 1 byte + MacroKeyEvent keyEvents[MAX_MACRO_LENGTH]; // 6 bytes * MAX_MACRO_LENGTH + uint8_t length; // 1 byte }; struct MacroPlayStatus @@ -386,55 +386,55 @@ uint8_t previousJoy1State = 0xFF; // Initialize to 0xFF so that initial state tr uint8_t previousJoy2State = 0xFF; const uint8_t joystick1Descriptor[] PROGMEM = { - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x05, // USAGE (Game Pad) - 0xA1, 0x01, // COLLECTION (Application) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x05, // USAGE (Game Pad) + 0xA1, 0x01, // COLLECTION (Application) 0x85, HID_ID_JOYSTICK1, // REPORT_ID - 0x09, 0x01, // USAGE (Pointer) - 0xA1, 0x00, // COLLECTION (Physical) - 0x09, 0x30, // USAGE (X) - 0x09, 0x31, // USAGE (Y) - 0x15, 0xFF, // LOGICAL_MINIMUM (-1) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x95, 0x02, // REPORT_COUNT (2) - 0x75, 0x02, // REPORT_SIZE (2) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0xC0, // END_COLLECTION - 0x05, 0x09, // USAGE_PAGE (Button) - 0x19, 0x01, // USAGE_MINIMUM (Button 1) - 0x29, 0x02, // USAGE_MAXIMUM (Button 2) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x95, 0x02, // REPORT_COUNT (2) - 0x75, 0x01, // REPORT_SIZE (1) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0xC0 // END_COLLECTION + 0x09, 0x01, // USAGE (Pointer) + 0xA1, 0x00, // COLLECTION (Physical) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0xFF, // LOGICAL_MINIMUM (-1) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x02, // REPORT_COUNT (2) + 0x75, 0x02, // REPORT_SIZE (2) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xC0, // END_COLLECTION + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x02, // USAGE_MAXIMUM (Button 2) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x02, // REPORT_COUNT (2) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xC0 // END_COLLECTION }; const uint8_t joystick2Descriptor[] PROGMEM = { - 0x05, 0x01, // USAGE_PAGE (Generic Desktop) - 0x09, 0x05, // USAGE (Game Pad) - 0xA1, 0x01, // COLLECTION (Application) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x05, // USAGE (Game Pad) + 0xA1, 0x01, // COLLECTION (Application) 0x85, HID_ID_JOYSTICK2, // REPORT_ID - 0x09, 0x01, // USAGE (Pointer) - 0xA1, 0x00, // COLLECTION (Physical) - 0x09, 0x30, // USAGE (X) - 0x09, 0x31, // USAGE (Y) - 0x15, 0xFF, // LOGICAL_MINIMUM (-1) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x95, 0x02, // REPORT_COUNT (2) - 0x75, 0x02, // REPORT_SIZE (2) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0xC0, // END_COLLECTION - 0x05, 0x09, // USAGE_PAGE (Button) - 0x19, 0x01, // USAGE_MINIMUM (Button 1) - 0x29, 0x02, // USAGE_MAXIMUM (Button 2) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x95, 0x02, // REPORT_COUNT (2) - 0x75, 0x01, // REPORT_SIZE (1) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0xC0 // END_COLLECTION + 0x09, 0x01, // USAGE (Pointer) + 0xA1, 0x00, // COLLECTION (Physical) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0xFF, // LOGICAL_MINIMUM (-1) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x02, // REPORT_COUNT (2) + 0x75, 0x02, // REPORT_SIZE (2) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xC0, // END_COLLECTION + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x02, // USAGE_MAXIMUM (Button 2) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x02, // REPORT_COUNT (2) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xC0 // END_COLLECTION }; // Wrap Descriptors in HIDSubDescriptor @@ -444,26 +444,26 @@ HIDSubDescriptor joystick2HID(joystick2Descriptor, sizeof(joystick2Descriptor)); #if ENABLE_MULTIMEDIA_KEYS const uint8_t multimediaDescriptor[] PROGMEM = { - 0x05, 0x0C, // USAGE_PAGE (Consumer Devices) - 0x09, 0x01, // USAGE (Consumer Control) - 0xA1, 0x01, // COLLECTION (Application) + 0x05, 0x0C, // USAGE_PAGE (Consumer Devices) + 0x09, 0x01, // USAGE (Consumer Control) + 0xA1, 0x01, // COLLECTION (Application) 0x85, HID_ID_MULTIMEDIA, // REPORT_ID (5) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x25, 0x01, // LOGICAL_MAXIMUM (1) - 0x75, 0x01, // REPORT_SIZE (1) - 0x95, 0x07, // REPORT_COUNT (7) - 0x09, 0xB5, // USAGE (Next Track) - 0x09, 0xB6, // USAGE (Previous Track) - 0x09, 0xB7, // USAGE (Stop) - 0x09, 0xCD, // USAGE (Play/Pause) - 0x09, 0xE2, // USAGE (Mute) - 0x09, 0xE9, // USAGE (Volume Up) - 0x09, 0xEA, // USAGE (Volume Down) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0x95, 0x01, // REPORT_COUNT (1) - 0x75, 0x01, // REPORT_SIZE (1) - 0x81, 0x03, // INPUT (Const,Var,Abs) - Padding - 0xC0 // END_COLLECTION + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x07, // REPORT_COUNT (7) + 0x09, 0xB5, // USAGE (Next Track) + 0x09, 0xB6, // USAGE (Previous Track) + 0x09, 0xB7, // USAGE (Stop) + 0x09, 0xCD, // USAGE (Play/Pause) + 0x09, 0xE2, // USAGE (Mute) + 0x09, 0xE9, // USAGE (Volume Up) + 0x09, 0xEA, // USAGE (Volume Down) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x03, // INPUT (Const,Var,Abs) - Padding + 0xC0 // END_COLLECTION }; HIDSubDescriptor multimediaHID(multimediaDescriptor, sizeof(multimediaDescriptor)); @@ -507,13 +507,13 @@ void saveMacrosToEEPROM() #endif size_t size_of_macros = sizeof(macros); - //check that it doesn't exceed 1kb of eeprom - //we add size of size_t, 1 bytes for the save version ad 4 bytes for the checksum + // check that it doesn't exceed 1kb of eeprom + // we add size of size_t, 1 bytes for the save version ad 4 bytes for the checksum if (size_of_macros + sizeof(size_t) + sizeof(uint8_t) + sizeof(uint32_t) > SIZE_OF_EEPROM) { - #if DEBUG_MODE +#if DEBUG_MODE Serial.println("Size of macros exceeds 1kb of EEPROM, not saving"); - #endif +#endif return; } @@ -556,17 +556,17 @@ bool loadMacrosFromEEPROM() address += sizeof(size_of_macros); if (save_version != MACRO_SAVE_VERSION) { - #if DEBUG_MODE +#if DEBUG_MODE Serial.println("Save version mismatch, clearing macros"); - #endif +#endif cleanMacros(); return false; } if (size_of_macros != sizeof(macros)) { - #if DEBUG_MODE +#if DEBUG_MODE Serial.println("Macro length mismatch, clearing macros"); - #endif +#endif cleanMacros(); return false; } @@ -809,6 +809,7 @@ void processKeyCode() { // 'Help' key toggles function mode functionMode = isKeyDown; + return; } else if (currentKeyCode == AMIGA_KEY_CAPS_LOCK) { @@ -840,10 +841,10 @@ void processKeyCode() recordingMacroIndex = 0; recordingSlot = true; interrupts(); // Enable interrupts to exit critical section - #if DEBUG_MODE - Serial.print("Recording slot selected: "); - Serial.println(currentMacroSlot, HEX); - #endif +#if DEBUG_MODE + Serial.print("Recording slot selected: "); + Serial.println(currentMacroSlot, HEX); +#endif } return; } @@ -962,11 +963,11 @@ bool isAmigaModifierKey(uint8_t keyCode) void keyPress(uint8_t keyCode) { - record_key(keyCode,true); // if macro recording on, record key - #if DEBUG_MODE - Serial.print("Key press: "); - Serial.println(keyCode, HEX); - #endif + record_key(keyCode, true); // if macro recording on, record key +#if DEBUG_MODE + Serial.print("Key press: "); + Serial.println(keyCode, HEX); +#endif uint8_t hidCode = keyTable[keyCode]; if (isAmigaModifierKey(keyCode)) { @@ -991,11 +992,11 @@ void keyPress(uint8_t keyCode) void keyRelease(uint8_t keyCode) { - record_key(keyCode,false); // if macro recording on, record key - #if DEBUG_MODE - Serial.print("Key release: "); - Serial.println(keyCode, HEX); - #endif + record_key(keyCode, false); // if macro recording on, record key +#if DEBUG_MODE + Serial.print("Key release: "); + Serial.println(keyCode, HEX); +#endif uint8_t hidCode = keyTable[keyCode]; if (isAmigaModifierKey(keyCode)) { @@ -1079,12 +1080,13 @@ void stopRecording() noInterrupts(); // Disable interrupts to enter critical section recording = false; recordingSlot = false; - //normalize delays in the macro + // normalize delays in the macro const uint32_t firstDelay = macros[recordingMacroSlot].keyEvents[0].delay; for (int i = 0; i < macros[recordingMacroSlot].length; i++) { macros[recordingMacroSlot].keyEvents[i].delay -= firstDelay; } + functionMode = false; // Save macros to EEPROM saveMacrosToEEPROM(); interrupts(); // Enable interrupts to exit critical section @@ -1093,7 +1095,7 @@ void stopRecording() void cleanMacros() { - memset(macros, 0x00, sizeof(macros)); + memset(macros, 0x00, sizeof(macros)); } void resetMacros() @@ -1132,7 +1134,7 @@ void playMacroSlot(uint8_t slot) } else { -// togle playing +// toggle playing #if DEBUG_MODE Serial.print("Stop Play macro slot: "); Serial.println(slot); @@ -1190,52 +1192,56 @@ void stopAllMacros() void playMacro() { - if(recording){ + if (recording) + { return; } static uint32_t lastMacroTime = 0; const uint32_t currentTime = millis(); - if (currentTime - lastMacroTime >= MACRO_DELAY) + if (millis() - lastMacroTime >= MACRO_DELAY) { for (uint8_t macro_slot = 0; macro_slot < MACRO_SLOTS; macro_slot++) { // Check if the macro is currently playing if (macroPlayStatus[macro_slot].playing) { - if (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length) - { - //Process Key Event if delay has passed - if (currentTime - macroPlayStatus[macro_slot].playStartTime >= macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay) { - if(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].isPressed){ - keyPress(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); - } - else{ - keyRelease(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); - } - // Move to the next report in the macro - macroPlayStatus[macro_slot].macroIndex++; - } - } - - // Check if the macro has completed - if (macroPlayStatus[macro_slot].macroIndex >= macros[macro_slot].length) - { - if (macroPlayStatus[macro_slot].loop) - { - // Reset to the beginning of the macro if looping - macroPlayStatus[macro_slot].macroIndex = 0; - macroPlayStatus[macro_slot].playStartTime = millis(); - } - else - { - // Stop playing if not looping - macroPlayStatus[macro_slot].playing = false; - macroPlayStatus[macro_slot].macroIndex = 0; - macroPlayStatus[macro_slot].playStartTime = 0; - } - } + if (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length) + { + // Process Key Event if delay has passed + if (currentTime - macroPlayStatus[macro_slot].playStartTime >= macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay) + { + if (macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].isPressed) + { + keyPress(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); + } + else + { + keyRelease(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); + } + // Move to the next report in the macro + macroPlayStatus[macro_slot].macroIndex++; + } + } + + // Check if the macro has completed + if (macroPlayStatus[macro_slot].macroIndex >= macros[macro_slot].length) + { + if (macroPlayStatus[macro_slot].loop) + { + // Reset to the beginning of the macro if looping + macroPlayStatus[macro_slot].macroIndex = 0; + macroPlayStatus[macro_slot].playStartTime = millis(); + } + else + { + // Stop playing if not looping + macroPlayStatus[macro_slot].playing = false; + macroPlayStatus[macro_slot].macroIndex = 0; + macroPlayStatus[macro_slot].playStartTime = 0; + } + } } } From aaed6e9bf49e10f635e4c5b678eaf9d7a404822d Mon Sep 17 00:00:00 2001 From: Luka Void Date: Wed, 27 Nov 2024 09:57:35 +0100 Subject: [PATCH 06/19] Macros play in a dedicated macro "virtual keyboard" and enhance macro processing logic --- Amiga500-USB-Keyboard-Leonardo.ino | 147 ++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 21 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index d76585c..9e853a1 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -35,12 +35,12 @@ // If default values are changed and the size of eeprom is exceed, // macros and the keyboard will still work if enough sram // but macros will not be persistent anymore - // because the macro save to eeprom function will gracefully cancel -#define MACRO_DELAY 1 // ms between macro processing non blocking + // because the macro save to eeprom function will gracefully cancel if 1kb is exceeded +#define MACRO_DELAY 20 // ms between macro processing loops non blocking (at every loop all eligible key events in the delay window are processed) #define CONCURENT_MACROS 2 // How many macros can be played at the same time #define MACRO_SAVE_VERSION 4 // Version of the saved macros -#define PROGRAMMATIC_KEYS_RELEASE 2 // delay between press and release on programmatic keys (sent keystrokes) +#define PROGRAMMATIC_KEYS_RELEASE 2 // delay in ms between press and release on programmatic keys (sent keystrokes) #if PERSISTENT_MACRO #include @@ -66,7 +66,8 @@ enum UsedHIDDescriptors HID_ID_KEYBOARD = 2, // Default HID ID for keyboard (from Keyboard.h) HID_ID_JOYSTICK1, // Custom HID ID for Joystick 1 descriptor, added in setup() HID_ID_JOYSTICK2, // Custom HID ID for Joystick 2 descriptor, added in setup() - HID_ID_MULTIMEDIA // Custom HID ID for Consumer device descriptor, added in setup() + HID_ID_MULTIMEDIA, // Custom HID ID for Consumer device descriptor, added in setup() + HID_ID_MACROVKEYS, // Custom HID ID for Macro Virtual Keyboard, added in setup() }; // NOTE: Do not modify Arduino core files; all necessary descriptors are added dynamically in setup() @@ -358,6 +359,41 @@ struct MacroPlayStatus uint32_t playStartTime; }; +static const uint8_t macroKeyboardDescriptor[] PROGMEM = { + + // Keyboard + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) // 47 + 0x09, 0x06, // USAGE (Keyboard) + 0xa1, 0x01, // COLLECTION (Application) + 0x85, HID_ID_MACROVKEYS, // REPORT_ID + 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, 0x06, // REPORT_COUNT (6) + 0x75, 0x08, // REPORT_SIZE (8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x73, // LOGICAL_MAXIMUM (115) + 0x05, 0x07, // USAGE_PAGE (Keyboard) + + 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) + 0x29, 0x73, // USAGE_MAXIMUM (Keyboard Application) + 0x81, 0x00, // INPUT (Data,Ary,Abs) + 0xc0, // END_COLLECTION +}; + +HIDSubDescriptor macroKeyboardHID(macroKeyboardDescriptor, sizeof(macroKeyboardDescriptor)); + struct MultimediaKeyReport { uint8_t reportId; @@ -369,6 +405,8 @@ MultimediaKeyReport multimediaKeyReport = {HID_ID_MULTIMEDIA, 0}; // Global variables KeyReport keyReport; KeyReport prevkeyReport; +KeyReport macroKeyReport; +KeyReport macroPrevkeyReport; uint32_t handshakeTimer = 0; Macro macros[MACRO_SLOTS]; // 5 macro slots MacroPlayStatus macroPlayStatus[MACRO_SLOTS]; @@ -626,6 +664,9 @@ void setup() memset(&keyReport, 0x00, sizeof(KeyReport)); memset(&prevkeyReport, 0xFF, sizeof(KeyReport)); + memset(¯oKeyReport, 0x00, sizeof(KeyReport)); + memset(¯oPrevkeyReport, 0xFF, sizeof(KeyReport)); + #if ENABLE_JOYSTICKS HID().AppendDescriptor(&joystick1HID); HID().AppendDescriptor(&joystick2HID); @@ -641,7 +682,7 @@ void setup() #if ENABLE_MULTIMEDIA_KEYS HID().AppendDescriptor(&multimediaHID); #endif - + HID().AppendDescriptor(¯oKeyboardHID); // Initialize Keyboard (Port B) DDRB &= ~(BITMASK_A500CLK | BITMASK_A500SP | BITMASK_A500RES); // Set pins as INPUT } @@ -781,12 +822,37 @@ void sendReport() } } +void resetReportMacro() +{ + macroKeyReport.modifiers = 0; + for (uint8_t i = 0; i < 6; i++) + { + macroKeyReport.keys[i] = 0; + } +} + +void sendReportMacro() +{ + if (memcmp(¯oKeyReport, ¯oPrevkeyReport, sizeof(KeyReport)) != 0) + { + HID().SendReport(HID_ID_MACROVKEYS, ¯oKeyReport, sizeof(KeyReport)); + memcpy(¯oPrevkeyReport, ¯oKeyReport, sizeof(KeyReport)); + } +} + void releaseAll() { resetReport(); sendReport(); } +void releaseAllMacro() +{ + resetReportMacro(); + sendReportMacro(); +} + + void sendMultimediaKey(uint8_t keyBit) { multimediaKeyReport.keys |= keyBit; // Set the key bit @@ -1046,6 +1112,47 @@ void keystroke(uint8_t keyCode, uint8_t modifiers) #endif } +void keyPressMacro(uint8_t keyCode) +{ + uint8_t hidCode = keyTable[keyCode]; + if (isAmigaModifierKey(keyCode)) + { + macroKeyReport.modifiers |= hidCode; // Modifier key + } + else + { + for (uint8_t i = 0; i < 6; i++) + { + if (macroKeyReport.keys[i] == 0) + { + macroKeyReport.keys[i] = hidCode; + break; + } + } + } + sendReportMacro(); +} + +void keyReleaseMacro(uint8_t keyCode) +{ + uint8_t hidCode = keyTable[keyCode]; + if (isAmigaModifierKey(keyCode)) + { + macroKeyReport.modifiers &= ~hidCode; // Modifier key + } + else + { + for (uint8_t i = 0; i < 6; i++) + { + if (macroKeyReport.keys[i] == hidCode) + { + macroKeyReport.keys[i] = 0; + } + } + } + sendReportMacro(); +} + void multimediaKeystroke(uint8_t keyCode) { sendMultimediaKey(keyCode); @@ -1105,7 +1212,6 @@ void resetMacros() #endif noInterrupts(); // Disable interrupts to enter critical section stopAllMacros(); - releaseAll(); cleanMacros(); saveMacrosToEEPROM(); interrupts(); // Enable interrupts to exit critical section @@ -1143,6 +1249,7 @@ void playMacroSlot(uint8_t slot) macroPlayStatus[slot].loop = false; macroPlayStatus[slot].macroIndex = 0; macroPlayStatus[slot].playStartTime = 0; + releaseAllMacro(); } interrupts(); // Enable interrupts to exit critical section } @@ -1186,7 +1293,7 @@ void stopAllMacros() macroPlayStatus[i].playStartTime = 0; } } - releaseAll(); + releaseAllMacro(); } void playMacro() @@ -1207,22 +1314,20 @@ void playMacro() // Check if the macro is currently playing if (macroPlayStatus[macro_slot].playing) { - if (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length) + // Process Key Events if delay has passed + while (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length && + currentTime - macroPlayStatus[macro_slot].playStartTime >= macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay) { - // Process Key Event if delay has passed - if (currentTime - macroPlayStatus[macro_slot].playStartTime >= macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay) + if (macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].isPressed) + { + keyPressMacro(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); + } + else { - if (macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].isPressed) - { - keyPress(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); - } - else - { - keyRelease(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); - } - // Move to the next report in the macro - macroPlayStatus[macro_slot].macroIndex++; + keyReleaseMacro(macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].keyCode); } + // Move to the next report in the macro + macroPlayStatus[macro_slot].macroIndex++; } // Check if the macro has completed @@ -1278,7 +1383,7 @@ void record_key(uint8_t keycode, bool isPressed) uint8_t macroSlotFromKeyCode(uint8_t keyCode) { - uint8_t slot = keyCode - 0x55; + uint8_t slot = keyCode - AMIGA_KEY_F6; if (slot >= MACRO_SLOTS) { slot = MACRO_SLOTS - 1; // Ensure it does not exceed the maximum slots From fc1c90f453c47d4c453964b55b41416b4a7c0c59 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Wed, 27 Nov 2024 11:20:44 +0100 Subject: [PATCH 07/19] Fix debug output for macro recording and enhance logging details --- Amiga500-USB-Keyboard-Leonardo.ino | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 9e853a1..7e07ac9 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -909,7 +909,7 @@ void processKeyCode() interrupts(); // Enable interrupts to exit critical section #if DEBUG_MODE Serial.print("Recording slot selected: "); - Serial.println(currentMacroSlot, HEX); + Serial.println(recordingMacroSlot, HEX); #endif } return; @@ -1194,6 +1194,18 @@ void stopRecording() macros[recordingMacroSlot].keyEvents[i].delay -= firstDelay; } functionMode = false; + #if DEBUG_MODE + Serial.println("Recording of macro stopped:"); + for (int i = 0; i < macros[recordingMacroSlot].length; i++) + { + Serial.print("-EVENT: "); + Serial.print(macros[recordingMacroSlot].keyEvents[i].isPressed); + Serial.print(" "); + Serial.print(macros[recordingMacroSlot].keyEvents[i].keyCode, HEX); + Serial.print(" "); + Serial.println(macros[recordingMacroSlot].keyEvents[i].delay); + } + #endif // Save macros to EEPROM saveMacrosToEEPROM(); interrupts(); // Enable interrupts to exit critical section @@ -1362,7 +1374,7 @@ void record_key(uint8_t keycode, bool isPressed) noInterrupts(); // Disable interrupts to enter critical section #if DEBUG_MODE Serial.print("Recording key report at index: "); - Serial.println(macroIndex); + Serial.println(recordingMacroIndex); #endif macros[recordingMacroSlot].keyEvents[recordingMacroIndex].isPressed = isPressed; macros[recordingMacroSlot].keyEvents[recordingMacroIndex].keyCode = keycode; From 2d4b988e4c521915a87b976196c5305af9e34945 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Wed, 27 Nov 2024 23:22:23 +0100 Subject: [PATCH 08/19] Enhance macro playback with robot mode and increase concurrent macro capacity --- Amiga500-USB-Keyboard-Leonardo.ino | 23 ++++++++++++++++++++--- README.md | 6 ++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 7e07ac9..1c989a6 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -36,8 +36,8 @@ // macros and the keyboard will still work if enough sram // but macros will not be persistent anymore // because the macro save to eeprom function will gracefully cancel if 1kb is exceeded -#define MACRO_DELAY 20 // ms between macro processing loops non blocking (at every loop all eligible key events in the delay window are processed) -#define CONCURENT_MACROS 2 // How many macros can be played at the same time +#define MACRO_DELAY 2 // ms between macro processing loops non blocking (at every loop all eligible key events in the delay window are processed) +#define CONCURENT_MACROS 3 // How many macros can be played at the same time #define MACRO_SAVE_VERSION 4 // Version of the saved macros #define PROGRAMMATIC_KEYS_RELEASE 2 // delay in ms between press and release on programmatic keys (sent keystrokes) @@ -359,6 +359,8 @@ struct MacroPlayStatus uint32_t playStartTime; }; +bool robotMacroMode=false; + static const uint8_t macroKeyboardDescriptor[] PROGMEM = { // Keyboard @@ -975,6 +977,9 @@ void handleFunctionModeKey() case AMIGA_KEY_DELETE: resetMacros(); break; // Help + Del: Stop any playing macro and reset all macros including eeprom + case AMIGA_KEY_R: + robotMacroMode = !robotMacroMode; + break; // Help + R: Toggle robot macro mode case AMIGA_KEY_F6: case AMIGA_KEY_F7: case AMIGA_KEY_F8: @@ -1326,9 +1331,13 @@ void playMacro() // Check if the macro is currently playing if (macroPlayStatus[macro_slot].playing) { + uint32_t nextDelay = macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay; + if(robotMacroMode){ + nextDelay = macroPlayStatus[macro_slot].macroIndex * 2; //slot times 2ms delay + } // Process Key Events if delay has passed while (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length && - currentTime - macroPlayStatus[macro_slot].playStartTime >= macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay) + currentTime - macroPlayStatus[macro_slot].playStartTime >= nextDelay) { if (macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].isPressed) { @@ -1340,6 +1349,14 @@ void playMacro() } // Move to the next report in the macro macroPlayStatus[macro_slot].macroIndex++; + if(macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length){ + if(robotMacroMode){ + nextDelay = macroPlayStatus[macro_slot].macroIndex * 2; + } + else{ + nextDelay = macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay; + } + } } // Check if the macro has completed diff --git a/README.md b/README.md index 95b64f1..4a5a32a 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ The **Help** key on the Amiga 500 keyboard is used as a modifier in this impleme | **Help + F3** | Record macro | | **Help + F4** | Save macro | | **Help + F5** | Toggle looping macro | +| **Help + R** | Toggle robot macro mode| | **Help + Backspace** | Stop all playing macros | | **Help + Del** | Reset all macros (delete all) | | **Help + F6** | Play macro slot 1 | @@ -167,9 +168,10 @@ There are 5 macro slots available, each capable of storing up to 32 key events. 1. **Play a Macro**: - Press **Help + F6** to **Help + F10** to play the macro stored in the corresponding slot (slots 1 to 5). - - The macro will replay the recorded key presses/releases at original timing. + - The macro will replay the recorded key presses/releases with the original timing by default. + - If Robot Macro Mode is on (toggle with **HELP + R**), the macro is played with 2ms intervals between key events instead of the original timings. In this mode, a macro with 32 key events will play one cycle in approximately 64 ms. -2. **Activate Looping** (BETA): +2. **Activate Looping**: - Press **Help + F5** to toggle looping mode for macros. - When looping is active: - A macro will repeat continuously once started. From ffa608a30b80a43ace6dcfdf1ea446809d616b9f Mon Sep 17 00:00:00 2001 From: Luka Void Date: Thu, 28 Nov 2024 14:51:19 +0100 Subject: [PATCH 09/19] - Optimizations - Minor bugfixes macro recording -Update README to include responsiveness metrics and clarify Robot Macro Mode behavior --- Amiga500-USB-Keyboard-Leonardo.ino | 211 +++++++++++++++-------------- README.md | 14 +- 2 files changed, 120 insertions(+), 105 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 1c989a6..a15cdc5 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -36,8 +36,9 @@ // macros and the keyboard will still work if enough sram // but macros will not be persistent anymore // because the macro save to eeprom function will gracefully cancel if 1kb is exceeded -#define MACRO_DELAY 2 // ms between macro processing loops non blocking (at every loop all eligible key events in the delay window are processed) -#define CONCURENT_MACROS 3 // How many macros can be played at the same time +#define MACRO_DELAY 20 // ms between macro processing loops non blocking (at every loop all eligible key events in the delay window are processed) +#define CONCURENT_MACROS 2 // How many macros can be played at the same time +#define MACRO_ROBOT_MODE_DEFAULT false // false = normal mode (real recorded delays between key events), true = robot mode (minimal regular delays between key events) #define MACRO_SAVE_VERSION 4 // Version of the saved macros #define PROGRAMMATIC_KEYS_RELEASE 2 // delay in ms between press and release on programmatic keys (sent keystrokes) @@ -57,6 +58,9 @@ #define BITMASK_JOY2 0b11110011 // IO A0..A5 #endif +#define MIN_HANDSHAKE_WAIT_TIME 700 //microsecconds: experimentally determined going lower that 600 can cause issues and noise, + //resulting in corrupted keypress. Added 100 as safety margin. This work stable. + // Preprocessor flag to enable or disable debug mode // Debug mode provides some console output. #define DEBUG_MODE 0 @@ -315,6 +319,14 @@ const uint8_t keyTable[AMIGA_KEY_COUNT] = { 0x10 // 0x67: AMIGA_KEY_AMIGA_RIGHT -> HID KEY_MODIFIER_RIGHT_CONTROL }; +const uint8_t specialKeys[] = { + AMIGA_KEY_HELP, + AMIGA_KEY_CAPS_LOCK, + AMIGA_KEY_NUMPAD_NUMLOCK_LPAREN, + AMIGA_KEY_NUMPAD_SCRLOCK_RPAREN, + // Add other special keys here +}; + enum MultimediaKey { MMKEY_NEXT_TRACK = 1 << 0, // 0x01 @@ -359,7 +371,7 @@ struct MacroPlayStatus uint32_t playStartTime; }; -bool robotMacroMode=false; +bool robotMacroMode=MACRO_ROBOT_MODE_DEFAULT; static const uint8_t macroKeyboardDescriptor[] PROGMEM = { @@ -409,14 +421,14 @@ KeyReport keyReport; KeyReport prevkeyReport; KeyReport macroKeyReport; KeyReport macroPrevkeyReport; -uint32_t handshakeTimer = 0; -Macro macros[MACRO_SLOTS]; // 5 macro slots -MacroPlayStatus macroPlayStatus[MACRO_SLOTS]; + bool recording = false; bool recordingSlot = false; bool macro_looping = false; uint8_t recordingMacroSlot = 0; uint8_t recordingMacroIndex = 0; +Macro macros[MACRO_SLOTS]; // 5 macro slots +MacroPlayStatus macroPlayStatus[MACRO_SLOTS]; #if ENABLE_JOYSTICKS // Joystick states @@ -659,9 +671,7 @@ void setup() } // Wait for Serial to be ready Serial.println("Debug mode enabled"); #endif - noInterrupts(); // Disable interrupts to enter critical section loadMacrosFromEEPROM(); - interrupts(); // Enable interrupts to exit critical section memset(&keyReport, 0x00, sizeof(KeyReport)); memset(&prevkeyReport, 0xFF, sizeof(KeyReport)); @@ -723,6 +733,7 @@ void handleJoystick2() void handleKeyboard() { + static uint32_t handshakeTimer = 0; uint8_t pinB = PINB; if (((pinB & BITMASK_A500RES) == 0) && keyboardState != WAIT_RES) @@ -764,9 +775,9 @@ void handleKeyboard() { DDRB |= BITMASK_A500SP; // Set SP pin as OUTPUT PORTB &= ~BITMASK_A500SP; // Set SP pin LOW - handshakeTimer = millis(); + handshakeTimer = micros(); } - else if (millis() - handshakeTimer > 10) + else if (micros() - handshakeTimer > MIN_HANDSHAKE_WAIT_TIME) { handshakeTimer = 0; DDRB &= ~BITMASK_A500SP; // Set SP pin as INPUT @@ -821,6 +832,10 @@ void sendReport() { HID().SendReport(HID_ID_KEYBOARD, &keyReport, sizeof(KeyReport)); memcpy(&prevkeyReport, &keyReport, sizeof(KeyReport)); + #if DEBUG_MODE + Serial.println("Sent report ->>> "); + printKeyReport(); + #endif } } @@ -867,87 +882,112 @@ void releaseMultimediaKey(uint8_t keyBit) HID().SendReport(multimediaKeyReport.reportId, &multimediaKeyReport.keys, sizeof(multimediaKeyReport.keys)); } +bool isSpecialKey(uint8_t keyCode) { + for (uint8_t i = 0; i < sizeof(specialKeys) / sizeof(specialKeys[0]); i++) { + if (specialKeys[i] == keyCode) { + return true; + } + } + return false; +} + +uint8_t ignoreNextRelease = 0; + void processKeyCode() { -#if DEBUG_MODE - Serial.print("Processing key code: "); + if (!(currentKeyCode < AMIGA_KEY_COUNT)) + { + return; + } + + if (recording && ignoreNextRelease > 0 && ignoreNextRelease == currentKeyCode && !isKeyDown) + { + ignoreNextRelease = 0; + return; + } + + #if DEBUG_MODE + if (isKeyDown) + { + Serial.println(" "); + Serial.print("Key Press: "); + } + else + { + Serial.print("Key Release: "); + } Serial.println(currentKeyCode, HEX); -#endif + #endif + if (currentKeyCode == AMIGA_KEY_HELP) { // 'Help' key toggles function mode functionMode = isKeyDown; return; } - else if (currentKeyCode == AMIGA_KEY_CAPS_LOCK) + + if (isKeyDown && currentKeyCode == AMIGA_KEY_CAPS_LOCK) { // CapsLock key keystroke(0x39, 0x00); return; } - else + + if (isKeyDown && currentKeyCode == AMIGA_KEY_NUMPAD_NUMLOCK_LPAREN) { - if (isKeyDown) - { - // Key down message received - if (functionMode) - { - // Special function with 'Help' key - handleFunctionModeKey(); - return; - } - else - { + keystroke(0x53, 0); // NumLock + return; + } + if (isKeyDown && currentKeyCode == AMIGA_KEY_NUMPAD_SCRLOCK_RPAREN) + { + keystroke(0x47, 0); // ScrollLock + return; + } - if (recording && !recordingSlot) - { - if (currentKeyCode >= AMIGA_KEY_F6 && currentKeyCode <= AMIGA_KEY_F10) - { - noInterrupts(); // Disable interrupts to enter critical section - recordingMacroSlot = macroSlotFromKeyCode(currentKeyCode); - memset(¯os[recordingMacroSlot], 0, sizeof(macros[recordingMacroSlot])); // Clear macro slot - recordingMacroIndex = 0; - recordingSlot = true; - interrupts(); // Enable interrupts to exit critical section -#if DEBUG_MODE - Serial.print("Recording slot selected: "); - Serial.println(recordingMacroSlot, HEX); -#endif - } - return; - } + // Special function with 'Help' key + if (isKeyDown && functionMode) + { + handleFunctionModeKey(); + return; + } - if (currentKeyCode == AMIGA_KEY_NUMPAD_NUMLOCK_LPAREN) - { - keystroke(0x53, 0); // NumLock - } - else if (currentKeyCode == AMIGA_KEY_NUMPAD_SCRLOCK_RPAREN) - { - keystroke(0x47, 0); // ScrollLock - } - else if (currentKeyCode < AMIGA_KEY_COUNT) - { - keyPress(currentKeyCode); - } - } - } - else + if (isKeyDown && recording && !recordingSlot) + { + if (currentKeyCode >= AMIGA_KEY_F6 && currentKeyCode <= AMIGA_KEY_F10) { - // Key release message received - if (currentKeyCode < AMIGA_KEY_COUNT) - { - keyRelease(currentKeyCode); - } + recordingMacroSlot = macroSlotFromKeyCode(currentKeyCode); + memset(¯os[recordingMacroSlot], 0, sizeof(macros[recordingMacroSlot])); // Clear macro slot + recordingMacroIndex = 0; + recordingSlot = true; + #if DEBUG_MODE + Serial.print("Recording slot selected: "); + Serial.println(recordingMacroSlot, HEX); + #endif } + ignoreNextRelease = currentKeyCode; + return; + } + + if (isKeyDown) + { + if (!isSpecialKey(currentKeyCode)) + keyPress(currentKeyCode); + } + else + { + // Key release message received + if (!isSpecialKey(currentKeyCode)) + keyRelease(currentKeyCode); } } void handleFunctionModeKey() { -#if DEBUG_MODE - Serial.print("Handling function mode key: "); - Serial.println(currentKeyCode, HEX); -#endif + #if DEBUG_MODE + Serial.print("Handling function mode key: "); + Serial.println(currentKeyCode, HEX); + #endif + ignoreNextRelease = currentKeyCode; switch (currentKeyCode) { case AMIGA_KEY_F1: @@ -1035,10 +1075,6 @@ bool isAmigaModifierKey(uint8_t keyCode) void keyPress(uint8_t keyCode) { record_key(keyCode, true); // if macro recording on, record key -#if DEBUG_MODE - Serial.print("Key press: "); - Serial.println(keyCode, HEX); -#endif uint8_t hidCode = keyTable[keyCode]; if (isAmigaModifierKey(keyCode)) { @@ -1056,18 +1092,11 @@ void keyPress(uint8_t keyCode) } } sendReport(); -#if DEBUG_MODE - printKeyReport(); -#endif } void keyRelease(uint8_t keyCode) { record_key(keyCode, false); // if macro recording on, record key -#if DEBUG_MODE - Serial.print("Key release: "); - Serial.println(keyCode, HEX); -#endif uint8_t hidCode = keyTable[keyCode]; if (isAmigaModifierKey(keyCode)) { @@ -1084,19 +1113,10 @@ void keyRelease(uint8_t keyCode) } } sendReport(); -#if DEBUG_MODE - printKeyReport(); -#endif } void keystroke(uint8_t keyCode, uint8_t modifiers) { -#if DEBUG_MODE - Serial.print("Keystroke: "); - Serial.print(keyCode, HEX); - Serial.print(" with modifiers: "); - Serial.println(modifiers, HEX); -#endif uint8_t originalModifiers = keyReport.modifiers; for (uint8_t i = 0; i < 6; i++) { @@ -1172,13 +1192,11 @@ void startRecording() #if DEBUG_MODE Serial.println("Start recording macro"); #endif - noInterrupts(); // Disable interrupts to enter critical section stopAllMacros(); recordingMacroIndex = 0; recordingMacroSlot = 0; recordingSlot = false; recording = true; - interrupts(); // Enable interrupts to exit critical section } } @@ -1189,7 +1207,6 @@ void stopRecording() #if DEBUG_MODE Serial.println("Stop recording macro"); #endif - noInterrupts(); // Disable interrupts to enter critical section recording = false; recordingSlot = false; // normalize delays in the macro @@ -1213,7 +1230,6 @@ void stopRecording() #endif // Save macros to EEPROM saveMacrosToEEPROM(); - interrupts(); // Enable interrupts to exit critical section } } @@ -1227,16 +1243,13 @@ void resetMacros() #if DEBUG_MODE Serial.println("Reset macros"); #endif - noInterrupts(); // Disable interrupts to enter critical section stopAllMacros(); cleanMacros(); saveMacrosToEEPROM(); - interrupts(); // Enable interrupts to exit critical section } void playMacroSlot(uint8_t slot) { - noInterrupts(); // Disable interrupts to enter critical section if (!recording && !macroPlayStatus[slot].playing && nMacrosPlaying() < CONCURENT_MACROS) { #if DEBUG_MODE @@ -1268,7 +1281,6 @@ void playMacroSlot(uint8_t slot) macroPlayStatus[slot].playStartTime = 0; releaseAllMacro(); } - interrupts(); // Enable interrupts to exit critical section } // Check if any macro is playing @@ -1333,7 +1345,7 @@ void playMacro() { uint32_t nextDelay = macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay; if(robotMacroMode){ - nextDelay = macroPlayStatus[macro_slot].macroIndex * 2; //slot times 2ms delay + nextDelay = macroPlayStatus[macro_slot].macroIndex * MACRO_DELAY; //slot times MACRO_DELAY } // Process Key Events if delay has passed while (macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length && @@ -1351,7 +1363,7 @@ void playMacro() macroPlayStatus[macro_slot].macroIndex++; if(macroPlayStatus[macro_slot].macroIndex < macros[macro_slot].length){ if(robotMacroMode){ - nextDelay = macroPlayStatus[macro_slot].macroIndex * 2; + nextDelay = macroPlayStatus[macro_slot].macroIndex * MACRO_DELAY; } else{ nextDelay = macros[macro_slot].keyEvents[macroPlayStatus[macro_slot].macroIndex].delay; @@ -1388,7 +1400,6 @@ void record_key(uint8_t keycode, bool isPressed) { if (recording && recordingSlot && recordingMacroIndex < MAX_MACRO_LENGTH) { - noInterrupts(); // Disable interrupts to enter critical section #if DEBUG_MODE Serial.print("Recording key report at index: "); Serial.println(recordingMacroIndex); @@ -1403,10 +1414,6 @@ void record_key(uint8_t keycode, bool isPressed) { stopRecording(); } - else - { - interrupts(); // Enable interrupts to exit critical section - } } } diff --git a/README.md b/README.md index 4a5a32a..280b14b 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,16 @@ bit, or 17 kbits/sec. Credit: [AmigaOS Wiki](https://wiki.amigaos.net/wiki/Keyboard_Device) +## Responsiveness + +Average from key press to release in normal use: ~66 ms + +Mostly due to the old membrane keys themselves. Keys have long travel time and need some force in the press. There is lot of variability for this reason. + +Minimum natural key press/release i achieved is: ~20 ms (very light press/tap to reduce key travel time) + +Worst was: ~398 ms (heavy strong key press) + ## Help Key Special Functions The **Help** key on the Amiga 500 keyboard is used as a modifier in this implementation, enabling additional functions when combined with other keys. Below are the available combinations and their corresponding functions. @@ -169,7 +179,7 @@ There are 5 macro slots available, each capable of storing up to 32 key events. 1. **Play a Macro**: - Press **Help + F6** to **Help + F10** to play the macro stored in the corresponding slot (slots 1 to 5). - The macro will replay the recorded key presses/releases with the original timing by default. - - If Robot Macro Mode is on (toggle with **HELP + R**), the macro is played with 2ms intervals between key events instead of the original timings. In this mode, a macro with 32 key events will play one cycle in approximately 64 ms. + - If Robot Macro Mode is on (toggle with **HELP + R**), the macro is played with minimal interval between key events. 2. **Activate Looping**: - Press **Help + F5** to toggle looping mode for macros. @@ -398,5 +408,3 @@ The **Arduino IDE** provides a graphical interface for writing, compiling, and u ## TODO - [ ] Add an optional Piezo Buzzer to the Leonardo for audio feedback, providing better user experience during macro recording. -- [ ] Multiple layouts. -- [ ] Remap any key on the fly From ec1dd57e265cbedea6a2771a8ff62ee4622a589f Mon Sep 17 00:00:00 2001 From: Luka Void Date: Thu, 28 Nov 2024 15:19:25 +0100 Subject: [PATCH 10/19] Add circular buffer for live key events and refactor key processing logic --- Amiga500-USB-Keyboard-Leonardo.ino | 76 ++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index a15cdc5..47d3262 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -16,6 +16,7 @@ #include #include +#include // Preprocessor flag to enable or disable multimedia keys (Consumer device) // Multimedia keys are mapped to the Amiga 500 keyboard and can be used to control media playback. @@ -660,6 +661,13 @@ bool loadMacrosFromEEPROM() return true; } #endif +struct lKeyEvent //live key event +{ + uint8_t keyCode; + bool isPressed; +}; + +CircularBuffer keysEvents; void setup() { @@ -706,6 +714,12 @@ void loop() handleJoystick2(); #endif handleKeyboard(); + // Process key events + while (!keysEvents.isEmpty()) + { + lKeyEvent event=keysEvents.shift(); // Remove the oldest element from the buffer + processKeyCode(event.keyCode, event.isPressed); + } playMacro(); } @@ -731,6 +745,16 @@ void handleJoystick2() } #endif +// Function to add key event to the buffer +void addKeyEventToBuffer(uint8_t keyCode, bool isPressed) +{ + lKeyEvent event; + event.keyCode = keyCode; + event.isPressed = isPressed; + keysEvents.push(event); // Add the event to the buffer +} + +// Function to handle keyboard events void handleKeyboard() { static uint32_t handshakeTimer = 0; @@ -802,7 +826,7 @@ void handleKeyboard() isKeyDown = ((pinB & BITMASK_A500SP) != 0); // true if key down interrupts(); keyboardState = HANDSHAKE; - processKeyCode(); + addKeyEventToBuffer(currentKeyCode, isKeyDown); } } } @@ -893,21 +917,21 @@ bool isSpecialKey(uint8_t keyCode) { uint8_t ignoreNextRelease = 0; -void processKeyCode() +void processKeyCode(uint8_t keyCode, bool isPressed) { - if (!(currentKeyCode < AMIGA_KEY_COUNT)) + if (!(keyCode < AMIGA_KEY_COUNT)) { return; } - if (recording && ignoreNextRelease > 0 && ignoreNextRelease == currentKeyCode && !isKeyDown) + if (recording && ignoreNextRelease > 0 && ignoreNextRelease == keyCode && !isPressed) { ignoreNextRelease = 0; return; } #if DEBUG_MODE - if (isKeyDown) + if (isPressed) { Serial.println(" "); Serial.print("Key Press: "); @@ -916,46 +940,46 @@ void processKeyCode() { Serial.print("Key Release: "); } - Serial.println(currentKeyCode, HEX); + Serial.println(keyCode, HEX); #endif - if (currentKeyCode == AMIGA_KEY_HELP) + if (keyCode == AMIGA_KEY_HELP) { // 'Help' key toggles function mode - functionMode = isKeyDown; + functionMode = isPressed; return; } - if (isKeyDown && currentKeyCode == AMIGA_KEY_CAPS_LOCK) + if (isPressed && keyCode == AMIGA_KEY_CAPS_LOCK) { // CapsLock key keystroke(0x39, 0x00); return; } - if (isKeyDown && currentKeyCode == AMIGA_KEY_NUMPAD_NUMLOCK_LPAREN) + if (isPressed && keyCode == AMIGA_KEY_NUMPAD_NUMLOCK_LPAREN) { keystroke(0x53, 0); // NumLock return; } - if (isKeyDown && currentKeyCode == AMIGA_KEY_NUMPAD_SCRLOCK_RPAREN) + if (isPressed && keyCode == AMIGA_KEY_NUMPAD_SCRLOCK_RPAREN) { keystroke(0x47, 0); // ScrollLock return; } // Special function with 'Help' key - if (isKeyDown && functionMode) + if (isPressed && functionMode) { - handleFunctionModeKey(); + handleFunctionModeKey(keyCode); return; } - if (isKeyDown && recording && !recordingSlot) + if (isPressed && recording && !recordingSlot) { - if (currentKeyCode >= AMIGA_KEY_F6 && currentKeyCode <= AMIGA_KEY_F10) + if (keyCode >= AMIGA_KEY_F6 && keyCode <= AMIGA_KEY_F10) { - recordingMacroSlot = macroSlotFromKeyCode(currentKeyCode); + recordingMacroSlot = macroSlotFromKeyCode(keyCode); memset(¯os[recordingMacroSlot], 0, sizeof(macros[recordingMacroSlot])); // Clear macro slot recordingMacroIndex = 0; recordingSlot = true; @@ -964,31 +988,31 @@ void processKeyCode() Serial.println(recordingMacroSlot, HEX); #endif } - ignoreNextRelease = currentKeyCode; + ignoreNextRelease = keyCode; return; } - if (isKeyDown) + if (isPressed) { - if (!isSpecialKey(currentKeyCode)) - keyPress(currentKeyCode); + if (!isSpecialKey(keyCode)) + keyPress(keyCode); } else { // Key release message received - if (!isSpecialKey(currentKeyCode)) - keyRelease(currentKeyCode); + if (!isSpecialKey(keyCode)) + keyRelease(keyCode); } } -void handleFunctionModeKey() +void handleFunctionModeKey(uint8_t keyCode) { #if DEBUG_MODE Serial.print("Handling function mode key: "); - Serial.println(currentKeyCode, HEX); + Serial.println(keyCode, HEX); #endif - ignoreNextRelease = currentKeyCode; - switch (currentKeyCode) + ignoreNextRelease = keyCode; + switch (keyCode) { case AMIGA_KEY_F1: keystroke(0x44, 0); From 1d7afcf879e44980b40e42e136152f97d186051b Mon Sep 17 00:00:00 2001 From: Luka Void Date: Thu, 28 Nov 2024 15:21:12 +0100 Subject: [PATCH 11/19] Add CircularBuffer library to makefile for enhanced key event handling --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index c60d49b..a99968e 100644 --- a/makefile +++ b/makefile @@ -4,7 +4,7 @@ BOARD_FQBN = arduino:avr:leonardo # Fully Qualified Board Name for Arduino Leonardo SKETCH = Amiga500-USB-Keyboard-Leonardo.ino # Path to sketch file BUILD_DIR = build # Directory for build artifacts -LIBRARIES = Keyboard # List of required libraries +LIBRARIES = Keyboard CircularBuffer # List of required libraries CORE = arduino:avr # Required core # Commands From 2a2901b2b79e5c8da8e0d3222df97c929178b585 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Thu, 28 Nov 2024 15:28:15 +0100 Subject: [PATCH 12/19] Update README to include CircularBuffer library installation instructions --- README.md | 44 +++----------------------------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 280b14b..aa4c96b 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ For a demonstration of the original Amiga 500 keyboard in action, visit the [Ami |-----------------|----------|-----------------|----------| | Keyboard | 1.0.6+ | arduino:avr | 1.8.6+ | | HID | 1.0+ | | | +| CircularBuffer | 1.4.0+ | | | | EEPROM | 2.0+ | | | ## Wiring Information @@ -53,47 +54,6 @@ To connect the Amiga 500 keyboard to the Arduino Leonardo, refer to the followin - **LED2 (Purple, Pin 8)**: Not connected. Amiga keyboard specs: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0173.html -``` -The keyboard transmits 8-bit data words serially to the main unit. Before -the transmission starts, both KCLK and KDAT are high. The keyboard starts -the transmission by putting out the first data bit (on KDAT), followed by -a pulse on KCLK (low then high); then it puts out the second data bit and -pulses KCLK until all eight data bits have been sent. After the end of -the last KCLK pulse, the keyboard pulls KDAT high again. - -When the computer has received the eighth bit, it must pulse KDAT low for -at least 1 (one) microsecond, as a handshake signal to the keyboard. The -handshake detection on the keyboard end will typically use a hardware -latch. The keyboard must be able to detect pulses greater than or equal -to 1 microsecond. Software MUST pulse the line low for 85 microseconds to -ensure compatibility with all keyboard models. - -All codes transmitted to the computer are rotated one bit before -transmission. The transmitted order is therefore 6-5-4-3-2-1-0-7. The -reason for this is to transmit the up/down flag last, in order to cause -a key-up code to be transmitted in case the keyboard is forced to restore - lost sync (explained in more detail below). - -The KDAT line is active low; that is, a high level (+5V) is interpreted as -0, and a low level (0V) is interpreted as 1. - - _____ ___ ___ ___ ___ ___ ___ ___ _________ - KCLK \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ - ___________________________________________________________ - KDAT \_____X_____X_____X_____X_____X_____X_____X_____/ - (6) (5) (4) (3) (2) (1) (0) (7) - - First Last - sent sent - -The keyboard processor sets the KDAT line about 20 microseconds before it -pulls KCLK low. KCLK stays low for about 20 microseconds, then goes high -again. The processor waits another 20 microseconds before changing KDAT. - -Therefore, the bit rate during transmission is about 60 microseconds per -bit, or 17 kbits/sec. -``` - --- ## Amiga 500 Keyboard Layout @@ -305,6 +265,7 @@ This method is ideal for users comfortable with the command line. **Arduino CLI* arduino-cli core update-index arduino-cli core install arduino:avr arduino-cli lib install "Keyboard" + arduino-cli lib install "CircularBuffer" ``` 3. **Connect Arduino Leonardo via USB** and identify the port: @@ -342,6 +303,7 @@ The **Arduino IDE** provides a graphical interface for writing, compiling, and u 2. **Install the Keyboard Library**: - In the Arduino IDE, go to Tools > Manage Libraries.... - In the Library Manager, search for "Keyboard" and install the Keyboard library. + - In the Library Manager, search for "CircularBuffer" and install the CircularBuffer library. 3. **Open Your Sketch**: - Launch the Arduino IDE. From 752d3dc4ccddce559e10147f0caecab5c0b70ff2 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Thu, 28 Nov 2024 15:37:17 +0100 Subject: [PATCH 13/19] Refactor key processing logic to remove unnecessary recording check --- Amiga500-USB-Keyboard-Leonardo.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 47d3262..d697768 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -924,7 +924,7 @@ void processKeyCode(uint8_t keyCode, bool isPressed) return; } - if (recording && ignoreNextRelease > 0 && ignoreNextRelease == keyCode && !isPressed) + if (ignoreNextRelease > 0 && ignoreNextRelease == keyCode && !isPressed) { ignoreNextRelease = 0; return; From 0cb348a1cc64fad376f9a9fabf9d81a77d60d63d Mon Sep 17 00:00:00 2001 From: Luka Void Date: Thu, 28 Nov 2024 22:38:00 +0100 Subject: [PATCH 14/19] refactor key event processing --- Amiga500-USB-Keyboard-Leonardo.ino | 63 ++++++++++++++++-------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index d697768..40121c5 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -42,6 +42,7 @@ #define MACRO_ROBOT_MODE_DEFAULT false // false = normal mode (real recorded delays between key events), true = robot mode (minimal regular delays between key events) #define MACRO_SAVE_VERSION 4 // Version of the saved macros +#define LIVE_KEY_EVENT_BUFFER_SIZE 50 // Size of the buffer for live key events #define PROGRAMMATIC_KEYS_RELEASE 2 // delay in ms between press and release on programmatic keys (sent keystrokes) #if PERSISTENT_MACRO @@ -59,8 +60,8 @@ #define BITMASK_JOY2 0b11110011 // IO A0..A5 #endif -#define MIN_HANDSHAKE_WAIT_TIME 700 //microsecconds: experimentally determined going lower that 600 can cause issues and noise, - //resulting in corrupted keypress. Added 100 as safety margin. This work stable. +#define MIN_HANDSHAKE_WAIT_TIME 700 //microsecconds: experimentally determined going lower that 600 can cause issues and noise, + //resulting in corrupted keypress. Added 100 as safety margin. This work stable. // Preprocessor flag to enable or disable debug mode // Debug mode provides some console output. @@ -350,6 +351,14 @@ enum KeyboardState WAIT_RES }; +struct lKeyEvent //live key event +{ + uint8_t keyCode; + bool isPressed; +}; + +CircularBuffer keysEvents; + // Macro structures struct MacroKeyEvent { @@ -522,12 +531,8 @@ const uint8_t multimediaDescriptor[] PROGMEM = { HIDSubDescriptor multimediaHID(multimediaDescriptor, sizeof(multimediaDescriptor)); #endif -// Keyboard state machine variables -KeyboardState keyboardState = SYNCH_HI; -uint8_t bitIndex = 0; -uint8_t currentKeyCode = 0; +// Keyboard state variables bool functionMode = false; // Indicates if 'Help' key is active -bool isKeyDown = false; #if PERSISTENT_MACRO // Function to calculate macros checksum @@ -661,13 +666,7 @@ bool loadMacrosFromEEPROM() return true; } #endif -struct lKeyEvent //live key event -{ - uint8_t keyCode; - bool isPressed; -}; -CircularBuffer keysEvents; void setup() { @@ -714,12 +713,7 @@ void loop() handleJoystick2(); #endif handleKeyboard(); - // Process key events - while (!keysEvents.isEmpty()) - { - lKeyEvent event=keysEvents.shift(); // Remove the oldest element from the buffer - processKeyCode(event.keyCode, event.isPressed); - } + processKeyEvents(); playMacro(); } @@ -745,6 +739,15 @@ void handleJoystick2() } #endif +void processKeyEvents() +{ + while (!keysEvents.isEmpty()) + { + lKeyEvent keyEvent = keysEvents.shift(); // Remove the oldest element from the buffer + processKeyCode(keyEvent.keyCode, keyEvent.isPressed); + } +} + // Function to add key event to the buffer void addKeyEventToBuffer(uint8_t keyCode, bool isPressed) { @@ -757,10 +760,12 @@ void addKeyEventToBuffer(uint8_t keyCode, bool isPressed) // Function to handle keyboard events void handleKeyboard() { + static KeyboardState keyboardState = SYNCH_HI; static uint32_t handshakeTimer = 0; - uint8_t pinB = PINB; + static uint8_t currentKeyCode = 0; + static uint8_t bitIndex = 0; - if (((pinB & BITMASK_A500RES) == 0) && keyboardState != WAIT_RES) + if (((PINB & BITMASK_A500RES) == 0) && keyboardState != WAIT_RES) { // Reset detected interrupts(); @@ -771,7 +776,7 @@ void handleKeyboard() else if (keyboardState == WAIT_RES) { // Waiting for reset end - if ((pinB & BITMASK_A500RES) != 0) + if ((PINB & BITMASK_A500RES) != 0) { keyboardState = SYNCH_HI; } @@ -779,7 +784,7 @@ void handleKeyboard() else if (keyboardState == SYNCH_HI) { // Sync Pulse High - if ((pinB & BITMASK_A500CLK) == 0) + if ((PINB & BITMASK_A500CLK) == 0) { keyboardState = SYNCH_LO; } @@ -787,7 +792,7 @@ void handleKeyboard() else if (keyboardState == SYNCH_LO) { // Sync Pulse Low - if ((pinB & BITMASK_A500CLK) != 0) + if ((PINB & BITMASK_A500CLK) != 0) { keyboardState = HANDSHAKE; } @@ -813,17 +818,17 @@ void handleKeyboard() else if (keyboardState == READ) { // Read key message (8 bits) - if ((pinB & BITMASK_A500CLK) != 0) + if ((PINB & BITMASK_A500CLK) != 0) { if (bitIndex--) { - currentKeyCode |= ((pinB & BITMASK_A500SP) == 0) << bitIndex; // Accumulate bits + currentKeyCode |= ((PINB & BITMASK_A500SP) == 0) << bitIndex; // Accumulate bits keyboardState = WAIT_LO; } else { // Read last bit (key down/up) - isKeyDown = ((pinB & BITMASK_A500SP) != 0); // true if key down + bool isKeyDown = ((PINB & BITMASK_A500SP) != 0); // true if key down interrupts(); keyboardState = HANDSHAKE; addKeyEventToBuffer(currentKeyCode, isKeyDown); @@ -833,7 +838,7 @@ void handleKeyboard() else if (keyboardState == WAIT_LO) { // Waiting for the next bit - if ((pinB & BITMASK_A500CLK) == 0) + if ((PINB & BITMASK_A500CLK) == 0) { noInterrupts(); keyboardState = READ; @@ -1049,7 +1054,7 @@ void handleFunctionModeKey(uint8_t keyCode) case AMIGA_KEY_F8: case AMIGA_KEY_F9: case AMIGA_KEY_F10: - playMacroSlot(macroSlotFromKeyCode(currentKeyCode)); + playMacroSlot(macroSlotFromKeyCode(keyCode)); break; // Help + F6 to F10: Play macro in corresponding slot #if ENABLE_MULTIMEDIA_KEYS case AMIGA_KEY_ARROW_UP: // HELP + Arrow Up: Volume Up From fabf8266521872cfb1ceb4f4321323670d6cd95b Mon Sep 17 00:00:00 2001 From: Luka Void Date: Sat, 30 Nov 2024 01:00:44 +0100 Subject: [PATCH 15/19] Removed the CircularBuffer implementation and the associated key event processing logic as it was unnecessary. These changes were initially introduced to explore the possibility of transitioning handleKeyboard to interrupt-based processing. However, this would require rewiring the keyboard and conducting additional experiments. For now, reverting to the existing polling approach. --- Amiga500-USB-Keyboard-Leonardo.ino | 31 +----------------------------- README.md | 3 --- makefile | 4 ++-- 3 files changed, 3 insertions(+), 35 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 40121c5..3140f42 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -16,7 +16,6 @@ #include #include -#include // Preprocessor flag to enable or disable multimedia keys (Consumer device) // Multimedia keys are mapped to the Amiga 500 keyboard and can be used to control media playback. @@ -42,7 +41,6 @@ #define MACRO_ROBOT_MODE_DEFAULT false // false = normal mode (real recorded delays between key events), true = robot mode (minimal regular delays between key events) #define MACRO_SAVE_VERSION 4 // Version of the saved macros -#define LIVE_KEY_EVENT_BUFFER_SIZE 50 // Size of the buffer for live key events #define PROGRAMMATIC_KEYS_RELEASE 2 // delay in ms between press and release on programmatic keys (sent keystrokes) #if PERSISTENT_MACRO @@ -351,14 +349,6 @@ enum KeyboardState WAIT_RES }; -struct lKeyEvent //live key event -{ - uint8_t keyCode; - bool isPressed; -}; - -CircularBuffer keysEvents; - // Macro structures struct MacroKeyEvent { @@ -713,7 +703,6 @@ void loop() handleJoystick2(); #endif handleKeyboard(); - processKeyEvents(); playMacro(); } @@ -739,24 +728,6 @@ void handleJoystick2() } #endif -void processKeyEvents() -{ - while (!keysEvents.isEmpty()) - { - lKeyEvent keyEvent = keysEvents.shift(); // Remove the oldest element from the buffer - processKeyCode(keyEvent.keyCode, keyEvent.isPressed); - } -} - -// Function to add key event to the buffer -void addKeyEventToBuffer(uint8_t keyCode, bool isPressed) -{ - lKeyEvent event; - event.keyCode = keyCode; - event.isPressed = isPressed; - keysEvents.push(event); // Add the event to the buffer -} - // Function to handle keyboard events void handleKeyboard() { @@ -831,7 +802,7 @@ void handleKeyboard() bool isKeyDown = ((PINB & BITMASK_A500SP) != 0); // true if key down interrupts(); keyboardState = HANDSHAKE; - addKeyEventToBuffer(currentKeyCode, isKeyDown); + processKeyCode(currentKeyCode, isKeyDown); } } } diff --git a/README.md b/README.md index aa4c96b..5e46728 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ For a demonstration of the original Amiga 500 keyboard in action, visit the [Ami |-----------------|----------|-----------------|----------| | Keyboard | 1.0.6+ | arduino:avr | 1.8.6+ | | HID | 1.0+ | | | -| CircularBuffer | 1.4.0+ | | | | EEPROM | 2.0+ | | | ## Wiring Information @@ -265,7 +264,6 @@ This method is ideal for users comfortable with the command line. **Arduino CLI* arduino-cli core update-index arduino-cli core install arduino:avr arduino-cli lib install "Keyboard" - arduino-cli lib install "CircularBuffer" ``` 3. **Connect Arduino Leonardo via USB** and identify the port: @@ -303,7 +301,6 @@ The **Arduino IDE** provides a graphical interface for writing, compiling, and u 2. **Install the Keyboard Library**: - In the Arduino IDE, go to Tools > Manage Libraries.... - In the Library Manager, search for "Keyboard" and install the Keyboard library. - - In the Library Manager, search for "CircularBuffer" and install the CircularBuffer library. 3. **Open Your Sketch**: - Launch the Arduino IDE. diff --git a/makefile b/makefile index a99968e..9d5a140 100644 --- a/makefile +++ b/makefile @@ -4,13 +4,13 @@ BOARD_FQBN = arduino:avr:leonardo # Fully Qualified Board Name for Arduino Leonardo SKETCH = Amiga500-USB-Keyboard-Leonardo.ino # Path to sketch file BUILD_DIR = build # Directory for build artifacts -LIBRARIES = Keyboard CircularBuffer # List of required libraries +LIBRARIES = Keyboard # List of required libraries CORE = arduino:avr # Required core # Commands ARDUINO_CLI = arduino-cli VERIFY_CMD = $(ARDUINO_CLI) compile --fqbn $(BOARD_FQBN) --warnings more --verify --build-path $(BUILD_DIR) $(SKETCH) -UPLOAD_CMD = $(ARDUINO_CLI) upload --fqbn $(BOARD_FQBN) --input-dir $(BUILD_DIR) --port $(shell $(ARDUINO_CLI) board list | grep -m 1 tty | awk '{print $$1}') +UPLOAD_CMD = $(ARDUINO_CLI) upload --fqbn $(BOARD_FQBN) --verbose --input-dir $(BUILD_DIR) --port $(shell $(ARDUINO_CLI) board list | grep -m 1 tty | awk '{print $$1}') INSTALL_LIBRARIES_CMD = $(ARDUINO_CLI) lib install $(LIBRARIES) UPDATE_CORES_LIBRARIES_CMD = $(ARDUINO_CLI) core update-index && $(ARDUINO_CLI) lib update-index && $(ARDUINO_CLI) core upgrade && $(ARDUINO_CLI) lib upgrade INSTALL_CORE_CMD = $(ARDUINO_CLI) core update-index && $(ARDUINO_CLI) core install $(CORE) From f4b456bd38318aa61040b73e4676bb7530af8fc6 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Sat, 30 Nov 2024 01:12:50 +0100 Subject: [PATCH 16/19] Update MIN_HANDSHAKE_WAIT_TIME to 65 microseconds as per Amiga 500 Technical Reference Manual --- Amiga500-USB-Keyboard-Leonardo.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 3140f42..99a2c40 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -58,8 +58,7 @@ #define BITMASK_JOY2 0b11110011 // IO A0..A5 #endif -#define MIN_HANDSHAKE_WAIT_TIME 700 //microsecconds: experimentally determined going lower that 600 can cause issues and noise, - //resulting in corrupted keypress. Added 100 as safety margin. This work stable. +#define MIN_HANDSHAKE_WAIT_TIME 65 //microsecconds: as specified in the Amiga 500 Technical Reference Manual // Preprocessor flag to enable or disable debug mode // Debug mode provides some console output. From 0e9e9054f9c7c44ed05beb46db8b27a807291e35 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Sat, 30 Nov 2024 01:14:15 +0100 Subject: [PATCH 17/19] Add note to DEBUG_MODE about potential interference with keyboard operation --- Amiga500-USB-Keyboard-Leonardo.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 99a2c40..2b5876a 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -62,6 +62,7 @@ // Preprocessor flag to enable or disable debug mode // Debug mode provides some console output. +// It can interfere with the keyboard operation, so it is recommended to disable it when not needed. #define DEBUG_MODE 0 enum UsedHIDDescriptors From d5c36be8633d2a57f6cd0548744198b8dc272413 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Sat, 30 Nov 2024 01:27:29 +0100 Subject: [PATCH 18/19] - fix capslock - minor fixes misc --- Amiga500-USB-Keyboard-Leonardo.ino | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Amiga500-USB-Keyboard-Leonardo.ino b/Amiga500-USB-Keyboard-Leonardo.ino index 2b5876a..4036c21 100644 --- a/Amiga500-USB-Keyboard-Leonardo.ino +++ b/Amiga500-USB-Keyboard-Leonardo.ino @@ -926,7 +926,7 @@ void processKeyCode(uint8_t keyCode, bool isPressed) return; } - if (isPressed && keyCode == AMIGA_KEY_CAPS_LOCK) + if (keyCode == AMIGA_KEY_CAPS_LOCK) { // CapsLock key keystroke(0x39, 0x00); @@ -951,20 +951,20 @@ void processKeyCode(uint8_t keyCode, bool isPressed) return; } - if (isPressed && recording && !recordingSlot) + if (recording && !recordingSlot) { - if (keyCode >= AMIGA_KEY_F6 && keyCode <= AMIGA_KEY_F10) + if (isPressed && keyCode >= AMIGA_KEY_F6 && keyCode <= AMIGA_KEY_F10) { recordingMacroSlot = macroSlotFromKeyCode(keyCode); memset(¯os[recordingMacroSlot], 0, sizeof(macros[recordingMacroSlot])); // Clear macro slot recordingMacroIndex = 0; recordingSlot = true; + ignoreNextRelease = keyCode; #if DEBUG_MODE Serial.print("Recording slot selected: "); Serial.println(recordingMacroSlot, HEX); #endif } - ignoreNextRelease = keyCode; return; } From f1816b5675f643ab2ae7a03ea3c0f291d7fc6755 Mon Sep 17 00:00:00 2001 From: Luka Void Date: Sat, 30 Nov 2024 01:31:57 +0100 Subject: [PATCH 19/19] Update README.md to improve formatting and add spacing for clarity --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5e46728..87b37e2 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,9 @@ To connect the Amiga 500 keyboard to the Arduino Leonardo, refer to the followin - **LED1 (Blue, Pin 7)**: Connects to **5V** for indicating power. - **LED2 (Purple, Pin 8)**: Not connected. + Amiga keyboard specs: http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0173.html + --- ## Amiga 500 Keyboard Layout