summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan <fauxpark@gmail.com>2020-04-08 11:04:31 +1000
committerFlorian Didron <fdidron@users.noreply.github.com>2020-06-12 17:00:27 +0900
commit7777c0454712128670f98a5c7f62da01f7c0aa58 (patch)
tree5e1e4d686e19282aff384fb0de576f325ec3ef4b
parent09e02e644e4a48d1b4ed1bf343da922192f8d7ec (diff)
spi_master for AVR (#8299)
* Change _delay_ms/us() to wait_ms/us() * Switch to platform-agnostic GPIO macros * Add AVR spi_master and migrate Adafruit BLE code * Set verbose back to false * Add clock divisor, bit order and SPI mode configuration for init * Add start and stop functions * Move configuration of mode, endianness and speed to `spi_start()` * Some breaks here would be good * Default Adafruit BLE clock divisor to 4 (2MHz on the Feather 32U4) * Remove mode and divisor enums * Add some docs * No hr at EOF * Add links in sidebar
-rw-r--r--drivers/avr/spi_master.c163
-rw-r--r--drivers/avr/spi_master.h57
-rw-r--r--tmk_core/protocol/lufa.mk1
-rw-r--r--tmk_core/protocol/lufa/adafruit_ble.cpp162
4 files changed, 259 insertions, 124 deletions
diff --git a/drivers/avr/spi_master.c b/drivers/avr/spi_master.c
new file mode 100644
index 0000000000..3f405ddf5c
--- /dev/null
+++ b/drivers/avr/spi_master.c
@@ -0,0 +1,163 @@
+/* Copyright 2020
+ *
+ * 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 <avr/io.h>
+
+#include "spi_master.h"
+#include "quantum.h"
+#include "timer.h"
+
+#if defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)
+# define SPI_SCK_PIN B1
+# define SPI_MOSI_PIN B2
+# define SPI_MISO_PIN B3
+#elif defined(__AVR_ATmega32A__)
+# define SPI_SCK_PIN B7
+# define SPI_MOSI_PIN B5
+# define SPI_MISO_PIN B6
+#elif defined(__AVR_ATmega328P__)
+# define SPI_SCK_PIN B5
+# define SPI_MOSI_PIN B3
+# define SPI_MISO_PIN B4
+#endif
+
+static pin_t currentSlavePin = NO_PIN;
+static uint8_t currentSlaveConfig = 0;
+static bool currentSlave2X = false;
+
+void spi_init(void) {
+ writePinHigh(SPI_SS_PIN);
+ setPinOutput(SPI_SCK_PIN);
+ setPinOutput(SPI_MOSI_PIN);
+ setPinInput(SPI_MISO_PIN);
+
+ SPCR = (_BV(SPE) | _BV(MSTR));
+}
+
+void spi_start(pin_t slavePin, bool lsbFirst, uint8_t mode, uint8_t divisor) {
+ if (currentSlavePin == NO_PIN && slavePin != NO_PIN) {
+ if (lsbFirst) {
+ currentSlaveConfig |= _BV(DORD);
+ }
+
+ switch (mode) {
+ case 1:
+ currentSlaveConfig |= _BV(CPHA);
+ break;
+ case 2:
+ currentSlaveConfig |= _BV(CPOL);
+ break;
+ case 3:
+ currentSlaveConfig |= (_BV(CPOL) | _BV(CPHA));
+ break;
+ }
+
+ uint8_t roundedDivisor = 1;
+ while (roundedDivisor < divisor) {
+ roundedDivisor <<= 1;
+ }
+
+ switch (roundedDivisor) {
+ case 16:
+ currentSlaveConfig |= _BV(SPR0);
+ break;
+ case 64:
+ currentSlaveConfig |= _BV(SPR1);
+ break;
+ case 128:
+ currentSlaveConfig |= (_BV(SPR1) | _BV(SPR0));
+ break;
+ case 2:
+ currentSlave2X = true;
+ break;
+ case 8:
+ currentSlave2X = true;
+ currentSlaveConfig |= _BV(SPR0);
+ break;
+ case 32:
+ currentSlave2X = true;
+ currentSlaveConfig |= _BV(SPR1);
+ break;
+ }
+
+ SPSR |= currentSlaveConfig;
+ currentSlavePin = slavePin;
+ setPinOutput(currentSlavePin);
+ writePinLow(currentSlavePin);
+ }
+}
+
+spi_status_t spi_write(uint8_t data, uint16_t timeout) {
+ SPDR = data;
+
+ uint16_t timeout_timer = timer_read();
+ while (!(SPSR & _BV(SPIF))) {
+ if ((timeout != SPI_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
+ return SPI_STATUS_TIMEOUT;
+ }
+ }
+
+ return SPDR;
+}
+
+spi_status_t spi_read(uint16_t timeout) {
+ SPDR = 0x00; // Dummy
+
+ uint16_t timeout_timer = timer_read();
+ while (!(SPSR & _BV(SPIF))) {
+ if ((timeout != SPI_TIMEOUT_INFINITE) && ((timer_read() - timeout_timer) >= timeout)) {
+ return SPI_STATUS_TIMEOUT;
+ }
+ }
+
+ return SPDR;
+}
+
+spi_status_t spi_transmit(const uint8_t *data, uint16_t length, uint16_t timeout) {
+ spi_status_t status = SPI_STATUS_ERROR;
+
+ for (uint16_t i = 0; i < length; i++) {
+ status = spi_write(data[i], timeout);
+ }
+
+ return status;
+}
+
+spi_status_t spi_receive(uint8_t *data, uint16_t length, uint16_t timeout) {
+ spi_status_t status = SPI_STATUS_ERROR;
+
+ for (uint16_t i = 0; i < length; i++) {
+ status = spi_read(timeout);
+
+ if (status > 0) {
+ data[i] = status;
+ }
+ }
+
+ return (status < 0) ? status : SPI_STATUS_SUCCESS;
+}
+
+void spi_stop(void) {
+ if (currentSlavePin != NO_PIN) {
+ setPinOutput(currentSlavePin);
+ writePinHigh(currentSlavePin);
+ currentSlavePin = NO_PIN;
+ SPCR &= ~(currentSlaveConfig);
+ currentSlaveConfig = 0;
+ SPSR = 0;
+ currentSlave2X = false;
+ }
+}
diff --git a/drivers/avr/spi_master.h b/drivers/avr/spi_master.h
new file mode 100644
index 0000000000..0bab2dc24e
--- /dev/null
+++ b/drivers/avr/spi_master.h
@@ -0,0 +1,57 @@
+/* Copyright 2020
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "quantum.h"
+
+typedef int16_t spi_status_t;
+
+// Hardware SS pin is defined in the header so that user code can refer to it
+#if defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)
+# define SPI_SS_PIN B0
+#elif defined(__AVR_ATmega32A__)
+# define SPI_SS_PIN B4
+#elif defined(__AVR_ATmega328P__)
+# define SPI_SS_PIN B2
+#endif
+
+#define SPI_STATUS_SUCCESS (0)
+#define SPI_STATUS_ERROR (-1)
+#define SPI_STATUS_TIMEOUT (-2)
+
+#define SPI_TIMEOUT_IMMEDIATE (0)
+#define SPI_TIMEOUT_INFINITE (0xFFFF)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void spi_init(void);
+
+void spi_start(pin_t slavePin, bool lsbFirst, uint8_t mode, uint8_t divisor);
+
+spi_status_t spi_write(uint8_t data, uint16_t timeout);
+
+spi_status_t spi_read(uint16_t timeout);
+
+spi_status_t spi_transmit(const uint8_t *data, uint16_t length, uint16_t timeout);
+
+spi_status_t spi_receive(uint8_t *data, uint16_t length, uint16_t timeout);
+
+void spi_stop(void);
+#ifdef __cplusplus
+}
+#endif
diff --git a/tmk_core/protocol/lufa.mk b/tmk_core/protocol/lufa.mk
index d7d1d9ffcc..d87802992e 100644
--- a/tmk_core/protocol/lufa.mk
+++ b/tmk_core/protocol/lufa.mk
@@ -29,6 +29,7 @@ ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)
endif
ifeq ($(strip $(BLUETOOTH)), AdafruitBLE)
+ LUFA_SRC += spi_master.c
LUFA_SRC += analog.c
LUFA_SRC += $(LUFA_DIR)/adafruit_ble.cpp
endif
diff --git a/tmk_core/protocol/lufa/adafruit_ble.cpp b/tmk_core/protocol/lufa/adafruit_ble.cpp
index 505794a80c..764475510d 100644
--- a/tmk_core/protocol/lufa/adafruit_ble.cpp
+++ b/tmk_core/protocol/lufa/adafruit_ble.cpp
@@ -1,15 +1,15 @@
#include "adafruit_ble.h"
+
#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
-#include <util/delay.h>
-#include <util/atomic.h>
#include "debug.h"
-#include "pincontrol.h"
#include "timer.h"
#include "action_util.h"
#include "ringbuffer.hpp"
#include <string.h>
+#include "spi_master.h"
+#include "wait.h"
#include "analog.h"
// These are the pin assignments for the 32u4 boards.
@@ -27,6 +27,12 @@
# define AdafruitBleIRQPin E6
#endif
+#ifndef AdafruitBleSpiClockSpeed
+# define AdafruitBleSpiClockSpeed 4000000UL // SCK frequency
+#endif
+
+#define SCK_DIVISOR (F_CPU / AdafruitBleSpiClockSpeed)
+
#define SAMPLE_BATTERY
#define ConnectionUpdateInterval 1000 /* milliseconds */
@@ -130,10 +136,6 @@ enum ble_system_event_bits {
BleSystemMidiRx = 10,
};
-// The SDEP.md file says 2MHz but the web page and the sample driver
-// both use 4MHz
-#define SpiBusSpeed 4000000
-
#define SdepTimeout 150 /* milliseconds */
#define SdepShortTimeout 10 /* milliseconds */
#define SdepBackOff 25 /* microseconds */
@@ -142,116 +144,32 @@ enum ble_system_event_bits {
static bool at_command(const char *cmd, char *resp, uint16_t resplen, bool verbose, uint16_t timeout = SdepTimeout);
static bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose = false);
-struct SPI_Settings {
- uint8_t spcr, spsr;
-};
-
-static struct SPI_Settings spi;
-
-// Initialize 4Mhz MSBFIRST MODE0
-void SPI_init(struct SPI_Settings *spi) {
- spi->spcr = _BV(SPE) | _BV(MSTR);
-#if F_CPU == 8000000
- // For MCUs running at 8MHz (such as Feather 32U4, or 3.3V Pro Micros) we set the SPI doublespeed bit
- spi->spsr = _BV(SPI2X);
-#endif
-
- ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
- // Ensure that SS is OUTPUT High
- digitalWrite(B0, PinLevelHigh);
- pinMode(B0, PinDirectionOutput);
-
- SPCR |= _BV(MSTR);
- SPCR |= _BV(SPE);
- pinMode(B1 /* SCK */, PinDirectionOutput);
- pinMode(B2 /* MOSI */, PinDirectionOutput);
- }
-}
-
-static inline void SPI_begin(struct SPI_Settings *spi) {
- SPCR = spi->spcr;
- SPSR = spi->spsr;
-}
-
-static inline uint8_t SPI_TransferByte(uint8_t data) {
- SPDR = data;
- asm volatile("nop");
- while (!(SPSR & _BV(SPIF))) {
- ; // wait
- }
- return SPDR;
-}
-
-static inline void spi_send_bytes(const uint8_t *buf, uint8_t len) {
- if (len == 0) return;
- const uint8_t *end = buf + len;
- while (buf < end) {
- SPDR = *buf;
- while (!(SPSR & _BV(SPIF))) {
- ; // wait
- }
- ++buf;
- }
-}
-
-static inline uint16_t spi_read_byte(void) { return SPI_TransferByte(0x00 /* dummy */); }
-
-static inline void spi_recv_bytes(uint8_t *buf, uint8_t len) {
- const uint8_t *end = buf + len;
- if (len == 0) return;
- while (buf < end) {
- SPDR = 0; // write a dummy to initiate read
- while (!(SPSR & _BV(SPIF))) {
- ; // wait
- }
- *buf = SPDR;
- ++buf;
- }
-}
-
-#if 0
-static void dump_pkt(const struct sdep_msg *msg) {
- print("pkt: type=");
- print_hex8(msg->type);
- print(" cmd=");
- print_hex8(msg->cmd_high);
- print_hex8(msg->cmd_low);
- print(" len=");
- print_hex8(msg->len);
- print(" more=");
- print_hex8(msg->more);
- print("\n");
-}
-#endif
-
// Send a single SDEP packet
static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) {
- SPI_begin(&spi);
-
- digitalWrite(AdafruitBleCSPin, PinLevelLow);
+ spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
uint16_t timerStart = timer_read();
bool success = false;
bool ready = false;
do {
- ready = SPI_TransferByte(msg->type) != SdepSlaveNotReady;
+ ready = spi_write(msg->type, 100) != SdepSlaveNotReady;
if (ready) {
break;
}
// Release it and let it initialize
- digitalWrite(AdafruitBleCSPin, PinLevelHigh);
- _delay_us(SdepBackOff);
- digitalWrite(AdafruitBleCSPin, PinLevelLow);
+ spi_stop();
+ wait_us(SdepBackOff);
+ spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
} while (timer_elapsed(timerStart) < timeout);
if (ready) {
// Slave is ready; send the rest of the packet
- spi_send_bytes(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len);
+ spi_transmit(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len, 100);
success = true;
}
- digitalWrite(AdafruitBleCSPin, PinLevelHigh);
+ spi_stop();
return success;
}
@@ -275,41 +193,39 @@ static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) {
bool ready = false;
do {
- ready = digitalRead(AdafruitBleIRQPin);
+ ready = readPin(AdafruitBleIRQPin);
if (ready) {
break;
}
- _delay_us(1);
+ wait_us(1);
} while (timer_elapsed(timerStart) < timeout);
if (ready) {
- SPI_begin(&spi);
-
- digitalWrite(AdafruitBleCSPin, PinLevelLow);
+ spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
do {
// Read the command type, waiting for the data to be ready
- msg->type = spi_read_byte();
+ msg->type = spi_read(100);
if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) {
// Release it and let it initialize
- digitalWrite(AdafruitBleCSPin, PinLevelHigh);
- _delay_us(SdepBackOff);
- digitalWrite(AdafruitBleCSPin, PinLevelLow);
+ spi_stop();
+ wait_us(SdepBackOff);
+ spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
continue;
}
// Read the rest of the header
- spi_recv_bytes(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)));
+ spi_receive(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)), 100);
// and get the payload if there is any
if (msg->len <= SdepMaxPayload) {
- spi_recv_bytes(msg->payload, msg->len);
+ spi_receive(msg->payload, msg->len, 100);
}
success = true;
break;
} while (timer_elapsed(timerStart) < timeout);
- digitalWrite(AdafruitBleCSPin, PinLevelHigh);
+ spi_stop();
}
return success;
}
@@ -320,7 +236,7 @@ static void resp_buf_read_one(bool greedy) {
return;
}
- if (digitalRead(AdafruitBleIRQPin)) {
+ if (readPin(AdafruitBleIRQPin)) {
struct sdep_msg msg;
again:
@@ -331,7 +247,7 @@ static void resp_buf_read_one(bool greedy) {
dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send));
}
- if (greedy && resp_buf.peek(last_send) && digitalRead(AdafruitBleIRQPin)) {
+ if (greedy && resp_buf.peek(last_send) && readPin(AdafruitBleIRQPin)) {
goto again;
}
}
@@ -361,7 +277,7 @@ static void send_buf_send_one(uint16_t timeout = SdepTimeout) {
dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size());
} else {
dprint("failed to send, will retry\n");
- _delay_ms(SdepTimeout);
+ wait_ms(SdepTimeout);
resp_buf_read_one(true);
}
}
@@ -382,20 +298,18 @@ static bool ble_init(void) {
state.configured = false;
state.is_connected = false;
- pinMode(AdafruitBleIRQPin, PinDirectionInput);
- pinMode(AdafruitBleCSPin, PinDirectionOutput);
- digitalWrite(AdafruitBleCSPin, PinLevelHigh);
+ setPinInput(AdafruitBleIRQPin);
- SPI_init(&spi);
+ spi_init();
// Perform a hardware reset
- pinMode(AdafruitBleResetPin, PinDirectionOutput);
- digitalWrite(AdafruitBleResetPin, PinLevelHigh);
- digitalWrite(AdafruitBleResetPin, PinLevelLow);
- _delay_ms(10);
- digitalWrite(AdafruitBleResetPin, PinLevelHigh);
+ setPinOutput(AdafruitBleResetPin);
+ writePinHigh(AdafruitBleResetPin);
+ writePinLow(AdafruitBleResetPin);
+ wait_ms(10);
+ writePinHigh(AdafruitBleResetPin);
- _delay_ms(1000); // Give it a second to initialize
+ wait_ms(1000); // Give it a second to initialize
state.initialized = true;
return state.initialized;
@@ -596,7 +510,7 @@ void adafruit_ble_task(void) {
resp_buf_read_one(true);
send_buf_send_one(SdepShortTimeout);
- if (resp_buf.empty() && (state.event_flags & UsingEvents) && digitalRead(AdafruitBleIRQPin)) {
+ if (resp_buf.empty() && (state.event_flags & UsingEvents) && readPin(AdafruitBleIRQPin)) {
// Must be an event update
if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) {
uint32_t mask = strtoul(resbuf, NULL, 16);