/* Copyright 2023 Cipulot * * 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 "ec_switch_matrix.h" #include "analog.h" #include "atomic_util.h" #include "math.h" #include "print.h" #include "wait.h" #if defined(__AVR__) # error "AVR platforms not supported due to a variety of reasons. Among them there are limited memory, limited number of pins and ADC not being able to give satisfactory results." #endif #define OPEN_DRAIN_SUPPORT defined(PAL_MODE_OUTPUT_OPENDRAIN) eeprom_ec_config_t eeprom_ec_config; ec_config_t ec_config; // Pin and port array const pin_t row_pins[] = MATRIX_ROW_PINS; const pin_t amux_sel_pins[] = AMUX_SEL_PINS; const pin_t amux_en_pins[] = AMUX_EN_PINS; const pin_t amux_n_col_sizes[] = AMUX_COL_CHANNELS_SIZES; const pin_t amux_n_col_channels[][AMUX_MAX_COLS_COUNT] = {AMUX_COL_CHANNELS}; #ifdef UNUSED_POSITIONS_LIST const uint8_t UNUSED_POSITIONS[][2] = UNUSED_POSITIONS_LIST; # define UNUSED_POSITIONS_COUNT (sizeof(UNUSED_POSITIONS) / sizeof(UNUSED_POSITIONS[0])) #endif #define AMUX_SEL_PINS_COUNT ARRAY_SIZE(amux_sel_pins) #define EXPECTED_AMUX_SEL_PINS_COUNT ceil(log2(AMUX_MAX_COLS_COUNT) // Checks for the correctness of the configuration _Static_assert(ARRAY_SIZE(amux_en_pins) == AMUX_COUNT, "AMUX_EN_PINS doesn't have the minimum number of bits required to enable all the multiplexers available"); // Check that number of select pins is enough to select all the channels _Static_assert(AMUX_SEL_PINS_COUNT == EXPECTED_AMUX_SEL_PINS_COUNT), "AMUX_SEL_PINS doesn't have the minimum number of bits required address all the channels"); // Check that number of elements in AMUX_COL_CHANNELS_SIZES is enough to specify the number of channels for all the multiplexers available _Static_assert(ARRAY_SIZE(amux_n_col_sizes) == AMUX_COUNT, "AMUX_COL_CHANNELS_SIZES doesn't have the minimum number of elements required to specify the number of channels for all the multiplexers available"); static uint16_t sw_value[MATRIX_ROWS][MATRIX_COLS]; static adc_mux adcMux; // Initialize the row pins void init_row(void) { // Set all row pins as output and low for (uint8_t idx = 0; idx < MATRIX_ROWS; idx++) { gpio_set_pin_output(row_pins[idx]); gpio_write_pin_low(row_pins[idx]); } } // Initialize the multiplexers void init_amux(void) { for (uint8_t idx = 0; idx < AMUX_COUNT; idx++) { gpio_set_pin_output(amux_en_pins[idx]); gpio_write_pin_low(amux_en_pins[idx]); } for (uint8_t idx = 0; idx < AMUX_SEL_PINS_COUNT; idx++) { gpio_set_pin_output(amux_sel_pins[idx]); } } // Disable all the unused rows void disable_unused_row(uint8_t row) { // disable all the other rows apart from the current selected one for (uint8_t idx = 0; idx < MATRIX_ROWS; idx++) { if (idx != row) { gpio_write_pin_low(row_pins[idx]); } } } // Select the multiplexer channel of the specified multiplexer void select_amux_channel(uint8_t channel, uint8_t col) { // Get the channel for the specified multiplexer uint8_t ch = amux_n_col_channels[channel][col]; // momentarily disable specified multiplexer gpio_write_pin_high(amux_en_pins[channel]); // Select the multiplexer channel for (uint8_t i = 0; i < AMUX_SEL_PINS_COUNT; i++) { gpio_write_pin(amux_sel_pins[i], ch & (1 << i)); } // re enable specified multiplexer gpio_write_pin_low(amux_en_pins[channel]); } // Disable all the unused multiplexers void disable_unused_amux(uint8_t channel) { // disable all the other multiplexers apart from the current selected one for (uint8_t idx = 0; idx < AMUX_COUNT; idx++) { if (idx != channel) { gpio_write_pin_high(amux_en_pins[idx]); } } } // Discharge the peak hold capacitor void discharge_capacitor(void) { #ifdef OPEN_DRAIN_SUPPORT gpio_write_pin_low(DISCHARGE_PIN); #else gpio_write_pin_low(DISCHARGE_PIN); gpio_set_pin_output(DISCHARGE_PIN); #endif } // Charge the peak hold capacitor void charge_capacitor(uint8_t row) { #ifdef OPEN_DRAIN_SUPPORT gpio_write_pin_high(DISCHARGE_PIN); #else gpio_set_pin_input(DISCHARGE_PIN); #endif gpio_write_pin_high(row_pins[row]); } // Initialize the peripherals pins int ec_init(void) { // Initialize ADC palSetLineMode(ANALOG_PORT, PAL_MODE_INPUT_ANALOG); adcMux = pinToMux(ANALOG_PORT); // Dummy call to make sure that adcStart() has been called in the appropriate state adc_read(adcMux); // Initialize discharge pin as discharge mode gpio_write_pin_low(DISCHARGE_PIN); #ifdef OPEN_DRAIN_SUPPORT gpio_set_pin_output_open_drain(DISCHARGE_PIN); #else gpio_set_pin_output(DISCHARGE_PIN); #endif // Initialize drive lines init_row(); // Initialize AMUXs init_amux(); return 0; } // Get the noise floor void ec_noise_floor(void) { // Initialize the noise floor for (uint8_t row = 0; row < MATRIX_ROWS; row++) { for (uint8_t col = 0; col < MATRIX_COLS; col++) { ec_config.noise_floor[row][col] = 0; } } // Sample the noise floor for (uint8_t i = 0; i < DEFAULT_NOISE_FLOOR_SAMPLING_COUNT; i++) { for (uint8_t amux = 0; amux < AMUX_COUNT; amux++) { disable_unused_amux(amux); for (uint8_t col = 0; col < amux_n_col_sizes[amux]; col++) { uint8_t sum = 0; for (uint8_t i = 0; i < (amux > 0 ? amux : 0); i++) sum += amux_n_col_sizes[i]; uint8_t adjusted_col = col + sum; for (uint8_t row = 0; row < MATRIX_ROWS; row++) { #ifdef UNUSED_POSITIONS_LIST if (is_unused_position(row, adjusted_col)) continue; #endif disable_unused_row(row); ec_config.noise_floor[row][adjusted_col] += ec_readkey_raw(amux, row, col); } } } wait_ms(5); } // Average the noise floor for (uint8_t row = 0; row < MATRIX_ROWS; row++) { for (uint8_t col = 0; col < MATRIX_COLS; col++) { ec_config.noise_floor[row][col] /= DEFAULT_NOISE_FLOOR_SAMPLING_COUNT; } } } // Scan key values and update matrix state bool ec_matrix_scan(matrix_row_t current_matrix[]) { bool updated = false; for (uint8_t amux = 0; amux < AMUX_COUNT; amux++) { disable_unused_amux(amux); for (uint8_t col = 0; col < amux_n_col_sizes[amux]; col++) { uint8_t sum = 0; for (uint8_t i = 0; i < (amux > 0 ? amux : 0); i++) sum += amux_n_col_sizes[i]; uint8_t adjusted_col = col + sum; for (uint8_t row = 0; row < MATRIX_ROWS; row++) { #ifdef UNUSED_POSITIONS_LIST if (is_unused_position(row, adjusted_col)) continue; #endif disable_unused_row(row); sw_value[row][adjusted_col] = ec_readkey_raw(amux, row, col); if (ec_config.bottoming_calibration) { if (ec_config.bottoming_calibration_starter[row][adjusted_col]) { ec_config.bottoming_reading[row][adjusted_col] = sw_value[row][adjusted_col]; ec_config.bottoming_calibration_starter[row][adjusted_col] = false; } else if (sw_value[row][adjusted_col] > ec_config.bottoming_reading[row][adjusted_col]) { ec_config.bottoming_reading[row][adjusted_col] = sw_value[row][adjusted_col]; } } else { updated |= ec_update_key(¤t_matrix[row], row, adjusted_col, sw_value[row][adjusted_col]); } } } } return ec_config.bottoming_calibration ? false : updated; } // Read the capacitive sensor value uint16_t ec_readkey_raw(uint8_t channel, uint8_t row, uint8_t col) { uint16_t sw_value = 0; // Select the multiplexer select_amux_channel(channel, col); // Set the row pin to low state to avoid ghosting gpio_write_pin_low(row_pins[row]); ATOMIC_BLOCK_FORCEON { // Set the row pin to high state and have capacitor charge charge_capacitor(row); // Read the ADC value sw_value = adc_read(adcMux); } // Discharge peak hold capacitor discharge_capacitor(); // Waiting for the ghost capacitor to discharge fully wait_us(DISCHARGE_TIME); return sw_value; } // Update press/release state of key bool ec_update_key(matrix_row_t *current_row, uint8_t row, uint8_t col, uint16_t sw_value) { bool current_state = (*current_row >> col) & 1; // Real Time Noise Floor Calibration if (sw_value < (ec_config.noise_floor[row][col] - NOISE_FLOOR_THRESHOLD)) { uprintf("Noise Floor Change: %d, %d, %d\n", row, col, sw_value); ec_config.noise_floor[row][col] = sw_value; ec_config.rescaled_mode_0_actuation_threshold[row][col] = rescale(ec_config.mode_0_actuation_threshold, 0, 1023, ec_config.noise_floor[row][col], eeprom_ec_config.bottoming_reading[row][col]); ec_config.rescaled_mode_0_release_threshold[row][col] = rescale(ec_config.mode_0_release_threshold, 0, 1023, ec_config.noise_floor[row][col], eeprom_ec_config.bottoming_reading[row][col]); ec_config.rescaled_mode_1_initial_deadzone_offset[row][col] = rescale(ec_config.mode_1_initial_deadzone_offset, 0, 1023, ec_config.noise_floor[row][col], eeprom_ec_config.bottoming_reading[row][col]); } // Normal board-wide APC if (ec_config.actuation_mode == 0) { if (current_state && sw_value < ec_config.rescaled_mode_0_release_threshold[row][col]) { *current_row &= ~(1 << col); uprintf("Key released: %d, %d, %d\n", row, col, sw_value); return true; } if ((!current_state) && sw_value > ec_config.rescaled_mode_0_actuation_threshold[row][col]) { *current_row |= (1 << col); uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value); return true; } } // Rapid Trigger else if (ec_config.actuation_mode == 1) { // Is key in active zone? if (sw_value > ec_config.rescaled_mode_1_initial_deadzone_offset[row][col]) { // Is key pressed while in active zone? if (current_state) { // Is the key still moving down? if (sw_value > ec_config.extremum[row][col]) { ec_config.extremum[row][col] = sw_value; uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value); } // Has key moved up enough to be released? else if (sw_value < ec_config.extremum[row][col] - ec_config.rescaled_mode_1_release_offset[row][col]) { ec_config.extremum[row][col] = sw_value; *current_row &= ~(1 << col); uprintf("Key released: %d, %d, %d\n", row, col, sw_value); return true; } } // Key is not pressed while in active zone else { // Is the key still moving up? if (sw_value < ec_config.extremum[row][col]) { ec_config.extremum[row][col] = sw_value; } // Has key moved down enough to be pressed? else if (sw_value > ec_config.extremum[row][col] + ec_config.rescaled_mode_1_actuation_offset[row][col]) { ec_config.extremum[row][col] = sw_value; *current_row |= (1 << col); uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value); return true; } } } // Key is not in active zone else { // Check to avoid key being stuck in pressed state near the active zone threshold if (sw_value < ec_config.extremum[row][col]) { ec_config.extremum[row][col] = sw_value; *current_row &= ~(1 << col); return true; } } } return false; } // Print the matrix values void ec_print_matrix(void) { for (uint8_t row = 0; row < MATRIX_ROWS; row++) { for (uint8_t col = 0; col < MATRIX_COLS - 1; col++) { uprintf("%4d,", sw_value[row][col]); } uprintf("%4d\n", sw_value[row][MATRIX_COLS - 1]); } print("\n"); } // Check if the position is unused #ifdef UNUSED_POSITIONS_LIST bool is_unused_position(uint8_t row, uint8_t col) { for (uint8_t i = 0; i < UNUSED_POSITIONS_COUNT; i++) { if (UNUSED_POSITIONS[i][0] == row && UNUSED_POSITIONS[i][1] == col) { return true; } } return false; } #endif // Rescale the value to a different range uint16_t rescale(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }