qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

ec_switch_matrix.c (13721B)


      1 /* Copyright 2023 Cipulot
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15  */
     16 
     17 #include "ec_switch_matrix.h"
     18 #include "analog.h"
     19 #include "atomic_util.h"
     20 #include "math.h"
     21 #include "print.h"
     22 #include "wait.h"
     23 
     24 #if defined(__AVR__)
     25 #    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."
     26 #endif
     27 
     28 #define OPEN_DRAIN_SUPPORT defined(PAL_MODE_OUTPUT_OPENDRAIN)
     29 
     30 eeprom_ec_config_t eeprom_ec_config;
     31 ec_config_t        ec_config;
     32 
     33 // Pin and port array
     34 const pin_t row_pins[]                                 = MATRIX_ROW_PINS;
     35 const pin_t amux_sel_pins[]                            = AMUX_SEL_PINS;
     36 const pin_t amux_en_pins[]                             = AMUX_EN_PINS;
     37 const pin_t amux_n_col_sizes[]                         = AMUX_COL_CHANNELS_SIZES;
     38 const pin_t amux_n_col_channels[][AMUX_MAX_COLS_COUNT] = {AMUX_COL_CHANNELS};
     39 
     40 #ifdef UNUSED_POSITIONS_LIST
     41 const uint8_t UNUSED_POSITIONS[][2] = UNUSED_POSITIONS_LIST;
     42 #    define UNUSED_POSITIONS_COUNT ARRAY_SIZE(UNUSED_POSITIONS)
     43 #endif
     44 
     45 #define AMUX_SEL_PINS_COUNT ARRAY_SIZE(amux_sel_pins)
     46 #define EXPECTED_AMUX_SEL_PINS_COUNT ceil(log2(AMUX_MAX_COLS_COUNT)
     47 
     48 // Checks for the correctness of the configuration
     49 _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");
     50 // Check that number of select pins is enough to select all the channels
     51 _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");
     52 // Check that number of elements in AMUX_COL_CHANNELS_SIZES is enough to specify the number of channels for all the multiplexers available
     53 _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");
     54 
     55 static uint16_t sw_value[MATRIX_ROWS][MATRIX_COLS];
     56 
     57 static adc_mux adcMux;
     58 
     59 // Initialize the row pins
     60 void init_row(void) {
     61     // Set all row pins as output and low
     62     for (uint8_t idx = 0; idx < MATRIX_ROWS; idx++) {
     63         gpio_set_pin_output(row_pins[idx]);
     64         gpio_write_pin_low(row_pins[idx]);
     65     }
     66 }
     67 
     68 // Initialize the multiplexers
     69 void init_amux(void) {
     70     for (uint8_t idx = 0; idx < AMUX_COUNT; idx++) {
     71         gpio_set_pin_output(amux_en_pins[idx]);
     72         gpio_write_pin_low(amux_en_pins[idx]);
     73     }
     74     for (uint8_t idx = 0; idx < AMUX_SEL_PINS_COUNT; idx++) {
     75         gpio_set_pin_output(amux_sel_pins[idx]);
     76     }
     77 }
     78 
     79 // Disable all the unused rows
     80 void disable_unused_row(uint8_t row) {
     81     // disable all the other rows apart from the current selected one
     82     for (uint8_t idx = 0; idx < MATRIX_ROWS; idx++) {
     83         if (idx != row) {
     84             gpio_write_pin_low(row_pins[idx]);
     85         }
     86     }
     87 }
     88 
     89 // Select the multiplexer channel of the specified multiplexer
     90 void select_amux_channel(uint8_t channel, uint8_t col) {
     91     // Get the channel for the specified multiplexer
     92     uint8_t ch = amux_n_col_channels[channel][col];
     93     // momentarily disable specified multiplexer
     94     gpio_write_pin_high(amux_en_pins[channel]);
     95     // Select the multiplexer channel
     96     for (uint8_t i = 0; i < AMUX_SEL_PINS_COUNT; i++) {
     97         gpio_write_pin(amux_sel_pins[i], ch & (1 << i));
     98     }
     99     // re enable specified multiplexer
    100     gpio_write_pin_low(amux_en_pins[channel]);
    101 }
    102 
    103 // Disable all the unused multiplexers
    104 void disable_unused_amux(uint8_t channel) {
    105     // disable all the other multiplexers apart from the current selected one
    106     for (uint8_t idx = 0; idx < AMUX_COUNT; idx++) {
    107         if (idx != channel) {
    108             gpio_write_pin_high(amux_en_pins[idx]);
    109         }
    110     }
    111 }
    112 // Discharge the peak hold capacitor
    113 void discharge_capacitor(void) {
    114 #ifdef OPEN_DRAIN_SUPPORT
    115     gpio_write_pin_low(DISCHARGE_PIN);
    116 #else
    117     gpio_write_pin_low(DISCHARGE_PIN);
    118     gpio_set_pin_output(DISCHARGE_PIN);
    119 #endif
    120 }
    121 
    122 // Charge the peak hold capacitor
    123 void charge_capacitor(uint8_t row) {
    124 #ifdef OPEN_DRAIN_SUPPORT
    125     gpio_write_pin_high(DISCHARGE_PIN);
    126 #else
    127     gpio_set_pin_input(DISCHARGE_PIN);
    128 #endif
    129     gpio_write_pin_high(row_pins[row]);
    130 }
    131 
    132 // Initialize the peripherals pins
    133 int ec_init(void) {
    134     // Initialize ADC
    135     palSetLineMode(ANALOG_PORT, PAL_MODE_INPUT_ANALOG);
    136     adcMux = pinToMux(ANALOG_PORT);
    137 
    138     // Dummy call to make sure that adcStart() has been called in the appropriate state
    139     adc_read(adcMux);
    140 
    141     // Initialize discharge pin as discharge mode
    142     gpio_write_pin_low(DISCHARGE_PIN);
    143 #ifdef OPEN_DRAIN_SUPPORT
    144     gpio_set_pin_output_open_drain(DISCHARGE_PIN);
    145 #else
    146     gpio_set_pin_output(DISCHARGE_PIN);
    147 #endif
    148 
    149     // Initialize drive lines
    150     init_row();
    151 
    152     // Initialize AMUXs
    153     init_amux();
    154 
    155     return 0;
    156 }
    157 
    158 // Get the noise floor
    159 void ec_noise_floor(void) {
    160     // Initialize the noise floor
    161     for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
    162         for (uint8_t col = 0; col < MATRIX_COLS; col++) {
    163             ec_config.noise_floor[row][col] = 0;
    164         }
    165     }
    166 
    167     // Sample the noise floor
    168     for (uint8_t i = 0; i < DEFAULT_NOISE_FLOOR_SAMPLING_COUNT; i++) {
    169         for (uint8_t amux = 0; amux < AMUX_COUNT; amux++) {
    170             disable_unused_amux(amux);
    171             for (uint8_t col = 0; col < amux_n_col_sizes[amux]; col++) {
    172                 uint8_t sum = 0;
    173                 for (uint8_t i = 0; i < (amux > 0 ? amux : 0); i++)
    174                     sum += amux_n_col_sizes[i];
    175                 uint8_t adjusted_col = col + sum;
    176                 for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
    177 #ifdef UNUSED_POSITIONS_LIST
    178                     if (is_unused_position(row, adjusted_col)) continue;
    179 #endif
    180                     disable_unused_row(row);
    181                     ec_config.noise_floor[row][adjusted_col] += ec_readkey_raw(amux, row, col);
    182                 }
    183             }
    184         }
    185         wait_ms(5);
    186     }
    187 
    188     // Average the noise floor
    189     for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
    190         for (uint8_t col = 0; col < MATRIX_COLS; col++) {
    191             ec_config.noise_floor[row][col] /= DEFAULT_NOISE_FLOOR_SAMPLING_COUNT;
    192         }
    193     }
    194 }
    195 
    196 // Scan key values and update matrix state
    197 bool ec_matrix_scan(matrix_row_t current_matrix[]) {
    198     bool updated = false;
    199 
    200     for (uint8_t amux = 0; amux < AMUX_COUNT; amux++) {
    201         disable_unused_amux(amux);
    202         for (uint8_t col = 0; col < amux_n_col_sizes[amux]; col++) {
    203             uint8_t sum = 0;
    204             for (uint8_t i = 0; i < (amux > 0 ? amux : 0); i++)
    205                 sum += amux_n_col_sizes[i];
    206             uint8_t adjusted_col = col + sum;
    207             for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
    208 #ifdef UNUSED_POSITIONS_LIST
    209                 if (is_unused_position(row, adjusted_col)) continue;
    210 #endif
    211                 disable_unused_row(row);
    212                 sw_value[row][adjusted_col] = ec_readkey_raw(amux, row, col);
    213 
    214                 if (ec_config.bottoming_calibration) {
    215                     if (ec_config.bottoming_calibration_starter[row][adjusted_col]) {
    216                         ec_config.bottoming_reading[row][adjusted_col]             = sw_value[row][adjusted_col];
    217                         ec_config.bottoming_calibration_starter[row][adjusted_col] = false;
    218                     } else if (sw_value[row][adjusted_col] > ec_config.bottoming_reading[row][adjusted_col]) {
    219                         ec_config.bottoming_reading[row][adjusted_col] = sw_value[row][adjusted_col];
    220                     }
    221                 } else {
    222                     updated |= ec_update_key(&current_matrix[row], row, adjusted_col, sw_value[row][adjusted_col]);
    223                 }
    224             }
    225         }
    226     }
    227 
    228     return ec_config.bottoming_calibration ? false : updated;
    229 }
    230 
    231 // Read the capacitive sensor value
    232 uint16_t ec_readkey_raw(uint8_t channel, uint8_t row, uint8_t col) {
    233     uint16_t sw_value = 0;
    234 
    235     // Select the multiplexer
    236     select_amux_channel(channel, col);
    237 
    238     // Set the row pin to low state to avoid ghosting
    239     gpio_write_pin_low(row_pins[row]);
    240 
    241     ATOMIC_BLOCK_FORCEON {
    242         // Set the row pin to high state and have capacitor charge
    243         charge_capacitor(row);
    244         // Read the ADC value
    245         sw_value = adc_read(adcMux);
    246     }
    247     // Discharge peak hold capacitor
    248     discharge_capacitor();
    249     // Waiting for the ghost capacitor to discharge fully
    250     wait_us(DISCHARGE_TIME);
    251 
    252     return sw_value;
    253 }
    254 
    255 // Update press/release state of key
    256 bool ec_update_key(matrix_row_t* current_row, uint8_t row, uint8_t col, uint16_t sw_value) {
    257     bool current_state = (*current_row >> col) & 1;
    258 
    259     // Real Time Noise Floor Calibration
    260     if (sw_value < (ec_config.noise_floor[row][col] - NOISE_FLOOR_THRESHOLD)) {
    261         uprintf("Noise Floor Change: %d, %d, %d\n", row, col, sw_value);
    262         ec_config.noise_floor[row][col]                             = sw_value;
    263         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]);
    264         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]);
    265         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]);
    266     }
    267 
    268     // Normal board-wide APC
    269     if (ec_config.actuation_mode == 0) {
    270         if (current_state && sw_value < ec_config.rescaled_mode_0_release_threshold[row][col]) {
    271             *current_row &= ~(1 << col);
    272             uprintf("Key released: %d, %d, %d\n", row, col, sw_value);
    273             return true;
    274         }
    275         if ((!current_state) && sw_value > ec_config.rescaled_mode_0_actuation_threshold[row][col]) {
    276             *current_row |= (1 << col);
    277             uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value);
    278             return true;
    279         }
    280     }
    281     // Rapid Trigger
    282     else if (ec_config.actuation_mode == 1) {
    283         // Is key in active zone?
    284         if (sw_value > ec_config.rescaled_mode_1_initial_deadzone_offset[row][col]) {
    285             // Is key pressed while in active zone?
    286             if (current_state) {
    287                 // Is the key still moving down?
    288                 if (sw_value > ec_config.extremum[row][col]) {
    289                     ec_config.extremum[row][col] = sw_value;
    290                     uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value);
    291                 }
    292                 // Has key moved up enough to be released?
    293                 else if (sw_value < ec_config.extremum[row][col] - ec_config.rescaled_mode_1_release_offset[row][col]) {
    294                     ec_config.extremum[row][col] = sw_value;
    295                     *current_row &= ~(1 << col);
    296                     uprintf("Key released: %d, %d, %d\n", row, col, sw_value);
    297                     return true;
    298                 }
    299             }
    300             // Key is not pressed while in active zone
    301             else {
    302                 // Is the key still moving up?
    303                 if (sw_value < ec_config.extremum[row][col]) {
    304                     ec_config.extremum[row][col] = sw_value;
    305                 }
    306                 // Has key moved down enough to be pressed?
    307                 else if (sw_value > ec_config.extremum[row][col] + ec_config.rescaled_mode_1_actuation_offset[row][col]) {
    308                     ec_config.extremum[row][col] = sw_value;
    309                     *current_row |= (1 << col);
    310                     uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value);
    311                     return true;
    312                 }
    313             }
    314         }
    315         // Key is not in active zone
    316         else {
    317             // Check to avoid key being stuck in pressed state near the active zone threshold
    318             if (sw_value < ec_config.extremum[row][col]) {
    319                 ec_config.extremum[row][col] = sw_value;
    320                 *current_row &= ~(1 << col);
    321                 return true;
    322             }
    323         }
    324     }
    325     return false;
    326 }
    327 
    328 // Print the matrix values
    329 void ec_print_matrix(void) {
    330     for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
    331         for (uint8_t col = 0; col < MATRIX_COLS - 1; col++) {
    332             uprintf("%4d,", sw_value[row][col]);
    333         }
    334         uprintf("%4d\n", sw_value[row][MATRIX_COLS - 1]);
    335     }
    336     print("\n");
    337 }
    338 
    339 // Check if the position is unused
    340 #ifdef UNUSED_POSITIONS_LIST
    341 bool is_unused_position(uint8_t row, uint8_t col) {
    342     for (uint8_t i = 0; i < UNUSED_POSITIONS_COUNT; i++) {
    343         if (UNUSED_POSITIONS[i][0] == row && UNUSED_POSITIONS[i][1] == col) {
    344             return true;
    345         }
    346     }
    347     return false;
    348 }
    349 #endif
    350 
    351 // Rescale the value to a different range
    352 uint16_t rescale(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) {
    353     return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
    354 }