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
This commit is contained in:
Ryan
2020-04-08 11:04:31 +10:00
committed by Florian Didron
parent 09e02e644e
commit 7777c04547
4 changed files with 259 additions and 124 deletions

163
drivers/avr/spi_master.c Normal file
View File

@@ -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
drivers/avr/spi_master.h Normal file
View File

@@ -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

View File

@@ -29,6 +29,7 @@ ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)
endif endif
ifeq ($(strip $(BLUETOOTH)), AdafruitBLE) ifeq ($(strip $(BLUETOOTH)), AdafruitBLE)
LUFA_SRC += spi_master.c
LUFA_SRC += analog.c LUFA_SRC += analog.c
LUFA_SRC += $(LUFA_DIR)/adafruit_ble.cpp LUFA_SRC += $(LUFA_DIR)/adafruit_ble.cpp
endif endif

View File

@@ -1,15 +1,15 @@
#include "adafruit_ble.h" #include "adafruit_ble.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <alloca.h> #include <alloca.h>
#include <util/delay.h>
#include <util/atomic.h>
#include "debug.h" #include "debug.h"
#include "pincontrol.h"
#include "timer.h" #include "timer.h"
#include "action_util.h" #include "action_util.h"
#include "ringbuffer.hpp" #include "ringbuffer.hpp"
#include <string.h> #include <string.h>
#include "spi_master.h"
#include "wait.h"
#include "analog.h" #include "analog.h"
// These are the pin assignments for the 32u4 boards. // These are the pin assignments for the 32u4 boards.
@@ -27,6 +27,12 @@
# define AdafruitBleIRQPin E6 # define AdafruitBleIRQPin E6
#endif #endif
#ifndef AdafruitBleSpiClockSpeed
# define AdafruitBleSpiClockSpeed 4000000UL // SCK frequency
#endif
#define SCK_DIVISOR (F_CPU / AdafruitBleSpiClockSpeed)
#define SAMPLE_BATTERY #define SAMPLE_BATTERY
#define ConnectionUpdateInterval 1000 /* milliseconds */ #define ConnectionUpdateInterval 1000 /* milliseconds */
@@ -130,10 +136,6 @@ enum ble_system_event_bits {
BleSystemMidiRx = 10, 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 SdepTimeout 150 /* milliseconds */
#define SdepShortTimeout 10 /* milliseconds */ #define SdepShortTimeout 10 /* milliseconds */
#define SdepBackOff 25 /* microseconds */ #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(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); 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 // Send a single SDEP packet
static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) { static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) {
SPI_begin(&spi); spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
digitalWrite(AdafruitBleCSPin, PinLevelLow);
uint16_t timerStart = timer_read(); uint16_t timerStart = timer_read();
bool success = false; bool success = false;
bool ready = false; bool ready = false;
do { do {
ready = SPI_TransferByte(msg->type) != SdepSlaveNotReady; ready = spi_write(msg->type, 100) != SdepSlaveNotReady;
if (ready) { if (ready) {
break; break;
} }
// Release it and let it initialize // Release it and let it initialize
digitalWrite(AdafruitBleCSPin, PinLevelHigh); spi_stop();
_delay_us(SdepBackOff); wait_us(SdepBackOff);
digitalWrite(AdafruitBleCSPin, PinLevelLow); spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
} while (timer_elapsed(timerStart) < timeout); } while (timer_elapsed(timerStart) < timeout);
if (ready) { if (ready) {
// Slave is ready; send the rest of the packet // 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; success = true;
} }
digitalWrite(AdafruitBleCSPin, PinLevelHigh); spi_stop();
return success; return success;
} }
@@ -275,41 +193,39 @@ static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) {
bool ready = false; bool ready = false;
do { do {
ready = digitalRead(AdafruitBleIRQPin); ready = readPin(AdafruitBleIRQPin);
if (ready) { if (ready) {
break; break;
} }
_delay_us(1); wait_us(1);
} while (timer_elapsed(timerStart) < timeout); } while (timer_elapsed(timerStart) < timeout);
if (ready) { if (ready) {
SPI_begin(&spi); spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
digitalWrite(AdafruitBleCSPin, PinLevelLow);
do { do {
// Read the command type, waiting for the data to be ready // 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) { if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) {
// Release it and let it initialize // Release it and let it initialize
digitalWrite(AdafruitBleCSPin, PinLevelHigh); spi_stop();
_delay_us(SdepBackOff); wait_us(SdepBackOff);
digitalWrite(AdafruitBleCSPin, PinLevelLow); spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR);
continue; continue;
} }
// Read the rest of the header // 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 // and get the payload if there is any
if (msg->len <= SdepMaxPayload) { if (msg->len <= SdepMaxPayload) {
spi_recv_bytes(msg->payload, msg->len); spi_receive(msg->payload, msg->len, 100);
} }
success = true; success = true;
break; break;
} while (timer_elapsed(timerStart) < timeout); } while (timer_elapsed(timerStart) < timeout);
digitalWrite(AdafruitBleCSPin, PinLevelHigh); spi_stop();
} }
return success; return success;
} }
@@ -320,7 +236,7 @@ static void resp_buf_read_one(bool greedy) {
return; return;
} }
if (digitalRead(AdafruitBleIRQPin)) { if (readPin(AdafruitBleIRQPin)) {
struct sdep_msg msg; struct sdep_msg msg;
again: 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)); 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; 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()); dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size());
} else { } else {
dprint("failed to send, will retry\n"); dprint("failed to send, will retry\n");
_delay_ms(SdepTimeout); wait_ms(SdepTimeout);
resp_buf_read_one(true); resp_buf_read_one(true);
} }
} }
@@ -382,20 +298,18 @@ static bool ble_init(void) {
state.configured = false; state.configured = false;
state.is_connected = false; state.is_connected = false;
pinMode(AdafruitBleIRQPin, PinDirectionInput); setPinInput(AdafruitBleIRQPin);
pinMode(AdafruitBleCSPin, PinDirectionOutput);
digitalWrite(AdafruitBleCSPin, PinLevelHigh);
SPI_init(&spi); spi_init();
// Perform a hardware reset // Perform a hardware reset
pinMode(AdafruitBleResetPin, PinDirectionOutput); setPinOutput(AdafruitBleResetPin);
digitalWrite(AdafruitBleResetPin, PinLevelHigh); writePinHigh(AdafruitBleResetPin);
digitalWrite(AdafruitBleResetPin, PinLevelLow); writePinLow(AdafruitBleResetPin);
_delay_ms(10); wait_ms(10);
digitalWrite(AdafruitBleResetPin, PinLevelHigh); writePinHigh(AdafruitBleResetPin);
_delay_ms(1000); // Give it a second to initialize wait_ms(1000); // Give it a second to initialize
state.initialized = true; state.initialized = true;
return state.initialized; return state.initialized;
@@ -596,7 +510,7 @@ void adafruit_ble_task(void) {
resp_buf_read_one(true); resp_buf_read_one(true);
send_buf_send_one(SdepShortTimeout); 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 // Must be an event update
if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) { if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) {
uint32_t mask = strtoul(resbuf, NULL, 16); uint32_t mask = strtoul(resbuf, NULL, 16);