summaryrefslogtreecommitdiff
path: root/keyboards/handwired/lagrange/transport.c
blob: 8f6973925ff21566d08d4afd474d376743270765 (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
/* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

#include <spi_master.h>

#include "quantum.h"
#include "split_util.h"
#include "transport.h"
#include "timer.h"

#include "lagrange.h"

struct led_context {
    led_t led_state;
    layer_state_t layer_state;
};

uint8_t transceive(uint8_t b) {
    for (SPDR = b ; !(SPSR & _BV(SPIF)) ; );
    return SPDR;
}

/* The SPI bus, doesn't have any form of protocol built in, so when
 * the other side isn't present, any old noise on the line will appear
 * as matrix data.  To avoid interpreting data as keystrokes, we do a
 * simple n-way (8-way here) handshake before each scan, where each
 * side sends a prearranged sequence of bytes. */

bool shake_hands(bool master) {
    const uint8_t m = master ? 0xf8 : 0;
    const uint8_t a = 0xa8 ^ m, b = 0x50 ^ m;
    bool synchronized = true;

    uint8_t i;

    i = SPSR;
    i = SPDR;

    do {
        /* Cycling the SS pin on each attempt is necessary, as it
         * resets the AVR's SPI core and guarantees proper
         * alignment. */

        if (master) {
            writePinLow(SPI_SS_PIN);
        }

        for (i = 0 ; i < 8 ; i += 1) {
            if (transceive(a + i) != b + i) {
                synchronized = false;
                break;
            }
        }

        if (master) {
            writePinHigh(SPI_SS_PIN);
        }
    } while (i < 8);

    return synchronized;
}

bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    const struct led_context context = {
        host_keyboard_led_state(),
        layer_state
    };

    uint8_t i;

    /* We shake hands both before and after transmitting the matrix.
     * Doing it before transmitting is necessary to ensure
     * synchronization: Due to the master-slave nature of the SPI bus,
     * the master calls the shots.  If we just go ahead and start
     * clocking bits, the slave side might be otherwise engaged at
     * that moment, so we'll initially read zeros, or garbage.  Then
     * when the slave gets around to transmitting its matrix, we'll
     * misinterpret the keys it sends, leading to spurious
     * keypresses. */

    /* The handshake forces the master to wait for the slave to be
     * ready to start transmitting. */

    do {
        shake_hands(true);

        /* Receive the matrix from the other side, while transmitting
         * LED and layer states. */

        spi_start(SPI_SS_PIN, 0, 0, 4);

        for (i = 0 ; i < sizeof(matrix_row_t[MATRIX_ROWS / 2]) ; i += 1) {
            spi_status_t x;

            x = spi_write(i < sizeof(struct led_context) ?
                          ((uint8_t *)&context)[i] : 0);

            if (x == SPI_STATUS_TIMEOUT) {
                return false;
            }

            ((uint8_t *)slave_matrix)[i] = (uint8_t)x;
        }

        spi_stop();

        /* In case of errors during the transmission, e.g. if the
         * cable was disconnected and since there is no inherent
         * error-checking protocol, we would simply interpret noise as
         * data. */

        /* To avoid this, both sides shake hands after transmitting.
         * If synchronization was lost during transmission, the (first)
         * handshake will fail.  In that case we go around and
         * re-transmit. */

    } while (!shake_hands(true));

    return true;
}

void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
    static struct led_context context;
    struct led_context new_context;

    uint8_t i;

    /* Do the reverse of master above.  Note that timing is critical,
     * so interrupts must be turned off. */

    cli();
    shake_hands(false);

    do {
        for (i = 0 ; i < sizeof(matrix_row_t[MATRIX_ROWS / 2]) ; i += 1) {
            uint8_t b;

            b = transceive(((uint8_t *)slave_matrix)[i]);

            if (i < sizeof(struct led_context)) {
                ((uint8_t *)&new_context)[i] = b;
            }
        }
    } while (!shake_hands(false));

    sei();

    /* Update the layer and LED state if necessary. */

    if (!isLeftHand) {
        if (context.led_state.raw != new_context.led_state.raw) {
            context.led_state.raw = new_context.led_state.raw;
            led_update_kb(context.led_state);
        }

        if (context.layer_state != new_context.layer_state) {
            context.layer_state = new_context.layer_state;
            layer_state_set_kb(context.layer_state);
        }
    }
}

void transport_master_init(void) {
    /* We need to set the SS pin as output as the handshake logic
     * above depends on it and the SPI master driver won't do it
     * before we call spi_start(). */

    writePinHigh(SPI_SS_PIN);
    setPinOutput(SPI_SS_PIN);

    spi_init();

    shake_hands(true);
}

void transport_slave_init(void) {
    /* The datasheet isn't very clear on whether the internal pull-up
     * is selectable when the SS pin is used by the SPI slave, but
     * experimentations shows that it is, at least on the ATMega32u4.
     * We enable the pull-up to guard against the case where both
     * halves end up as slaves.  In that case the SS pin would
     * otherwise be floating and free to fluctuate due to picked up
     * noise, etc. When reading low it would make both halves think
     * they're asserted making the MISO pin an output on both ends and
     * leading to potential shorts. */

    setPinInputHigh(SPI_SS_PIN);
    setPinInput(SPI_SCK_PIN);
    setPinInput(SPI_MOSI_PIN);
    setPinOutput(SPI_MISO_PIN);

    SPCR = _BV(SPE);

    shake_hands(false);
}