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