// File recommented by recomment.cpp // on Dec 9 2019 at 10:14:05. // // -*- mode: C; tab-width: 2 ; indent-tabs-mode: nil -*- // // Universal Keyboard Project // ASDF firmware - small, fast, and simple keyboard encoder. // // asdf.c // // This file contains code for: // - the main scan code and key handler routines // - key matrix declarations and keyboard state variables not delegated // elsewhere. // - Key debouncing logic and data stuctures // - dispatch of special functions bound to keys. // // Copyright 2019 David Fenyes // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) any // later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more // details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . #include #include #include "asdf.h" #include "asdf_ascii.h" #include "asdf_physical.h" #include "asdf_virtual.h" #include "asdf_keymaps.h" #include "asdf_repeat.h" #include "asdf_modifiers.h" #include "asdf_buffer.h" #include "asdf_arch.h" #include "asdf_hook.h" // The key scanner keeps track of the last stable (debounced) state of each key // in the matrix, one bit per key, 8 bits per row. static asdf_cols_t last_stable_key_state[ASDF_MAX_ROWS]; // Each key is debounced separately, supporting true N-key rollover, allowing a // new key to be pressed when the previously pressed key is still debouncing. // This requires a debounce counter for each key. This is is a large RAM // footprint, 64 bytes for an 8 x 8 key matrix. It would be possible to only // debounce the most recently pressed key and reduce the RAM usage to a single // debounce counuter. However, this would probably require different handling of // key presses and releases, and special handling for modifier keys, perhaps // including separate debounce logic in the handlers for toggle keys such as // CAPS_LOCK. static uint8_t debounce_counters[ASDF_MAX_ROWS][ASDF_MAX_COLS]; // Stores the last key pressed static asdf_keycode_t last_key; // This is the handle for the char output buffer static asdf_buffer_handle_t asdf_keycode_buffer; // This is the handle for the message output buffer static asdf_buffer_handle_t asdf_message_buffer; // The delay time for strings sent via parallel port. This is needed because // most keyboards have no handshaking, and we need to allow time for various // environments to process the characters. Some, such as basic interpreters, are // rather slow. So we we allow keyboard modules to modifiy this delay. static uint16_t asdf_print_delay_ms; // PROCEDURE: asdf_put_code // INPUTS: (asdf_keycode_t) code: code to be buffered for output // OUTPUTS: none // // DESCRIPTION: Takes a keycode argument and buffers for output. // // SIDE EFFECTS: modifies buffer state. // // NOTES: If buffer is full, silently drop the code. // // SCOPE: public // // COMPLEXITY: 1 // void asdf_put_code(asdf_keycode_t code) { asdf_buffer_put(asdf_keycode_buffer, code); } // PROCEDURE: asdf_putc // INPUTS: (char) c: character to be buffered for output // (FILE *) stream - only used to match prototype. // OUTPUTS: none // // DESCRIPTION: Takes a character generated by the system and buffers for // output. // // SIDE EFFECTS: modifies buffer state. // // NOTES: If buffer is full, silently drop the code. // // SCOPE: public // // COMPLEXITY: 1 // int asdf_putc(char c, FILE *stream) { // for messages, add CR to LF: if ('\n' == c) { asdf_putc('\r', stream); } asdf_buffer_put(asdf_message_buffer, (asdf_keycode_t) c); return (int) c; } // PROCEDURE: asdf_next_code // INPUTS: none // // OUTPUTS: (asdf_keycode_t) returns next value in buffer. If both buffers are // empty, the code ASDF_INVALID_CODE is returned. // // DESCRIPTION: Checks the message buffer, and returns a character // if present. Otherwise, return the next code in the keycode // buffer. // // SIDE EFFECTS: modifies buffer state. // // NOTES: A delay is enforced for system messages, to reduce the risk of dropped // characters with unbuffered polling hosts. No delay is needed for typed // keycodes, as these are generated at human speeds. // // SCOPE: public // // COMPLEXITY: 2 // asdf_keycode_t asdf_next_code(void) { asdf_keycode_t code = asdf_buffer_get(asdf_message_buffer); if (ASDF_INVALID_CODE == code) { code = asdf_buffer_get(asdf_keycode_buffer); } else { // for system message asdf_arch_delay_ms(asdf_print_delay_ms); } return code; } // PROCEDURE: asdf_set_print_delay // INPUTS: (uint8_t) delay_ms // // OUTPUTS: none // // DESCRIPTION: sets the delay to be used by the system print buffer // // SIDE EFFECTS: modifies character delay variable. // // NOTES: A delay is enforced for system messages, to reduce the risk of dropped // characters with unbuffered polling hosts. No delay is needed for typed // keycodes, as these are generated at human speeds. // // SCOPE: public // // COMPLEXITY: 1 // void asdf_set_print_delay(uint8_t delay_ms) { asdf_print_delay_ms = delay_ms; } // PROCEDURE: asdf_lookup_keycode // INPUTS: row, col: specify a row and column in the keyboard matrix // OUTPUTS: returns a keycode // // DESCRIPTION: Given a row and column in the keyboard matrix, returns the // corresponding keycode, depending on the state of the modifier keys. // // SIDE EFFECTS: none // // NOTES: // // SCOPE: private // // COMPLEXITY: 1 // asdf_keycode_t asdf_lookup_keycode(uint8_t row, uint8_t col) { return asdf_keymaps_get_code(row, col, asdf_modifier_index()); } // PROCEDURE: asdf_activate_action // INPUTS: keycode: an action key code // OUTPUTS: none // // DESCRIPTION: This routine is called when a key bound to an action code is // pressed, and maps the action code to a function call, or other action // appropriate for activation of the function. // // SIDE EFFECTS: All the actions may have side effects, depending on the function called. // // SCOPE: private // // NOTES: The switch() could be implemented as an array of function pointers, // essentially a jump table. However, the switch statement will also be // implemented as a jump table, and may be more efficiently implemented as a // code jump table than an array of pointers stored in flash that would require // an additional flash-read operation. Also, the switch jumptable will make more // efficient use of space than a sparse array of function pointers. The // reduction in cyclometric complexity by using an array is a technicality, // since the elements must still be entered as lines of code in some part of the // program. Here, they are arranged in the place they are used. // // COMPLEXITY: 1 (+18 for the switch) // static void asdf_activate_action(action_t keycode) { switch (keycode) { case ACTION_SHIFT: { asdf_modifier_shift_activate(); break; } case ACTION_SHIFTLOCK_ON: { asdf_modifier_shiftlock_on_activate(); break; } case ACTION_SHIFTLOCK_TOGGLE: { asdf_modifier_shiftlock_toggle_activate(); break; } case ACTION_CAPS: { asdf_modifier_capslock_activate(); break; } case ACTION_CTRL: { asdf_modifier_ctrl_activate(); break; } case ACTION_REPEAT: { asdf_repeat_activate(); break; } case ACTION_MAPSEL_0: { asdf_keymaps_map_select_0_set(); break; } case ACTION_MAPSEL_1: { asdf_keymaps_map_select_1_set(); break; } case ACTION_MAPSEL_2: { asdf_keymaps_map_select_2_set(); break; } case ACTION_MAPSEL_3: { asdf_keymaps_map_select_3_set(); break; } case ACTION_STROBE_POLARITY_SELECT: { asdf_arch_set_pos_strobe(); break; } case ACTION_AUTOREPEAT_SELECT: { asdf_repeat_auto_on(); break; } case ACTION_VLED1: { asdf_virtual_activate(VLED1); break; } case ACTION_VLED2: { asdf_virtual_activate(VLED2); break; } case ACTION_VLED3: { asdf_virtual_activate(VLED3); break; } case ACTION_VOUT1: { asdf_virtual_activate(VOUT1); break; } case ACTION_VOUT2: { asdf_virtual_activate(VOUT2); break; } case ACTION_VOUT3: { asdf_virtual_activate(VOUT3); break; } case ACTION_VOUT4: { asdf_virtual_activate(VOUT4); break; } case ACTION_VOUT5: { asdf_virtual_activate(VOUT5); break; } case ACTION_VOUT6: { asdf_virtual_activate(VOUT6); break; } case ACTION_NOTHING: case ACTION_HERE_IS: case ACTION_FN_1: asdf_hook_execute(ASDF_HOOK_USER_1); break; case ACTION_FN_2: asdf_hook_execute(ASDF_HOOK_USER_2); break; case ACTION_FN_3: asdf_hook_execute(ASDF_HOOK_USER_3); break; case ACTION_FN_4: asdf_hook_execute(ASDF_HOOK_USER_4); break; case ACTION_FN_5: asdf_hook_execute(ASDF_HOOK_USER_5); break; case ACTION_FN_6: asdf_hook_execute(ASDF_HOOK_USER_6); break; case ACTION_FN_7: asdf_hook_execute(ASDF_HOOK_USER_7); break; case ACTION_FN_8: asdf_hook_execute(ASDF_HOOK_USER_8); break; case ACTION_FN_9: asdf_hook_execute(ASDF_HOOK_USER_9); break; case ACTION_FN_10: asdf_hook_execute(ASDF_HOOK_USER_10); break; default: break; } } // PROCEDURE: asdf_deactivate_action // INPUTS: keycode: an action key code // OUTPUTS: none // // DESCRIPTION: This routine is called when a key bound to an action code is // released, and maps the action code to a function call, or other action // appropriate for deactivation of the function. // // SIDE EFFECTS: All the actions may have side effects, depending on the function called. // // SCOPE: private // // NOTES: See NOTE for asdf_activate_action(). The Virtual Outputs have // activate() functions, but no deactivate() functions, and are handled by the // default case. // // COMPLEXITY: 1 (+9 for the switch, see note) // static void asdf_deactivate_action(action_t keycode) { switch (keycode) { case ACTION_SHIFT: { asdf_modifier_shift_deactivate(); break; } case ACTION_CTRL: { asdf_modifier_ctrl_deactivate(); break; } case ACTION_REPEAT: { asdf_repeat_deactivate(); break; } case ACTION_MAPSEL_0: { asdf_keymaps_map_select_0_clear(); break; } case ACTION_MAPSEL_1: { asdf_keymaps_map_select_1_clear(); break; } case ACTION_MAPSEL_2: { asdf_keymaps_map_select_2_clear(); break; } case ACTION_MAPSEL_3: { asdf_keymaps_map_select_3_clear(); break; } case ACTION_STROBE_POLARITY_SELECT: { asdf_arch_set_neg_strobe(); break; } case ACTION_AUTOREPEAT_SELECT: { asdf_repeat_auto_off(); break; } case ACTION_NOTHING: default: break; } } // PROCEDURE: asdf_activate_key // INPUTS: keycode - the code for a key that has been pressed. // OUTPUTS: none // // DESCRIPTION: Called when a key has been pressed. If the key is bound to a // value, output the value. If the key is bound to an action, call the activate action // handler with the keycode. // // SIDE EFFECTS: If the code is a value, the last key is updated to the current // value, and repeat timer is reset. If the code is an action code, the activate // action dispatcher produces side effects. See asdf_activate_action() // // SCOPE: private // // COMPLEXITY: 3 // static void asdf_activate_key(asdf_keycode_t keycode) { if (keycode > ASDF_ACTION) { // ASDF_ACTION = ASDF_NOTHING = no action. asdf_activate_action((action_t) keycode); } else { // activate a new codable keypress asdf_put_code(keycode); if (last_key != keycode) { last_key = keycode; asdf_repeat_reset_count(); } } } // PROCEDURE: asdf_deactivate_key // INPUTS: keycode - the code for a key that has been released // OUTPUTS: none // // DESCRIPTION: Called when a key has been released. If the key is bound to a // value, output the value. If the key is bound to an action, call the deactivate // action handler with the keycode. // // SIDE EFFECTS: If the code is a value, the last key is set to ACTION_NOTHING, // which is effectively a NOP. If the code is an action code, the deactivate action // dispatcher produces side effects. See asdf_deactivate_action() // // SCOPE: private // // COMPLEXITY: 3 // static void asdf_deactivate_key(asdf_keycode_t keycode) { if (keycode > ASDF_ACTION) { asdf_deactivate_action((action_t) keycode); } else { // deactivate a released keypress if (last_key == keycode) { last_key = ACTION_NOTHING; } } } // PROCEDURE: asdf_handle_key_press_or_release // INPUTS: row, col: the row and column number of the key that has changed. // OUTPUTS: none // // DESCRIPTION: Given a row and column of a key that has changed: // // 1) Debounce the key by decrementing a debounce timer. If not yet debounced, // return. // 2) When the key is debounced, if the key is pressed, then call the activation // function associated with the key. Otherwise, if the key is released, call // the deactivation function for the key. // // SIDE EFFECTS: - Modifies debounce counter for the row and column // - After key debounces, modifies the table of stable key states // - Activate/deactivate functions have side effects // // NOTES: // // SCOPE: private // // COMPLEXITY: 3 // static void asdf_handle_key_press_or_release(uint8_t row, uint8_t col, uint8_t key_was_pressed) { uint8_t *debounce_count = &debounce_counters[row][col]; if (!(--(*debounce_count))) { // debounce timed out. Set new stable state and activate or // deactivate key. *debounce_count = ASDF_DEBOUNCE_TIME_MS; if (key_was_pressed) { last_stable_key_state[row] |= 1 << col; asdf_activate_key(asdf_lookup_keycode(row, col)); } else { // key was released last_stable_key_state[row] &= ~(1 << col); asdf_deactivate_key(asdf_lookup_keycode(row, col)); } } } // PROCEDURE: asdf_handle_key_held_pressed // INPUTS: row, col: The row and column of a key that is being held down // OUTPUTS: none // // DESCRIPTION: Given a row and column of a key that has been debounced and // continues to be pressed: // 1) Determine if this is the last key pressed. // 2) If it's the most recent key, then check to see if it's time to repeat the key code. // 3) If it's time to repeat, then do the repeat and reset the repeat timer. // // SIDE EFFECTS: // - Causes repeat timer to tick, and may reset the repeat timer. // - activate_key() will send a key code. // // NOTES: last_key is always a key code, never an action function, so only valid // key codes will be repeated. // // SCOPE: private // // COMPLEXITY: 3 // static void asdf_handle_key_held_pressed(uint8_t row, uint8_t col) { if (asdf_lookup_keycode(row, col) == last_key) { // if last debounced code-producing key is still pressed, handle repeat if (asdf_repeat()) { asdf_activate_key(last_key); } } } // PROCEDURE: asdf_keyscan // INPUTS: none // OUTPUTS: none // // DESCRIPTION: Scans the key matrix. For each row, read the columns and compare // with last stable state. For each changed key, call a key-change handler // function. For each stable pressed key, call a "continued press" handler // function. // // SIDE EFFECTS: // // NOTES: 1) The keyboard state is stored as an array of words, with one word // per row, and each bit in a word representing one key in the row. // // 2) While it is tempting to use bit hacks to reduce the number of // cycles through the inner loop, I have opted to just loop over all // the bits (as long as there are some changed or pressed keys). For // an 8-bit word, bit tricks don't deliver much performance boost, // but do decrease code clarity. // // COMPLEXITY: 5 // void asdf_keyscan(void) { asdf_cols_t (*row_reader)(uint8_t) = (asdf_cols_t(*)(uint8_t)) asdf_hook_get(ASDF_HOOK_ROW_SCANNER); asdf_hook_execute(ASDF_HOOK_EACH_SCAN); for (uint8_t row = 0; row < asdf_keymaps_num_rows(); row++) { asdf_cols_t row_key_state = (*row_reader)(row); asdf_cols_t changed = row_key_state ^ last_stable_key_state[row]; // loop over the bits until all changed or pressed keys in the row are handled. for (uint8_t col = 0; /*(changed || row_key_state) && */col < asdf_keymaps_num_cols(); col++) { if (changed & 1) { // key state is different from last stable state asdf_handle_key_press_or_release(row, col, row_key_state & 1); } else if (row_key_state & 1) { asdf_handle_key_held_pressed(row, col); } changed >>= 1; row_key_state >>= 1; } } } // PROCEDURE: asdf_apply_all_actions // INPUTS: none // OUTPUTS: none // // DESCRIPTION: Scans the key matrix. For each row, read the columns, and for // each bit, if an action is specified, then call the activate or deactivate // function for that action, based on the key state. This is necessary to ensure // that configuration settings, such as DIP switches and jumper settings, are // properly handled when a keymap is changed. // SIDE EFFECTS: See DESCRIPTION // // COMPLEXITY: 5 // // SCOPE: private // void asdf_apply_all_actions(void) { asdf_cols_t (*row_reader)(uint8_t) = (asdf_cols_t(*)(uint8_t)) asdf_hook_get(ASDF_HOOK_ROW_SCANNER); for (uint8_t row = 0; row < asdf_keymaps_num_rows(); row++) { asdf_cols_t row_key_state = (*row_reader)(row); // To avoid double actions, assign last stable key state to current state. last_stable_key_state[row] = row_key_state; // loop over the bits until all changed or pressed keys in the row are handled. for (uint8_t col = 0; col < asdf_keymaps_num_cols(); col++) { asdf_keycode_t code = asdf_lookup_keycode(row, col); if (row_key_state & 1) { asdf_activate_action(code); } else { asdf_deactivate_action(code); } row_key_state >>= 1; } } } // PROCEDURE: asdf_init // INPUTS: none // OUTPUTS: none // // DESCRIPTION: Reserve an output buffer to hold keycodes to be sent, and // initialize the keyboard state and debounce counters // // SIDE EFFECTS: see DESCRIPTION // // SCOPE: public // // COMPLEXITY: 3 // void asdf_init(void) { last_key = ACTION_NOTHING; asdf_buffer_init(); // initialize the buffers asdf_repeat_init(); // initialize the repeat counters asdf_keymaps_init(); // initialize keymaps. This also initializes the modifier // key states. // reserve a buffer for the ASCII output: asdf_keycode_buffer = asdf_buffer_new(ASDF_KEYCODE_BUFFER_SIZE); asdf_message_buffer = asdf_buffer_new(ASDF_MESSAGE_BUFFER_SIZE); // Initialize all the keys to the unpressed state, and initialze the debounce // counters. for (uint8_t row = 0; row < ASDF_MAX_ROWS; row++) { last_stable_key_state[row] = 0; for (uint8_t col = 0; col < ASDF_MAX_COLS; col++) { debounce_counters[row][col] = ASDF_DEBOUNCE_TIME_MS; } } } //-------|---------|---------+---------+---------+---------+---------+---------+ // Above line is 80 columns, and should display completely in the editor.