-
Notifications
You must be signed in to change notification settings - Fork 456
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add PIO-based SoftwareSPI enabling SPI on any pins (#2778)
* Add PIO-based SoftwareSPI enabling SPI on any pins The Raspberry Pi team has a working PIO-based SPI interface. Wrap it to work like a hardware SPI interface, allowing SPI on any pin combination. Tested reading and writing an SD card using unmodified SD library. * Add W5500 example Good for testing, shows non-contiguous pin outs.
1 parent
a426fbf
commit acf81f4
Showing
11 changed files
with
1,110 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
libraries/SoftwareSPI/examples/FilesSoftwareSPI/FilesSoftwareSPI.ino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
SD card basic file example with Software SPI | ||
This example shows how to create and destroy an SD card file | ||
The circuit: | ||
SD card attached to Pico as follows: | ||
** SCK - GPIO0 | ||
** CS - GPIO1 | ||
** MISO (AKA RX) - GPIO2 | ||
** MOSI (AKA TX) - GPIO3 | ||
created Nov 2010 | ||
by David A. Mellis | ||
modified 9 Apr 2012 | ||
by Tom Igoe | ||
This example code is in the public domain. | ||
*/ | ||
|
||
#include <SoftwareSPI.h> | ||
|
||
const int _SCK = 0; | ||
const int _CS = 1; // Must be SCK+1 for HW CS support | ||
const int _MISO = 2; | ||
const int _MOSI = 3; | ||
SoftwareSPI softSPI(_SCK, _MISO, _MOSI, _CS); | ||
|
||
#include <SD.h> | ||
|
||
File myFile; | ||
|
||
void setup() { | ||
// Open serial communications and wait for port to open: | ||
Serial.begin(115200); | ||
|
||
do { | ||
delay(100); // wait for serial port to connect. Needed for native USB port only | ||
} while (!Serial); | ||
|
||
Serial.print("Initializing SD card..."); | ||
|
||
bool sdInitialized = false; | ||
sdInitialized = SD.begin(_CS, softSPI); | ||
if (!sdInitialized) { | ||
Serial.println("initialization failed!"); | ||
return; | ||
} | ||
Serial.println("initialization done."); | ||
|
||
if (SD.exists("example.txt")) { | ||
Serial.println("example.txt exists."); | ||
} else { | ||
Serial.println("example.txt doesn't exist."); | ||
} | ||
|
||
// open a new file and immediately close it: | ||
Serial.println("Creating example.txt..."); | ||
myFile = SD.open("example.txt", FILE_WRITE); | ||
myFile.close(); | ||
|
||
// Check to see if the file exists: | ||
if (SD.exists("example.txt")) { | ||
Serial.println("example.txt exists."); | ||
} else { | ||
Serial.println("example.txt doesn't exist."); | ||
} | ||
|
||
// delete the file: | ||
Serial.println("Removing example.txt..."); | ||
SD.remove("example.txt"); | ||
|
||
if (SD.exists("example.txt")) { | ||
Serial.println("example.txt exists."); | ||
} else { | ||
Serial.println("example.txt doesn't exist."); | ||
} | ||
} | ||
|
||
void loop() { | ||
// nothing happens after setup finishes. | ||
} | ||
|
||
|
||
|
98 changes: 98 additions & 0 deletions
98
libraries/SoftwareSPI/examples/W5500SoftwareSPI/W5500SoftwareSPI.ino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
This sketch establishes a TCP connection to a "quote of the day" service. | ||
It sends a "hello" message, and then prints received data. | ||
*/ | ||
|
||
#include <W5500lwIP.h> | ||
|
||
const char* host = "djxmmx.net"; | ||
const uint16_t port = 17; | ||
|
||
#include <SoftwareSPI.h> | ||
const int _SCK = 0; // Any pin allowed | ||
const int _CS = 1; // Must be SCK+1 for HW CS support | ||
const int _MISO = 28; // Note that MOSI and MISO don't need to be contiguous. Any pins allowed | ||
const int _MOSI = 3; // Any pin not used elsewhere | ||
const int _INT = 4; // W5500 IRQ line | ||
|
||
SoftwareSPI softSPI(_SCK, _MISO, _MOSI, _CS); | ||
|
||
Wiznet5500lwIP eth(_CS, softSPI, _INT); | ||
|
||
void setup() { | ||
Serial.begin(115200); | ||
delay(5000); | ||
Serial.println(); | ||
Serial.println(); | ||
Serial.println("Starting Ethernet port"); | ||
|
||
// Start the Ethernet port | ||
if (!eth.begin()) { | ||
Serial.println("No wired Ethernet hardware detected. Check pinouts, wiring."); | ||
while (1) { | ||
delay(1000); | ||
} | ||
} | ||
|
||
while (!eth.connected()) { | ||
Serial.print("."); | ||
delay(500); | ||
} | ||
|
||
Serial.println(""); | ||
Serial.println("Ethernet connected"); | ||
Serial.println("IP address: "); | ||
Serial.println(eth.localIP()); | ||
} | ||
|
||
void loop() { | ||
static bool wait = false; | ||
|
||
Serial.print("connecting to "); | ||
Serial.print(host); | ||
Serial.print(':'); | ||
Serial.println(port); | ||
|
||
// Use WiFiClient class to create TCP connections | ||
WiFiClient client; | ||
if (!client.connect(host, port)) { | ||
Serial.println("connection failed"); | ||
delay(5000); | ||
return; | ||
} | ||
|
||
// This will send a string to the server | ||
Serial.println("sending data to server"); | ||
if (client.connected()) { | ||
client.println("hello from RP2040"); | ||
} | ||
|
||
// wait for data to be available | ||
unsigned long timeout = millis(); | ||
while (client.available() == 0) { | ||
if (millis() - timeout > 5000) { | ||
Serial.println(">>> Client Timeout !"); | ||
client.stop(); | ||
delay(60000); | ||
return; | ||
} | ||
} | ||
|
||
// Read all the lines of the reply from server and print them to Serial | ||
Serial.println("receiving from remote server"); | ||
// not testing 'client.connected()' since we do not need to send data here | ||
while (client.available()) { | ||
char ch = static_cast<char>(client.read()); | ||
Serial.print(ch); | ||
} | ||
|
||
// Close the connection | ||
Serial.println(); | ||
Serial.println("closing connection"); | ||
client.stop(); | ||
|
||
if (wait) { | ||
delay(300000); // execute once every 5 minutes, don't flood remote service | ||
} | ||
wait = true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
####################################### | ||
# Syntax Coloring Map SPI | ||
####################################### | ||
|
||
####################################### | ||
# Instances (KEYWORD2) | ||
####################################### | ||
|
||
SoftwareSPI KEYWORD1 | ||
|
||
####################################### | ||
# Methods and Functions (KEYWORD2) | ||
####################################### | ||
begin KEYWORD2 | ||
end KEYWORD2 | ||
beginTransaction KEYWORD2 | ||
endTransaction KEYWORD2 | ||
SPISettings KEYWORD2 | ||
transfer KEYWORD2 | ||
transfer16 KEYWORD2 | ||
setBitOrder KEYWORD2 | ||
setDataMode KEYWORD2 | ||
setClockDivider KEYWORD2 | ||
setSCK KEYWORD2 | ||
setMOSI KEYWORD2 | ||
setMISO KEYWORD2 | ||
setCS KEYWORD2 | ||
|
||
####################################### | ||
# Constants (LITERAL1) | ||
####################################### | ||
SPI_CLOCK_DIV4 LITERAL1 | ||
SPI_CLOCK_DIV16 LITERAL1 | ||
SPI_CLOCK_DIV64 LITERAL1 | ||
SPI_CLOCK_DIV128 LITERAL1 | ||
SPI_CLOCK_DIV2 LITERAL1 | ||
SPI_CLOCK_DIV8 LITERAL1 | ||
SPI_CLOCK_DIV32 LITERAL1 | ||
SPI_CLOCK_DIV64 LITERAL1 | ||
SPI_MODE0 LITERAL1 | ||
SPI_MODE1 LITERAL1 | ||
SPI_MODE2 LITERAL1 | ||
SPI_MODE3 LITERAL1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
name=SoftwareSPI | ||
version=1.0 | ||
author=Earle F. Philhower, III <earlephilhower@yahoo.com> | ||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com> | ||
sentence=Uses the PIO to provide an SPI interface on any pin. | ||
paragraph= | ||
category=Signal Input/Output | ||
url=http://arduino.cc/en/Reference/SPI | ||
architectures=rp2040 | ||
dot_a_linkage=true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,365 @@ | ||
/* | ||
PIO-based SPI Master library for the Raspberry Pi Pico RP2040 | ||
Copyright (c) 2025 Earle F. Philhower, III <earlephilhower@yahoo.com> | ||
This library is free software; you can redistribute it and/or | ||
modify it under the terms of the GNU Lesser General Public | ||
License as published by the Free Software Foundation; either | ||
version 2.1 of the License, or (at your option) any later version. | ||
This library 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 | ||
Lesser General Public License for more details. | ||
You should have received a copy of the GNU Lesser General Public | ||
License along with this library; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#include "SoftwareSPI.h" | ||
#include <hardware/gpio.h> | ||
#include <hardware/structs/iobank0.h> | ||
#include <hardware/irq.h> | ||
#include "spi.pio.h" | ||
|
||
#ifdef USE_TINYUSB | ||
// For Serial when selecting TinyUSB. Can't include in the core because Arduino IDE | ||
// will not link in libraries called from the core. Instead, add the header to all | ||
// the standard libraries in the hope it will still catch some user cases where they | ||
// use these libraries. | ||
// See https://github.com/earlephilhower/arduino-pico/issues/167#issuecomment-848622174 | ||
#include <Adafruit_TinyUSB.h> | ||
#endif | ||
|
||
SoftwareSPI::SoftwareSPI(pin_size_t sck, pin_size_t miso, pin_size_t mosi, pin_size_t cs) { | ||
_running = false; | ||
_initted = false; | ||
_spis = SPISettings(1, LSBFIRST, SPI_MODE0); // Ensure spi_init called by setting current freq to 0 | ||
_sck = sck; | ||
_miso = miso; | ||
_mosi = mosi; | ||
_cs = cs; | ||
} | ||
|
||
inline spi_cpol_t SoftwareSPI::cpol() { | ||
switch (_spis.getDataMode()) { | ||
case SPI_MODE0: | ||
return SPI_CPOL_0; | ||
case SPI_MODE1: | ||
return SPI_CPOL_0; | ||
case SPI_MODE2: | ||
return SPI_CPOL_1; | ||
case SPI_MODE3: | ||
return SPI_CPOL_1; | ||
} | ||
// Error | ||
return SPI_CPOL_0; | ||
} | ||
|
||
inline spi_cpha_t SoftwareSPI::cpha() { | ||
switch (_spis.getDataMode()) { | ||
case SPI_MODE0: | ||
return SPI_CPHA_0; | ||
case SPI_MODE1: | ||
return SPI_CPHA_1; | ||
case SPI_MODE2: | ||
return SPI_CPHA_0; | ||
case SPI_MODE3: | ||
return SPI_CPHA_1; | ||
} | ||
// Error | ||
return SPI_CPHA_0; | ||
} | ||
|
||
inline uint8_t SoftwareSPI::reverseByte(uint8_t b) { | ||
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; | ||
b = (b & 0xCC) >> 2 | (b & 0x33) << 2; | ||
b = (b & 0xAA) >> 1 | (b & 0x55) << 1; | ||
return b; | ||
} | ||
|
||
inline uint16_t SoftwareSPI::reverse16Bit(uint16_t w) { | ||
return (reverseByte(w & 0xff) << 8) | (reverseByte(w >> 8)); | ||
} | ||
|
||
// The HW can't do LSB first, only MSB first, so need to bitreverse | ||
void SoftwareSPI::adjustBuffer(const void *s, void *d, size_t cnt, bool by16) { | ||
if (_spis.getBitOrder() == MSBFIRST) { | ||
memcpy(d, s, cnt * (by16 ? 2 : 1)); | ||
} else if (!by16) { | ||
const uint8_t *src = (const uint8_t *)s; | ||
uint8_t *dst = (uint8_t *)d; | ||
for (size_t i = 0; i < cnt; i++) { | ||
*(dst++) = reverseByte(*(src++)); | ||
} | ||
} else { /* by16 */ | ||
const uint16_t *src = (const uint16_t *)s; | ||
uint16_t *dst = (uint16_t *)d; | ||
for (size_t i = 0; i < cnt; i++) { | ||
*(dst++) = reverse16Bit(*(src++)); | ||
} | ||
} | ||
} | ||
|
||
void SoftwareSPI::_adjustPIO(int bits) { | ||
if (_bits == bits) { | ||
return; // Nothing to do! | ||
} | ||
// Manually set the shiftctl and possibly Y for 8 bits | ||
pio_sm_set_enabled(_pio, _sm, false); | ||
uint32_t v = _pio->sm[_sm].shiftctrl ; | ||
v &= ~0x3e000000 | ~0x01f00000; | ||
if (bits == 8) { | ||
v |= 0x108 << 20; // Hardcode push/pull threshold 0'b0100001000, there is no simple accessor I can find | ||
} else { | ||
v |= 0x210 << 20; // 0b'1000010000 | ||
} | ||
_pio->sm[_sm].shiftctrl = v; | ||
if (_hwCS) { | ||
pio_sm_exec(_pio, _sm, pio_encode_set(pio_x, bits - 2)); | ||
pio_sm_exec(_pio, _sm, pio_encode_set(pio_y, bits - 2)); | ||
} | ||
pio_sm_set_enabled(_pio, _sm, true); | ||
_bits = bits; | ||
} | ||
|
||
byte SoftwareSPI::transfer(uint8_t data) { | ||
uint8_t ret; | ||
if (!_initted) { | ||
return 0; | ||
} | ||
data = (_spis.getBitOrder() == MSBFIRST) ? data : reverseByte(data); | ||
DEBUGSPI("SPI::transfer(%02x), cpol=%d, cpha=%d\n", data, cpol(), cpha()); | ||
_adjustPIO(8); | ||
io_rw_8 *txfifo = (io_rw_8 *) &_pio->txf[_sm]; | ||
io_rw_8 *rxfifo = (io_rw_8 *) &_pio->rxf[_sm]; | ||
while (pio_sm_is_tx_fifo_full(_pio, _sm)) { /* noop wait */ } | ||
*txfifo = data; | ||
while (pio_sm_is_rx_fifo_empty(_pio, _sm)) { /* noop wait for in data */ } | ||
ret = *rxfifo; | ||
ret = (_spis.getBitOrder() == MSBFIRST) ? ret : reverseByte(ret); | ||
DEBUGSPI("SPI: read back %02x\n", ret); | ||
return ret; | ||
} | ||
|
||
uint16_t SoftwareSPI::transfer16(uint16_t data) { | ||
uint16_t ret; | ||
if (!_initted) { | ||
return 0; | ||
} | ||
data = (_spis.getBitOrder() == MSBFIRST) ? data : reverse16Bit(data); | ||
DEBUGSPI("SPI::transfer16(%04x), cpol=%d, cpha=%d\n", data, cpol(), cpha()); | ||
_adjustPIO(16); | ||
io_rw_16 *txfifo = (io_rw_16 *) &_pio->txf[_sm]; | ||
io_rw_16 *rxfifo = (io_rw_16 *) &_pio->rxf[_sm]; | ||
while (pio_sm_is_tx_fifo_full(_pio, _sm)) { /* noop wait */ } | ||
*txfifo = data; | ||
while (pio_sm_is_rx_fifo_empty(_pio, _sm)) { /* noop wait for in data */ } | ||
ret = *rxfifo; | ||
ret = (_spis.getBitOrder() == MSBFIRST) ? ret : reverse16Bit(ret); | ||
DEBUGSPI("SPI: read back %04x\n", ret); | ||
return ret; | ||
} | ||
|
||
void SoftwareSPI::transfer(void *buf, size_t count) { | ||
transfer(buf, buf, count); | ||
} | ||
|
||
void SoftwareSPI::transfer(const void *csrc, void *cdest, size_t count) { | ||
if (!_initted) { | ||
return; | ||
} | ||
DEBUGSPI("SPI::transfer(%p, %p %d)\n", csrc, cdest, count); | ||
const uint8_t *src = reinterpret_cast<const uint8_t *>(csrc); | ||
uint8_t *dest = reinterpret_cast<uint8_t *>(cdest); | ||
_adjustPIO(8); | ||
io_rw_8 *txfifo = (io_rw_8 *) &_pio->txf[_sm]; | ||
io_rw_8 *rxfifo = (io_rw_8 *) &_pio->rxf[_sm]; | ||
int txleft = count; | ||
int rxleft = count; | ||
|
||
if (_spis.getBitOrder() == !MSBFIRST) { | ||
// We're going to hack like heck here and reverse the txbuf into the receive buff (because txbuff is const | ||
// Then by construction SPI will send before it received, we can use the rx buff to trans and recv | ||
for (size_t i = 0; i < count; i++) { | ||
dest[i] = reverseByte(src[i]); | ||
} | ||
src = dest; // We'll transmit the flipped data... | ||
} | ||
|
||
while (txleft || rxleft) { | ||
while (txleft && !pio_sm_is_tx_fifo_full(_pio, _sm)) { | ||
*txfifo = *src++; | ||
txleft--; | ||
} | ||
while (rxleft && !pio_sm_is_rx_fifo_empty(_pio, _sm)) { | ||
*dest++ = *rxfifo; | ||
rxleft--; | ||
} | ||
} | ||
|
||
if (_spis.getBitOrder() == !MSBFIRST) { | ||
// Now we have data in recv but also need to flip it before returning to the app | ||
for (size_t i = 0; i < count; i++) { | ||
dest[i] = reverseByte(dest[i]); | ||
} | ||
} | ||
DEBUGSPI("SPI::transfer completed\n"); | ||
} | ||
|
||
#ifdef PICO_RP2350B | ||
#define GPIOIRQREGS 6 | ||
#else | ||
#define GPIOIRQREGS 4 | ||
#endif | ||
|
||
void SoftwareSPI::beginTransaction(SPISettings settings) { | ||
noInterrupts(); // Avoid possible race conditions if IRQ comes in while main app is in middle of this | ||
DEBUGSPI("SPI::beginTransaction(clk=%lu, bo=%s)\n", settings.getClockFreq(), (settings.getBitOrder() == MSBFIRST) ? "MSB" : "LSB"); | ||
if (_initted && settings == _spis) { | ||
DEBUGSPI("SPI: Reusing existing initted SPI\n"); | ||
} else { | ||
/* Only de-init if the clock changes frequency */ | ||
if (settings.getClockFreq() != _spis.getClockFreq()) { | ||
DEBUGSPI("SPI: initting SPI\n"); | ||
float divider = (float)rp2040.f_cpu() / (float)settings.getClockFreq(); | ||
divider /= _hwCS ? 4.0f : 4.0f; | ||
pio_sm_set_clkdiv(_pio, _sm, divider); | ||
DEBUGSPI("SPI: divider=%f\n", divider); | ||
} | ||
_spis = settings; | ||
// Note we can only change frequency, not CPOL/CPHA (which would be physically not too useful anyway) | ||
_initted = true; | ||
} | ||
// Disable any IRQs that are being used for SPI | ||
io_bank0_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; | ||
DEBUGSPI("SPI: IRQ masks before = %08x %08x %08x %08x %08x %08x\n", (unsigned)irq_ctrl_base->inte[0], | ||
(unsigned)irq_ctrl_base->inte[1], (unsigned)irq_ctrl_base->inte[2], (unsigned)irq_ctrl_base->inte[3], | ||
(GPIOIRQREGS > 4) ? (unsigned)irq_ctrl_base->inte[4] : 0, (GPIOIRQREGS > 5) ? (unsigned)irq_ctrl_base->inte[5] : 0); | ||
for (auto entry : _usingIRQs) { | ||
int gpio = entry.first; | ||
|
||
// There is no gpio_get_irq, so manually twiddle the register | ||
io_rw_32 *en_reg = &irq_ctrl_base->inte[gpio / 8]; | ||
uint32_t val = ((*en_reg) >> (4 * (gpio % 8))) & 0xf; | ||
_usingIRQs.insert_or_assign(gpio, val); | ||
DEBUGSPI("SPI: GPIO %d = %lu\n", gpio, val); | ||
(*en_reg) ^= val << (4 * (gpio % 8)); | ||
} | ||
DEBUGSPI("SPI: IRQ masks after = %08x %08x %08x %08x %08x %08x\n", (unsigned)irq_ctrl_base->inte[0], | ||
(unsigned)irq_ctrl_base->inte[1], (unsigned)irq_ctrl_base->inte[2], (unsigned)irq_ctrl_base->inte[3], | ||
(GPIOIRQREGS > 4) ? (unsigned)irq_ctrl_base->inte[4] : 0, (GPIOIRQREGS > 5) ? (unsigned)irq_ctrl_base->inte[5] : 0); | ||
interrupts(); | ||
} | ||
|
||
void SoftwareSPI::endTransaction(void) { | ||
noInterrupts(); // Avoid race condition so the GPIO IRQs won't come back until all state is restored | ||
DEBUGSPI("SPI::endTransaction()\n"); | ||
// Re-enable IRQs | ||
for (auto entry : _usingIRQs) { | ||
int gpio = entry.first; | ||
int mode = entry.second; | ||
gpio_set_irq_enabled(gpio, mode, true); | ||
} | ||
io_bank0_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; | ||
(void) irq_ctrl_base; | ||
DEBUGSPI("SPI: IRQ masks = %08x %08x %08x %08x %08x %08x\n", (unsigned)irq_ctrl_base->inte[0], (unsigned)irq_ctrl_base->inte[1], | ||
(unsigned)irq_ctrl_base->inte[2], (unsigned)irq_ctrl_base->inte[3], (GPIOIRQREGS > 4) ? (unsigned)irq_ctrl_base->inte[4] : 0, | ||
(GPIOIRQREGS > 5) ? (unsigned)irq_ctrl_base->inte[5] : 0); | ||
interrupts(); | ||
} | ||
|
||
bool SoftwareSPI::setCS(pin_size_t pin) { | ||
if (pin < 1) { | ||
// CS is SCK+1, so has to be at least GPIO1 | ||
return false; | ||
} | ||
if (!_running || (_cs == pin)) { | ||
_cs = pin; | ||
_sck = _cs - 1; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
bool SoftwareSPI::setSCK(pin_size_t pin) { | ||
if (!_running || (_sck == pin)) { | ||
_sck = pin; | ||
_cs = pin + 1; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
bool SoftwareSPI::setMISO(pin_size_t pin) { | ||
if (!_running || (_miso == pin)) { | ||
_miso = pin; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
bool SoftwareSPI::setMOSI(pin_size_t pin) { | ||
if (!_running || (_mosi == pin)) { | ||
_mosi = pin; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
void SoftwareSPI::begin(bool hwCS) { | ||
DEBUGSPI("SPI::begin(%d), rx=%d, cs=%d, sck=%d, tx=%d\n", hwCS, _miso, _cs, _sck, _mosi); | ||
float divider = (float)rp2040.f_cpu() / (float)_spis.getClockFreq(); | ||
DEBUGSPI("SPI: divider=%f\n", divider); | ||
if (!hwCS) { | ||
_spi = new PIOProgram(cpha() == SPI_CPHA_0 ? &spi_cpha0_program : &spi_cpha1_program); | ||
if (!_spi->prepare(&_pio, &_sm, &_off, _sck, 1)) { | ||
_running = false; | ||
delete _spi; | ||
_spi = nullptr; | ||
return; | ||
} | ||
pio_spi_init(_pio, _sm, _off, 8, divider / 4.0f, cpha(), cpol(), _sck, _mosi, _miso); | ||
} else { | ||
_spi = new PIOProgram(cpha() == SPI_CPHA_0 ? &spi_cpha0_cs_program : &spi_cpha1_cs_program); | ||
if (!_spi->prepare(&_pio, &_sm, &_off, _sck, 2)) { | ||
_running = false; | ||
delete _spi; | ||
_spi = nullptr; | ||
return; | ||
} | ||
pio_spi_cs_init(_pio, _sm, _off, 8, divider / 4.0f, cpha(), cpol(), _sck, _mosi, _miso); | ||
} | ||
_hwCS = hwCS; | ||
_bits = 8; | ||
// Give a default config in case user doesn't use beginTransaction | ||
beginTransaction(_spis); | ||
endTransaction(); | ||
} | ||
|
||
void SoftwareSPI::end() { | ||
DEBUGSPI("SPI::end()\n"); | ||
if (_initted) { | ||
DEBUGSPI("SPI: deinitting currently active SPI\n"); | ||
_initted = false; | ||
} | ||
_spis = SPISettings(0, LSBFIRST, SPI_MODE0); | ||
} | ||
|
||
void SoftwareSPI::setBitOrder(BitOrder order) { | ||
_spis = SPISettings(_spis.getClockFreq(), order, _spis.getDataMode()); | ||
beginTransaction(_spis); | ||
endTransaction(); | ||
} | ||
|
||
void SoftwareSPI::setDataMode(uint8_t uc_mode) { | ||
_spis = SPISettings(_spis.getClockFreq(), _spis.getBitOrder(), uc_mode); | ||
beginTransaction(_spis); | ||
endTransaction(); | ||
} | ||
|
||
void SoftwareSPI::setClockDivider(uint8_t uc_div) { | ||
(void) uc_div; // no-op | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
PIO-based SPI Master library for the Raspberry Pi Pico RP2040 | ||
Copyright (c) 2025 Earle F. Philhower, III <earlephilhower@yahoo.com> | ||
This library is free software; you can redistribute it and/or | ||
modify it under the terms of the GNU Lesser General Public | ||
License as published by the Free Software Foundation; either | ||
version 2.1 of the License, or (at your option) any later version. | ||
This library 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 | ||
Lesser General Public License for more details. | ||
You should have received a copy of the GNU Lesser General Public | ||
License along with this library; if not, write to the Free Software | ||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <Arduino.h> | ||
#include <api/HardwareSPI.h> | ||
#include <hardware/spi.h> | ||
#include <map> | ||
|
||
class SoftwareSPI : public arduino::HardwareSPI { | ||
public: | ||
/** | ||
@brief Create a PIO-based SPI instance | ||
@param [in] sck SCK GPIO | ||
@param [in] miso MISO GPIO | ||
@param [in] mosi MOSI GPIO | ||
@param [in] cs Optional CS pin for HW CS, must be SCK+1 | ||
*/ | ||
SoftwareSPI(pin_size_t sck, pin_size_t miso, pin_size_t mosi, pin_size_t cs = -1); | ||
|
||
// Send or receive 8- or 16-bit data. Returns read back value | ||
byte transfer(uint8_t data) override; | ||
uint16_t transfer16(uint16_t data) override; | ||
|
||
// Sends buffer in 8 bit chunks. Overwrites buffer with read data | ||
void transfer(void *buf, size_t count) override; | ||
|
||
// Sends one buffer and receives into another, much faster! can set rx or txbuf to nullptr | ||
void transfer(const void *txbuf, void *rxbuf, size_t count) override; | ||
|
||
// Call before/after every complete transaction | ||
void beginTransaction(SPISettings settings) override; | ||
void endTransaction(void) override; | ||
|
||
// Assign pins, call before begin() | ||
bool setMISO(pin_size_t pin); | ||
inline bool setRX(pin_size_t pin) { | ||
return setMISO(pin); | ||
} | ||
bool setCS(pin_size_t pin); | ||
bool setSCK(pin_size_t pin); | ||
bool setMOSI(pin_size_t pin); | ||
inline bool setTX(pin_size_t pin) { | ||
return setMOSI(pin); | ||
} | ||
|
||
// Call once to init/deinit SPI class, select pins, etc. | ||
virtual void begin() override { | ||
begin(false); | ||
} | ||
void begin(bool hwCS); | ||
void end() override; | ||
|
||
// Deprecated - do not use! | ||
void setBitOrder(BitOrder order) __attribute__((deprecated)); | ||
void setDataMode(uint8_t uc_mode) __attribute__((deprecated)); | ||
void setClockDivider(uint8_t uc_div) __attribute__((deprecated)); | ||
|
||
// List of GPIO IRQs to disable during a transaction | ||
virtual void usingInterrupt(int interruptNumber) override { | ||
_usingIRQs.insert({interruptNumber, 0}); | ||
} | ||
virtual void notUsingInterrupt(int interruptNumber) override { | ||
_usingIRQs.erase(interruptNumber); | ||
} | ||
virtual void attachInterrupt() override { /* noop */ } | ||
virtual void detachInterrupt() override { /* noop */ } | ||
|
||
private: | ||
spi_cpol_t cpol(); | ||
spi_cpha_t cpha(); | ||
uint8_t reverseByte(uint8_t b); | ||
uint16_t reverse16Bit(uint16_t w); | ||
void adjustBuffer(const void *s, void *d, size_t cnt, bool by16); | ||
void _adjustPIO(int bits); | ||
|
||
PIOProgram *_spi; | ||
PIO _pio; | ||
int _sm; | ||
int _off; | ||
|
||
SPISettings _spis; | ||
pin_size_t _sck, _miso, _mosi, _cs; | ||
bool _hwCS; | ||
bool _running; // SPI port active | ||
bool _initted; // Transaction begun | ||
int _bits; | ||
|
||
std::map<int, int> _usingIRQs; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
; | ||
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. | ||
; | ||
; SPDX-License-Identifier: BSD-3-Clause | ||
; | ||
|
||
; These programs implement full-duplex SPI, with a SCK period of 4 clock | ||
; cycles. A different program is provided for each value of CPHA, and CPOL is | ||
; achieved using the hardware GPIO inversion available in the IO controls. | ||
; | ||
; Transmit-only SPI can go twice as fast -- see the ST7789 example! | ||
.pio_version 0 // only requires PIO version 0 | ||
|
||
.program spi_cpha0 | ||
.side_set 1 | ||
|
||
; Pin assignments: | ||
; - SCK is side-set pin 0 | ||
; - MOSI is OUT pin 0 | ||
; - MISO is IN pin 0 | ||
; | ||
; Autopush and autopull must be enabled, and the serial frame size is set by | ||
; configuring the push/pull threshold. Shift left/right is fine, but you must | ||
; justify the data yourself. This is done most conveniently for frame sizes of | ||
; 8 or 16 bits by using the narrow store replication and narrow load byte | ||
; picking behaviour of RP2040's IO fabric. | ||
|
||
; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and | ||
; transitions on the trailing edge, or some time before the first leading edge. | ||
|
||
out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if | ||
in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low) | ||
|
||
.program spi_cpha1 | ||
.side_set 1 | ||
|
||
; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and | ||
; is captured on the trailing edge. | ||
|
||
out x, 1 side 0 ; Stall here on empty (keep SCK deasserted) | ||
mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) | ||
in pins, 1 side 0 ; Input data, deassert SCK | ||
|
||
% c-sdk { | ||
#include "hardware/gpio.h" | ||
static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits, | ||
float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) { | ||
pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) : spi_cpha0_program_get_default_config(prog_offs); | ||
sm_config_set_out_pins(&c, pin_mosi, 1); | ||
sm_config_set_in_pins(&c, pin_miso); | ||
sm_config_set_sideset_pins(&c, pin_sck); | ||
// Only support MSB-first in this example code (shift to left, auto push/pull, threshold=nbits) | ||
sm_config_set_out_shift(&c, false, true, n_bits); | ||
sm_config_set_in_shift(&c, false, true, n_bits); | ||
sm_config_set_clkdiv(&c, clkdiv); | ||
|
||
// MOSI, SCK output are low, MISO is input | ||
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi)); | ||
pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); | ||
pio_gpio_init(pio, pin_mosi); | ||
pio_gpio_init(pio, pin_miso); | ||
pio_gpio_init(pio, pin_sck); | ||
|
||
// The pin muxes can be configured to invert the output (among other things | ||
// and this is a cheesy way to get CPOL=1 | ||
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); | ||
// SPI is synchronous, so bypass input synchroniser to reduce input delay. | ||
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); | ||
|
||
pio_sm_init(pio, sm, prog_offs, &c); | ||
pio_sm_set_enabled(pio, sm, true); | ||
} | ||
%} | ||
|
||
; SPI with Chip Select | ||
; ----------------------------------------------------------------------------- | ||
; | ||
; For your amusement, here are some SPI programs with an automatic chip select | ||
; (asserted once data appears in TX FIFO, deasserts when FIFO bottoms out, has | ||
; a nice front/back porch). | ||
; | ||
; The number of bits per FIFO entry is configured via the Y register | ||
; and the autopush/pull threshold. From 2 to 32 bits. | ||
; | ||
; Pin assignments: | ||
; - SCK is side-set bit 0 | ||
; - CSn is side-set bit 1 | ||
; - MOSI is OUT bit 0 (host-to-device) | ||
; - MISO is IN bit 0 (device-to-host) | ||
; | ||
; This program only supports one chip select -- use GPIO if more are needed | ||
; | ||
; Provide a variation for each possibility of CPHA; for CPOL we can just | ||
; invert SCK in the IO muxing controls (downstream from PIO) | ||
|
||
|
||
; CPHA=0: data is captured on the leading edge of each SCK pulse (including | ||
; the first pulse), and transitions on the trailing edge | ||
|
||
.program spi_cpha0_cs | ||
.side_set 2 | ||
|
||
.wrap_target | ||
bitloop: | ||
out pins, 1 side 0x0 [1] | ||
in pins, 1 side 0x1 | ||
jmp x-- bitloop side 0x1 | ||
|
||
out pins, 1 side 0x0 | ||
mov x, y side 0x0 ; Reload bit counter from Y | ||
in pins, 1 side 0x1 | ||
jmp !osre bitloop side 0x1 ; Fall-through if TXF empties | ||
|
||
nop side 0x0 [1] ; CSn back porch | ||
public entry_point: ; Must set X,Y to n-2 before starting! | ||
pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles) | ||
.wrap ; Note ifempty to avoid time-of-check race | ||
|
||
; CPHA=1: data transitions on the leading edge of each SCK pulse, and is | ||
; captured on the trailing edge | ||
|
||
.program spi_cpha1_cs | ||
.side_set 2 | ||
|
||
.wrap_target | ||
bitloop: | ||
out pins, 1 side 0x1 [1] | ||
in pins, 1 side 0x0 | ||
jmp x-- bitloop side 0x0 | ||
|
||
out pins, 1 side 0x1 | ||
mov x, y side 0x1 | ||
in pins, 1 side 0x0 | ||
jmp !osre bitloop side 0x0 | ||
|
||
public entry_point: ; Must set X,Y to n-2 before starting! | ||
pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles) | ||
nop side 0x0 [1]; CSn front porch | ||
.wrap | ||
|
||
% c-sdk { | ||
#include "hardware/gpio.h" | ||
static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool cpha, bool cpol, | ||
uint pin_sck, uint pin_mosi, uint pin_miso) { | ||
pio_sm_config c = cpha ? spi_cpha1_cs_program_get_default_config(prog_offs) : spi_cpha0_cs_program_get_default_config(prog_offs); | ||
sm_config_set_out_pins(&c, pin_mosi, 1); | ||
sm_config_set_in_pins(&c, pin_miso); | ||
sm_config_set_sideset_pins(&c, pin_sck); | ||
sm_config_set_out_shift(&c, false, true, n_bits); | ||
sm_config_set_in_shift(&c, false, true, n_bits); | ||
sm_config_set_clkdiv(&c, clkdiv); | ||
|
||
pio_sm_set_pins_with_mask(pio, sm, (2u << pin_sck), (3u << pin_sck) | (1u << pin_mosi)); | ||
pio_sm_set_pindirs_with_mask(pio, sm, (3u << pin_sck) | (1u << pin_mosi), (3u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); | ||
pio_gpio_init(pio, pin_mosi); | ||
pio_gpio_init(pio, pin_miso); | ||
pio_gpio_init(pio, pin_sck); | ||
pio_gpio_init(pio, pin_sck + 1); | ||
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); | ||
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); | ||
|
||
uint entry_point = prog_offs + (cpha ? spi_cpha1_cs_offset_entry_point : spi_cpha0_cs_offset_entry_point); | ||
pio_sm_init(pio, sm, entry_point, &c); | ||
pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2)); | ||
pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2)); | ||
pio_sm_set_enabled(pio, sm, true); | ||
} | ||
%} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
// -------------------------------------------------- // | ||
// This file is autogenerated by pioasm; do not edit! // | ||
// -------------------------------------------------- // | ||
|
||
#pragma once | ||
|
||
#if !PICO_NO_HARDWARE | ||
#include "hardware/pio.h" | ||
#endif | ||
|
||
// --------- // | ||
// spi_cpha0 // | ||
// --------- // | ||
|
||
#define spi_cpha0_wrap_target 0 | ||
#define spi_cpha0_wrap 1 | ||
#define spi_cpha0_pio_version 0 | ||
|
||
static const uint16_t spi_cpha0_program_instructions[] = { | ||
// .wrap_target | ||
0x6101, // 0: out pins, 1 side 0 [1] | ||
0x5101, // 1: in pins, 1 side 1 [1] | ||
// .wrap | ||
}; | ||
|
||
#if !PICO_NO_HARDWARE | ||
static const struct pio_program spi_cpha0_program = { | ||
.instructions = spi_cpha0_program_instructions, | ||
.length = 2, | ||
.origin = -1, | ||
.pio_version = 0, | ||
#if PICO_PIO_VERSION > 0 | ||
.used_gpio_ranges = 0x0 | ||
#endif | ||
}; | ||
|
||
static inline pio_sm_config spi_cpha0_program_get_default_config(uint offset) { | ||
pio_sm_config c = pio_get_default_sm_config(); | ||
sm_config_set_wrap(&c, offset + spi_cpha0_wrap_target, offset + spi_cpha0_wrap); | ||
sm_config_set_sideset(&c, 1, false, false); | ||
return c; | ||
} | ||
#endif | ||
|
||
// --------- // | ||
// spi_cpha1 // | ||
// --------- // | ||
|
||
#define spi_cpha1_wrap_target 0 | ||
#define spi_cpha1_wrap 2 | ||
#define spi_cpha1_pio_version 0 | ||
|
||
static const uint16_t spi_cpha1_program_instructions[] = { | ||
// .wrap_target | ||
0x6021, // 0: out x, 1 side 0 | ||
0xb101, // 1: mov pins, x side 1 [1] | ||
0x4001, // 2: in pins, 1 side 0 | ||
// .wrap | ||
}; | ||
|
||
#if !PICO_NO_HARDWARE | ||
static const struct pio_program spi_cpha1_program = { | ||
.instructions = spi_cpha1_program_instructions, | ||
.length = 3, | ||
.origin = -1, | ||
.pio_version = 0, | ||
#if PICO_PIO_VERSION > 0 | ||
.used_gpio_ranges = 0x0 | ||
#endif | ||
}; | ||
|
||
static inline pio_sm_config spi_cpha1_program_get_default_config(uint offset) { | ||
pio_sm_config c = pio_get_default_sm_config(); | ||
sm_config_set_wrap(&c, offset + spi_cpha1_wrap_target, offset + spi_cpha1_wrap); | ||
sm_config_set_sideset(&c, 1, false, false); | ||
return c; | ||
} | ||
|
||
#include "hardware/gpio.h" | ||
static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits, | ||
float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) { | ||
pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) : spi_cpha0_program_get_default_config(prog_offs); | ||
sm_config_set_out_pins(&c, pin_mosi, 1); | ||
sm_config_set_in_pins(&c, pin_miso); | ||
sm_config_set_sideset_pins(&c, pin_sck); | ||
// Only support MSB-first in this example code (shift to left, auto push/pull, threshold=nbits) | ||
sm_config_set_out_shift(&c, false, true, n_bits); | ||
sm_config_set_in_shift(&c, false, true, n_bits); | ||
sm_config_set_clkdiv(&c, clkdiv); | ||
// MOSI, SCK output are low, MISO is input | ||
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi)); | ||
pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); | ||
pio_gpio_init(pio, pin_mosi); | ||
pio_gpio_init(pio, pin_miso); | ||
pio_gpio_init(pio, pin_sck); | ||
// The pin muxes can be configured to invert the output (among other things | ||
// and this is a cheesy way to get CPOL=1 | ||
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); | ||
// SPI is synchronous, so bypass input synchroniser to reduce input delay. | ||
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); | ||
pio_sm_init(pio, sm, prog_offs, &c); | ||
pio_sm_set_enabled(pio, sm, true); | ||
} | ||
|
||
#endif | ||
|
||
// ------------ // | ||
// spi_cpha0_cs // | ||
// ------------ // | ||
|
||
#define spi_cpha0_cs_wrap_target 0 | ||
#define spi_cpha0_cs_wrap 8 | ||
#define spi_cpha0_cs_pio_version 0 | ||
|
||
#define spi_cpha0_cs_offset_entry_point 8u | ||
|
||
static const uint16_t spi_cpha0_cs_program_instructions[] = { | ||
// .wrap_target | ||
0x6101, // 0: out pins, 1 side 0 [1] | ||
0x4801, // 1: in pins, 1 side 1 | ||
0x0840, // 2: jmp x--, 0 side 1 | ||
0x6001, // 3: out pins, 1 side 0 | ||
0xa022, // 4: mov x, y side 0 | ||
0x4801, // 5: in pins, 1 side 1 | ||
0x08e0, // 6: jmp !osre, 0 side 1 | ||
0xa142, // 7: nop side 0 [1] | ||
0x91e0, // 8: pull ifempty block side 2 [1] | ||
// .wrap | ||
}; | ||
|
||
#if !PICO_NO_HARDWARE | ||
static const struct pio_program spi_cpha0_cs_program = { | ||
.instructions = spi_cpha0_cs_program_instructions, | ||
.length = 9, | ||
.origin = -1, | ||
.pio_version = 0, | ||
#if PICO_PIO_VERSION > 0 | ||
.used_gpio_ranges = 0x0 | ||
#endif | ||
}; | ||
|
||
static inline pio_sm_config spi_cpha0_cs_program_get_default_config(uint offset) { | ||
pio_sm_config c = pio_get_default_sm_config(); | ||
sm_config_set_wrap(&c, offset + spi_cpha0_cs_wrap_target, offset + spi_cpha0_cs_wrap); | ||
sm_config_set_sideset(&c, 2, false, false); | ||
return c; | ||
} | ||
#endif | ||
|
||
// ------------ // | ||
// spi_cpha1_cs // | ||
// ------------ // | ||
|
||
#define spi_cpha1_cs_wrap_target 0 | ||
#define spi_cpha1_cs_wrap 8 | ||
#define spi_cpha1_cs_pio_version 0 | ||
|
||
#define spi_cpha1_cs_offset_entry_point 7u | ||
|
||
static const uint16_t spi_cpha1_cs_program_instructions[] = { | ||
// .wrap_target | ||
0x6901, // 0: out pins, 1 side 1 [1] | ||
0x4001, // 1: in pins, 1 side 0 | ||
0x0040, // 2: jmp x--, 0 side 0 | ||
0x6801, // 3: out pins, 1 side 1 | ||
0xa822, // 4: mov x, y side 1 | ||
0x4001, // 5: in pins, 1 side 0 | ||
0x00e0, // 6: jmp !osre, 0 side 0 | ||
0x91e0, // 7: pull ifempty block side 2 [1] | ||
0xa142, // 8: nop side 0 [1] | ||
// .wrap | ||
}; | ||
|
||
#if !PICO_NO_HARDWARE | ||
static const struct pio_program spi_cpha1_cs_program = { | ||
.instructions = spi_cpha1_cs_program_instructions, | ||
.length = 9, | ||
.origin = -1, | ||
.pio_version = 0, | ||
#if PICO_PIO_VERSION > 0 | ||
.used_gpio_ranges = 0x0 | ||
#endif | ||
}; | ||
|
||
static inline pio_sm_config spi_cpha1_cs_program_get_default_config(uint offset) { | ||
pio_sm_config c = pio_get_default_sm_config(); | ||
sm_config_set_wrap(&c, offset + spi_cpha1_cs_wrap_target, offset + spi_cpha1_cs_wrap); | ||
sm_config_set_sideset(&c, 2, false, false); | ||
return c; | ||
} | ||
|
||
#include "hardware/gpio.h" | ||
static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool cpha, bool cpol, | ||
uint pin_sck, uint pin_mosi, uint pin_miso) { | ||
pio_sm_config c = cpha ? spi_cpha1_cs_program_get_default_config(prog_offs) : spi_cpha0_cs_program_get_default_config(prog_offs); | ||
sm_config_set_out_pins(&c, pin_mosi, 1); | ||
sm_config_set_in_pins(&c, pin_miso); | ||
sm_config_set_sideset_pins(&c, pin_sck); | ||
sm_config_set_out_shift(&c, false, true, n_bits); | ||
sm_config_set_in_shift(&c, false, true, n_bits); | ||
sm_config_set_clkdiv(&c, clkdiv); | ||
pio_sm_set_pins_with_mask(pio, sm, (2u << pin_sck), (3u << pin_sck) | (1u << pin_mosi)); | ||
pio_sm_set_pindirs_with_mask(pio, sm, (3u << pin_sck) | (1u << pin_mosi), (3u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso)); | ||
pio_gpio_init(pio, pin_mosi); | ||
pio_gpio_init(pio, pin_miso); | ||
pio_gpio_init(pio, pin_sck); | ||
pio_gpio_init(pio, pin_sck + 1); | ||
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); | ||
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); | ||
uint entry_point = prog_offs + (cpha ? spi_cpha1_cs_offset_entry_point : spi_cpha0_cs_offset_entry_point); | ||
pio_sm_init(pio, sm, entry_point, &c); | ||
pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2)); | ||
pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2)); | ||
pio_sm_set_enabled(pio, sm, true); | ||
} | ||
|
||
#endif | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters