ec_switch_matrix.c (13748B)
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 (sizeof(UNUSED_POSITIONS) / sizeof(UNUSED_POSITIONS[0])) 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 // Checks for the correctness of the configuration 48 _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"); 49 // Check that number of select pins is enough to select all the channels 50 _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"); 51 // Check that number of elements in AMUX_COL_CHANNELS_SIZES is enough to specify the number of channels for all the multiplexers available 52 _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"); 53 54 static uint16_t sw_value[MATRIX_ROWS][MATRIX_COLS]; 55 56 static adc_mux adcMux; 57 58 // Initialize the row pins 59 void init_row(void) { 60 // Set all row pins as output and low 61 for (uint8_t idx = 0; idx < MATRIX_ROWS; idx++) { 62 gpio_set_pin_output(row_pins[idx]); 63 gpio_write_pin_low(row_pins[idx]); 64 } 65 } 66 67 // Initialize the multiplexers 68 void init_amux(void) { 69 for (uint8_t idx = 0; idx < AMUX_COUNT; idx++) { 70 gpio_set_pin_output(amux_en_pins[idx]); 71 gpio_write_pin_low(amux_en_pins[idx]); 72 } 73 for (uint8_t idx = 0; idx < AMUX_SEL_PINS_COUNT; idx++) { 74 gpio_set_pin_output(amux_sel_pins[idx]); 75 } 76 } 77 78 // Disable all the unused rows 79 void disable_unused_row(uint8_t row) { 80 // disable all the other rows apart from the current selected one 81 for (uint8_t idx = 0; idx < MATRIX_ROWS; idx++) { 82 if (idx != row) { 83 gpio_write_pin_low(row_pins[idx]); 84 } 85 } 86 } 87 88 // Select the multiplexer channel of the specified multiplexer 89 void select_amux_channel(uint8_t channel, uint8_t col) { 90 // Get the channel for the specified multiplexer 91 uint8_t ch = amux_n_col_channels[channel][col]; 92 // momentarily disable specified multiplexer 93 gpio_write_pin_high(amux_en_pins[channel]); 94 // Select the multiplexer channel 95 for (uint8_t i = 0; i < AMUX_SEL_PINS_COUNT; i++) { 96 gpio_write_pin(amux_sel_pins[i], ch & (1 << i)); 97 } 98 // re enable specified multiplexer 99 gpio_write_pin_low(amux_en_pins[channel]); 100 } 101 102 // Disable all the unused multiplexers 103 void disable_unused_amux(uint8_t channel) { 104 // disable all the other multiplexers apart from the current selected one 105 for (uint8_t idx = 0; idx < AMUX_COUNT; idx++) { 106 if (idx != channel) { 107 gpio_write_pin_high(amux_en_pins[idx]); 108 } 109 } 110 } 111 // Discharge the peak hold capacitor 112 void discharge_capacitor(void) { 113 #ifdef OPEN_DRAIN_SUPPORT 114 gpio_write_pin_low(DISCHARGE_PIN); 115 #else 116 gpio_write_pin_low(DISCHARGE_PIN); 117 gpio_set_pin_output(DISCHARGE_PIN); 118 #endif 119 } 120 121 // Charge the peak hold capacitor 122 void charge_capacitor(uint8_t row) { 123 #ifdef OPEN_DRAIN_SUPPORT 124 gpio_write_pin_high(DISCHARGE_PIN); 125 #else 126 gpio_set_pin_input(DISCHARGE_PIN); 127 #endif 128 gpio_write_pin_high(row_pins[row]); 129 } 130 131 // Initialize the peripherals pins 132 int ec_init(void) { 133 // Initialize ADC 134 palSetLineMode(ANALOG_PORT, PAL_MODE_INPUT_ANALOG); 135 adcMux = pinToMux(ANALOG_PORT); 136 137 // Dummy call to make sure that adcStart() has been called in the appropriate state 138 adc_read(adcMux); 139 140 // Initialize discharge pin as discharge mode 141 gpio_write_pin_low(DISCHARGE_PIN); 142 #ifdef OPEN_DRAIN_SUPPORT 143 gpio_set_pin_output_open_drain(DISCHARGE_PIN); 144 #else 145 gpio_set_pin_output(DISCHARGE_PIN); 146 #endif 147 148 // Initialize drive lines 149 init_row(); 150 151 // Initialize AMUXs 152 init_amux(); 153 154 return 0; 155 } 156 157 // Get the noise floor 158 void ec_noise_floor(void) { 159 // Initialize the noise floor 160 for (uint8_t row = 0; row < MATRIX_ROWS; row++) { 161 for (uint8_t col = 0; col < MATRIX_COLS; col++) { 162 ec_config.noise_floor[row][col] = 0; 163 } 164 } 165 166 // Sample the noise floor 167 for (uint8_t i = 0; i < DEFAULT_NOISE_FLOOR_SAMPLING_COUNT; i++) { 168 for (uint8_t amux = 0; amux < AMUX_COUNT; amux++) { 169 disable_unused_amux(amux); 170 for (uint8_t col = 0; col < amux_n_col_sizes[amux]; col++) { 171 uint8_t sum = 0; 172 for (uint8_t i = 0; i < (amux > 0 ? amux : 0); i++) 173 sum += amux_n_col_sizes[i]; 174 uint8_t adjusted_col = col + sum; 175 for (uint8_t row = 0; row < MATRIX_ROWS; row++) { 176 #ifdef UNUSED_POSITIONS_LIST 177 if (is_unused_position(row, adjusted_col)) continue; 178 #endif 179 disable_unused_row(row); 180 ec_config.noise_floor[row][adjusted_col] += ec_readkey_raw(amux, row, col); 181 } 182 } 183 } 184 wait_ms(5); 185 } 186 187 // Average the noise floor 188 for (uint8_t row = 0; row < MATRIX_ROWS; row++) { 189 for (uint8_t col = 0; col < MATRIX_COLS; col++) { 190 ec_config.noise_floor[row][col] /= DEFAULT_NOISE_FLOOR_SAMPLING_COUNT; 191 } 192 } 193 } 194 195 // Scan key values and update matrix state 196 bool ec_matrix_scan(matrix_row_t current_matrix[]) { 197 bool updated = false; 198 199 for (uint8_t amux = 0; amux < AMUX_COUNT; amux++) { 200 disable_unused_amux(amux); 201 for (uint8_t col = 0; col < amux_n_col_sizes[amux]; col++) { 202 uint8_t sum = 0; 203 for (uint8_t i = 0; i < (amux > 0 ? amux : 0); i++) 204 sum += amux_n_col_sizes[i]; 205 uint8_t adjusted_col = col + sum; 206 for (uint8_t row = 0; row < MATRIX_ROWS; row++) { 207 #ifdef UNUSED_POSITIONS_LIST 208 if (is_unused_position(row, adjusted_col)) continue; 209 #endif 210 disable_unused_row(row); 211 sw_value[row][adjusted_col] = ec_readkey_raw(amux, row, col); 212 213 if (ec_config.bottoming_calibration) { 214 if (ec_config.bottoming_calibration_starter[row][adjusted_col]) { 215 ec_config.bottoming_reading[row][adjusted_col] = sw_value[row][adjusted_col]; 216 ec_config.bottoming_calibration_starter[row][adjusted_col] = false; 217 } else if (sw_value[row][adjusted_col] > ec_config.bottoming_reading[row][adjusted_col]) { 218 ec_config.bottoming_reading[row][adjusted_col] = sw_value[row][adjusted_col]; 219 } 220 } else { 221 updated |= ec_update_key(¤t_matrix[row], row, adjusted_col, sw_value[row][adjusted_col]); 222 } 223 } 224 } 225 } 226 227 return ec_config.bottoming_calibration ? false : updated; 228 } 229 230 // Read the capacitive sensor value 231 uint16_t ec_readkey_raw(uint8_t channel, uint8_t row, uint8_t col) { 232 uint16_t sw_value = 0; 233 234 // Select the multiplexer 235 select_amux_channel(channel, col); 236 237 // Set the row pin to low state to avoid ghosting 238 gpio_write_pin_low(row_pins[row]); 239 240 ATOMIC_BLOCK_FORCEON { 241 // Set the row pin to high state and have capacitor charge 242 charge_capacitor(row); 243 // Read the ADC value 244 sw_value = adc_read(adcMux); 245 } 246 // Discharge peak hold capacitor 247 discharge_capacitor(); 248 // Waiting for the ghost capacitor to discharge fully 249 wait_us(DISCHARGE_TIME); 250 251 return sw_value; 252 } 253 254 // Update press/release state of key 255 bool ec_update_key(matrix_row_t *current_row, uint8_t row, uint8_t col, uint16_t sw_value) { 256 bool current_state = (*current_row >> col) & 1; 257 258 // Real Time Noise Floor Calibration 259 if (sw_value < (ec_config.noise_floor[row][col] - NOISE_FLOOR_THRESHOLD)) { 260 uprintf("Noise Floor Change: %d, %d, %d\n", row, col, sw_value); 261 ec_config.noise_floor[row][col] = sw_value; 262 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]); 263 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]); 264 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]); 265 } 266 267 // Normal board-wide APC 268 if (ec_config.actuation_mode == 0) { 269 if (current_state && sw_value < ec_config.rescaled_mode_0_release_threshold[row][col]) { 270 *current_row &= ~(1 << col); 271 uprintf("Key released: %d, %d, %d\n", row, col, sw_value); 272 return true; 273 } 274 if ((!current_state) && sw_value > ec_config.rescaled_mode_0_actuation_threshold[row][col]) { 275 *current_row |= (1 << col); 276 uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value); 277 return true; 278 } 279 } 280 // Rapid Trigger 281 else if (ec_config.actuation_mode == 1) { 282 // Is key in active zone? 283 if (sw_value > ec_config.rescaled_mode_1_initial_deadzone_offset[row][col]) { 284 // Is key pressed while in active zone? 285 if (current_state) { 286 // Is the key still moving down? 287 if (sw_value > ec_config.extremum[row][col]) { 288 ec_config.extremum[row][col] = sw_value; 289 uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value); 290 } 291 // Has key moved up enough to be released? 292 else if (sw_value < ec_config.extremum[row][col] - ec_config.rescaled_mode_1_release_offset[row][col]) { 293 ec_config.extremum[row][col] = sw_value; 294 *current_row &= ~(1 << col); 295 uprintf("Key released: %d, %d, %d\n", row, col, sw_value); 296 return true; 297 } 298 } 299 // Key is not pressed while in active zone 300 else { 301 // Is the key still moving up? 302 if (sw_value < ec_config.extremum[row][col]) { 303 ec_config.extremum[row][col] = sw_value; 304 } 305 // Has key moved down enough to be pressed? 306 else if (sw_value > ec_config.extremum[row][col] + ec_config.rescaled_mode_1_actuation_offset[row][col]) { 307 ec_config.extremum[row][col] = sw_value; 308 *current_row |= (1 << col); 309 uprintf("Key pressed: %d, %d, %d\n", row, col, sw_value); 310 return true; 311 } 312 } 313 } 314 // Key is not in active zone 315 else { 316 // Check to avoid key being stuck in pressed state near the active zone threshold 317 if (sw_value < ec_config.extremum[row][col]) { 318 ec_config.extremum[row][col] = sw_value; 319 *current_row &= ~(1 << col); 320 return true; 321 } 322 } 323 } 324 return false; 325 } 326 327 // Print the matrix values 328 void ec_print_matrix(void) { 329 for (uint8_t row = 0; row < MATRIX_ROWS; row++) { 330 for (uint8_t col = 0; col < MATRIX_COLS - 1; col++) { 331 uprintf("%4d,", sw_value[row][col]); 332 } 333 uprintf("%4d\n", sw_value[row][MATRIX_COLS - 1]); 334 } 335 print("\n"); 336 } 337 338 // Check if the position is unused 339 #ifdef UNUSED_POSITIONS_LIST 340 bool is_unused_position(uint8_t row, uint8_t col) { 341 for (uint8_t i = 0; i < UNUSED_POSITIONS_COUNT; i++) { 342 if (UNUSED_POSITIONS[i][0] == row && UNUSED_POSITIONS[i][1] == col) { 343 return true; 344 } 345 } 346 return false; 347 } 348 #endif 349 350 // Rescale the value to a different range 351 uint16_t rescale(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) { 352 return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 353 }