summaryrefslogtreecommitdiff
path: root/keyboards/snes_macropad/matrix.c
blob: 28d036aca9ca3fc4fa408cac47ce5e8e1e7b8be0 (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
// 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;
}