summaryrefslogtreecommitdiff
path: root/keyboards/cipulot/ec_typek/ec_switch_matrix.c
blob: da58a75bbcf304c91238b602e96703767b7b0799 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/* 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 <http://www.gnu.org/licenses/>.
 */

#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};

#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 ec_config_t config;
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]);
    }
}

// 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++) {
                    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++) {
            for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
                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;
                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(&current_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.mode_1_release_offset) {
                    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.mode_1_actuation_offset) {
                    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");
}

// 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;
}