// Copyright 2023 John Barbero Unenge (@jbarberu) // SPDX-License-Identifier: GPL-2.0-or-later #include "matrix.h" #include "gpio.h" #include "wait.h" #include "string.h" #define SNES_CLOCK GP0 #define SNES_LATCH GP1 #define SNES_D0 GP2 #define SNES_D1 GP3 #define SNES_IO GP4 #define KBD_ROW0 GP24 #define KBD_ROW1 GP23 #define KBD_ROW2 GP22 #define KBD_NUM_ROWS 3 #define KBD_COL0 GP18 #define KBD_COL1 GP19 #define KBD_COL2 GP20 #define KBD_COL3 GP21 #define KBD_ROW_SETUP_DELAY_US 5 // The real snes will clock 16 bits out of the controller, but only really has 12 bits of data #define SNES_DATA_BITS 16 #define SNES_DATA_SETUP_DELAY_US 10 #define SNES_CLOCK_PULSE_DURATION 10 static const int kbd_pin_map[] = { KBD_ROW0, KBD_ROW1, KBD_ROW2 }; void matrix_init_custom(void) { // init snes controller setPinInputHigh(SNES_D0); // todo: look into protocol for other strange snes controllers that use D1 and IO // setPinInputHigh(SNES_D1); // setPinInputHigh(SNES_IO); setPinOutput(SNES_CLOCK); setPinOutput(SNES_LATCH); writePinLow(SNES_CLOCK); writePinLow(SNES_LATCH); // init rows setPinOutput(KBD_ROW0); setPinOutput(KBD_ROW1); setPinOutput(KBD_ROW2); writePinHigh(KBD_ROW0); writePinHigh(KBD_ROW1); writePinHigh(KBD_ROW2); // init columns setPinInputHigh(KBD_COL0); setPinInputHigh(KBD_COL1); setPinInputHigh(KBD_COL2); setPinInputHigh(KBD_COL3); } static matrix_row_t readRow(size_t row, int setupDelay) { const int pin = kbd_pin_map[row]; // select the row setPinOutput(pin); writePinLow(pin); wait_us(setupDelay); // read the column data const matrix_row_t ret = (readPin(KBD_COL0) ? 0 : 1 << 0) | (readPin(KBD_COL1) ? 0 : 1 << 1) | (readPin(KBD_COL2) ? 0 : 1 << 2) | (readPin(KBD_COL3) ? 0 : 1 << 3); // deselect the row setPinOutput(pin); writePinHigh(pin); return ret; } static void readKeyboard(matrix_row_t current_matrix[]) { for (size_t row = 0; row < KBD_NUM_ROWS; ++row) { current_matrix[row] = readRow(row, KBD_ROW_SETUP_DELAY_US); } } static matrix_row_t getBits(uint16_t value, size_t bit0, size_t bit1, size_t bit2, size_t bit3) { matrix_row_t ret = 0; ret |= (value >> bit3) & 1; ret <<= 1; ret |= (value >> bit2) & 1; ret <<= 1; ret |= (value >> bit1) & 1; ret <<= 1; ret |= (value >> bit0) & 1; return ret; } static void readSnesController(matrix_row_t current_matrix[]) { uint16_t controller = 0; writePinHigh(SNES_LATCH); for (size_t bit = 0; bit < SNES_DATA_BITS; ++bit) { // Wait for shift register to setup the data line wait_us(SNES_DATA_SETUP_DELAY_US); // Shift accumulated data and read data pin controller <<= 1; controller |= readPin(SNES_D0) ? 0 : 1; // todo: maybe read D1 and IO here too // Shift next bit in writePinHigh(SNES_CLOCK); wait_us(SNES_CLOCK_PULSE_DURATION); writePinLow(SNES_CLOCK); } writePinLow(SNES_LATCH); controller >>= 4; // SNES button order is pretty random, and we'd like them to be a bit tidier current_matrix[3] = getBits(controller, 1, 0, 8, 9); current_matrix[4] = getBits(controller, 7, 6, 5, 4); current_matrix[5] = getBits(controller, 3, 11, 2, 10); } bool matrix_scan_custom(matrix_row_t current_matrix[]) { const size_t MATRIX_ARRAY_SIZE = MATRIX_ROWS * sizeof(matrix_row_t); // create a copy of the current_matrix, before we read hardware state matrix_row_t last_value[MATRIX_ROWS]; memcpy(last_value, current_matrix, MATRIX_ARRAY_SIZE); // read hardware state into current_matrix readKeyboard(current_matrix); readSnesController(current_matrix); // check if anything changed return memcmp(last_value, current_matrix, MATRIX_ARRAY_SIZE) != 0; }