summary refs log tree commit diff
path: root/drivers
diff options
context:
space:
mode:
authorRyan <fauxpark@gmail.com>2020-04-08 11:04:31 +1000
committerGitHub <noreply@github.com>2020-04-08 11:04:31 +1000
commit400ca2d035d7bdb85c077a53b2cd85cdd04882ab (patch)
treedd6c88d9deee75d52af52ee3d208abfa99072386 /drivers
parente409fb47f27f9cf56479928ed86eb2eb346eec54 (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
Diffstat (limited to 'drivers')
-rw-r--r--drivers/avr/spi_master.c163
-rw-r--r--drivers/avr/spi_master.h57
2 files changed, 220 insertions, 0 deletions
diff --git a/drivers/avr/spi_master.c b/drivers/avr/spi_master.c
new file mode 100644
index 0000000000..497d50536a
--- /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