Skip to content

Commit

Permalink
spi_master for AVR (qmk#8299)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
fauxpark authored and drashna committed May 24, 2020
1 parent e76a154 commit 0e59be9
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 124 deletions.
163 changes: 163 additions & 0 deletions drivers/avr/spi_master.c
Original file line number Diff line number Diff line change
@@ -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;
}
}
57 changes: 57 additions & 0 deletions drivers/avr/spi_master.h
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tmk_core/protocol/lufa.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 0e59be9

Please sign in to comment.