diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-02-11 08:10:12 +0100 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-02-13 05:53:45 +0100 |
commit | aa6c480f40241471d12c317c72acd94c2148a991 (patch) | |
tree | 1a6ed6298f3fef109a1989f313f7f1d75449f2ba /matrix.c | |
parent | dff9b75c6fb900830e478ad5c71ca42efe34ffc6 (diff) | |
download | tmk7637-aa6c480f40241471d12c317c72acd94c2148a991.tar.gz |
added the K7637 firmware
Diffstat (limited to 'matrix.c')
-rw-r--r-- | matrix.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/matrix.c b/matrix.c new file mode 100644 index 0000000..8f98afd --- /dev/null +++ b/matrix.c @@ -0,0 +1,337 @@ +/* +Copyright 2021 Robin Haberkorn <robin.haberkorn@googlemail.com> + +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 2 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 <http://www.gnu.org/licenses/>. +*/ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> + +#include <avr/io.h> +#include <util/delay.h> + +#include "print.h" +#include "debug.h" +#include "timer.h" +#include "led.h" +#include "host.h" +#include "pwm.h" +#include "keyclick.h" +#include "matrix.h" + +/* + * NOTE: The "Betriebsdokumentation" mentions that the keyboard matrix + * must not change for two scan cycles. + * The setting below actually means, it must not change for 2ms. + * This has been shown to be sufficient. + * It is still possible for keypresses to be instable but this has only + * been observed with a poor power source. + */ +#ifndef DEBOUNCE +# define DEBOUNCE 2 +#endif + +/* matrix state(1:on, 0:off) */ +static matrix_row_t matrix[MATRIX_ROWS]; +static matrix_row_t matrix_debouncing[MATRIX_ROWS]; + +static void init_pins(void); +static void select_col(uint8_t col); +static void unselect_cols(void); +static bool read_row(uint8_t row); + +void matrix_init(void) +{ + debug_enable = true; + debug_matrix = true; + + /* this also configures all LED pins and brings them into defined states */ + led_set(host_keyboard_leds()); + + init_pins(); + + /* initialize matrix state: all keys off */ + memset(matrix, sizeof(matrix), 0); + memset(matrix_debouncing, sizeof(matrix_debouncing), 0); +} + +/* + * FIXME: If we rotated the matrix (8 columns and 16 rows) + * we could simplify and speed up the code below. + * Unfortunately, we'd also have to rotate the Unimap translation table and + * it would no longer correspond with the "Serviceschaltplan". + */ +uint8_t matrix_scan(void) +{ + static uint16_t debouncing_time = 0, keyclick_time = 0; + /** Number of pressed keys in `matrix_debouncing` */ + uint8_t matrix_debouncing_pressed_keys = 0; + + for (uint8_t col = 0; col < MATRIX_COLS; col++) { + select_col(col); + /* + * Give the signals some time to settle. + * This is not mentioned in the "Betriebsdokumentation" + * but has been tested experimentally. + * 30us is used by all the other controller firmwares as well. + */ + _delay_us(30); + + uint8_t row = 0; + + /* + * The first 4 bits in the 15th column + * are sensed like ordinary keypresses but + * in reality represent a 3-bit "security key" + * (cf. Betriebsdokumentation, p.13f) with + * an actual resolution of 6 encoded into a special + * keylike device that's plugged into the keyboard. + * It makes no sense to map these original bits into + * the keyboard matrix. + * Instead we translate it into one of six key presses + * via unused fields of the keyboard matrix whenever + * the "security" key is removed. + * These pseudo-keypress are mapped to F19-F24 in unimap_trans + * and could be mapped at the OS level, eg. to lock up the screen. + */ + if (col+1 == MATRIX_COLS) { + static uint8_t last_security_key = 0; + uint8_t security_key = (!read_row(0) << 0) | + (!read_row(1) << 1) | + (!read_row(2) << 2); + + for (uint8_t i = 0; i < 6; i++) + matrix_debouncing[i] &= ~(1 << col); + if (security_key != last_security_key && security_key == 0 && + 1 <= last_security_key && last_security_key <= 6) + matrix_debouncing[last_security_key-1] |= (1 << col); + + last_security_key = security_key; + row = 6; + } + + while (row < MATRIX_ROWS) { + matrix_row_t prev_row = matrix_debouncing[row]; + + if (read_row(row)) { + matrix_debouncing[row] |= (1 << col); + matrix_debouncing_pressed_keys++; + } else { + matrix_debouncing[row] &= ~(1 << col); + } + + if (matrix_debouncing[row] != prev_row) + debouncing_time = timer_read(); + + row++; + } + + unselect_cols(); + } + + if (debouncing_time && timer_elapsed(debouncing_time) >= DEBOUNCE) { + /** Number of pressed keys in `matrix` */ + static uint8_t matrix_pressed_keys = 0; + + /* + * Trigger keyclick whenever the keyboard matrix has actually + * changed (ie. `debouncing_time` was set). + * When using the solenoid, the keyclick is supposed to immitate a + * mechanical click, so there should be one sound when pressing down + * the key and another when releasing it. + * If a key is already pressed (ie. you press an additional key), + * we release the solenoid for 50ms. + * We consciously do not _delay_ms(50) here since that would delay + * key event delivery. + * When using the buzzer, a short 50ms beep is played every time + * a new key is pressed. + */ + switch (keyclick_mode) { + case KEYCLICK_SOLENOID: + if (matrix_debouncing_pressed_keys > 1 || + (matrix_debouncing_pressed_keys == 1 && matrix_pressed_keys > 1)) { + keyclick_solenoid_set(false); + keyclick_time = timer_read(); + } else { + keyclick_solenoid_set(matrix_debouncing_pressed_keys); + } + break; + + case KEYCLICK_BUZZER: + if (matrix_debouncing_pressed_keys > matrix_pressed_keys) { + pwm_pb5_set_tone(550); + keyclick_time = timer_read(); + } + break; + + default: + break; + } + + memcpy(matrix, matrix_debouncing, sizeof(matrix)); + matrix_pressed_keys = matrix_debouncing_pressed_keys; + + debouncing_time = 0; + } + + /* + * This is responsible for performing a delayed action depending + * on the keyclick mode (see abovc). + * For solenoids, it reactivates them since they should be on and + * are released when the last key is released. + * When using the buzzer, this is responsible for turning it off + * after a short while. + * Polling here makes sure we do not delay any key delivery. + */ + if (keyclick_time && timer_elapsed(keyclick_time) >= 50) { + switch (keyclick_mode) { + case KEYCLICK_SOLENOID: + keyclick_solenoid_set(true); + break; + + case KEYCLICK_BUZZER: + if (!(host_keyboard_leds() & (1 << USB_LED_KANA))) + pwm_pb5_set_tone(0); + break; + + default: + break; + } + + keyclick_time = 0; + } + + return 1; +} + +inline +matrix_row_t matrix_get_row(uint8_t row) +{ + return matrix[row]; +} + +static void init_pins(void) +{ + /* + * All rows are input pins (DDR:0) and need pull-ups. + * There is a pull-up attached to the EPROM, but I acceidentally + * destroyed it. + * Therefore we enable internal pull-ups (PORT:1). + */ + /* Row 0: PB0 == D7 */ + DDRB &= ~0b1; + PORTB |= 0b1; + /* Row 1-2: PE4-5 == D5-6 (Connected by cable) */ + DDRE &= ~0b110000; + PORTE |= 0b110000; + /* Row 3-4: PF0-1 == D4-3 (NOTE: PF0 == Row 4, PF1 == Row 3) */ + DDRF &= ~0b11; + PORTF |= 0b11; + /* Row 5: PE7 == D2 */ + DDRE &= ~0b10000000; + PORTE |= 0b10000000; + /* Row 6-7: PB1-2 == D0-1 */ + DDRB &= ~0b110; + PORTB |= 0b110; + + /* + * We've got a strobe-sense matrix and we strobe via the column pins. + * Therefore they must be outputs (DDR:1). + */ + /* Column 0-3: PF4-7 == A11-14 */ + DDRF |= 0b11110000; + /* Column 4-11: PC0-7 == A3-10 */ + DDRC |= 0b11111111; + /* Column 12-13: PE0-1 == A1-2 */ + DDRE |= 0b11; + /* Column 14: PD7 == A0 */ + DDRD |= 0b10000000; + + /* + * PF3 == A15 must be LOW since otherwise some NAND gates (IC13) will interfere with D0-3 + * (half of the matrix rows). + * We might also draw this to GND by cable or destroy IC13. + * The latter might even save some power. + * + * FIXME: A15 must actually be triggered for some of the modifiers! + */ + DDRF |= 0b00001000; + PORTF &= ~0b00001000; + + /* + * Solenoid (ie. relay). It's LOW-active. + */ + DDRB |= (1 << PB3); + PORTB |= (1 << PB3); +} + +static void select_col(uint8_t col) +{ + /* + * FIXME: Simplify? Will go at the expense of readability. + */ + switch (col) { + case 0: PORTD |= 0b10000000; break; + case 1: PORTE |= 0b00000001; break; + case 2: PORTE |= 0b00000010; break; + case 3: PORTC |= 0b00000001; break; + case 4: PORTC |= 0b00000010; break; + case 5: PORTC |= 0b00000100; break; + case 6: PORTC |= 0b00001000; break; + case 7: PORTC |= 0b00010000; break; + case 8: PORTC |= 0b00100000; break; + case 9: PORTC |= 0b01000000; break; + case 10: PORTC |= 0b10000000; break; + case 11: PORTF |= 0b10000000; break; + case 12: PORTF |= 0b01000000; break; + case 13: PORTF |= 0b00100000; break; + case 14: PORTF |= 0b00010000; break; + case 15: PORTF |= 0b00001000; break; + } +} + +static void unselect_cols(void) +{ + PORTD &= ~0b10000000; + PORTE &= ~0b11; + PORTC &= ~0b11111111; + PORTF &= ~0b11111000; +} + +static bool read_row(uint8_t row) +{ + /* + * NOTE: The key mechanism pulls the row pin to GND when the key is + * pressed. + * However, the rows are not directly connected to the keyboard matrix + * but through NAND gates. The signal is therefore inverted. + * The NAND gates no longer serve any purpose and could be skipped + * altogether by desoldering them and directly connecting the inputs with + * the outputs. The PIN registers should then be inverted. + */ + switch (row) { + case 0: return PINB & 0b00000010; + case 1: return PINB & 0b00000100; + case 2: return PINE & 0b10000000; + case 3: return PINF & 0b00000001; + case 4: return PINF & 0b00000010; + case 5: return PINE & 0b00100000; + case 6: return PINE & 0b00010000; + case 7: return PINB & 0b00000001; + } + + /* not reached */ + return false; +} |