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.
This commit is contained in:
parent
a426fbf51d
commit
acf81f426c
11 changed files with 1110 additions and 2 deletions
|
|
@ -148,7 +148,7 @@ The RP2040 PIO state machines (SMs) are used to generate jitter-free:
|
||||||
* I2S Input
|
* I2S Input
|
||||||
* I2S Output
|
* I2S Output
|
||||||
* Software UARTs (Serial ports)
|
* Software UARTs (Serial ports)
|
||||||
|
* Software SPIs
|
||||||
|
|
||||||
# Installing via Arduino Boards Manager
|
# Installing via Arduino Boards Manager
|
||||||
## Windows-specific Notes
|
## Windows-specific Notes
|
||||||
|
|
|
||||||
13
docs/spi.rst
13
docs/spi.rst
|
|
@ -28,6 +28,19 @@ pin itself, as is the standard way in Arduino.
|
||||||
|
|
||||||
* The interrupt calls (``attachInterrupt``, and ``detachInterrpt``) are not implemented.
|
* The interrupt calls (``attachInterrupt``, and ``detachInterrpt``) are not implemented.
|
||||||
|
|
||||||
|
Software SPI (Master Only)
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Similar to ``SoftwareSerial``, ``SoftwareSPI`` creates a PIO based SPI interface that
|
||||||
|
can be used in the same manner as the hardware SPI devices. The constructor takes the
|
||||||
|
pins desired, which can be any GPIO pins with the rule that if hardware CS is used then
|
||||||
|
it must be on pin ``SCK + 1``. Construct a ``SoftwareSPI`` object in your code as
|
||||||
|
follows and use it as needed (i.e. pass it into ``SD.begin(_CS, softwareSPI);``
|
||||||
|
|
||||||
|
.. code:: cpp
|
||||||
|
|
||||||
|
#include <SoftwareSPI.h>
|
||||||
|
SoftwareSPI softSPI(_sck, _miso, _mosi); // no HW CS support, any selection of pins can be used
|
||||||
|
|
||||||
SPI Slave (SPISlave)
|
SPI Slave (SPISlave)
|
||||||
====================
|
====================
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
43
libraries/SoftwareSPI/keywords.txt
Normal file
43
libraries/SoftwareSPI/keywords.txt
Normal file
|
|
@ -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
|
||||||
10
libraries/SoftwareSPI/library.properties
Normal file
10
libraries/SoftwareSPI/library.properties
Normal file
|
|
@ -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
|
||||||
365
libraries/SoftwareSPI/src/SoftwareSPI.cpp
Normal file
365
libraries/SoftwareSPI/src/SoftwareSPI.cpp
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
109
libraries/SoftwareSPI/src/SoftwareSPI.h
Normal file
109
libraries/SoftwareSPI/src/SoftwareSPI.h
Normal file
|
|
@ -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;
|
||||||
|
};
|
||||||
168
libraries/SoftwareSPI/src/spi.pio
Normal file
168
libraries/SoftwareSPI/src/spi.pio
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
%}
|
||||||
218
libraries/SoftwareSPI/src/spi.pio.h
Normal file
218
libraries/SoftwareSPI/src/spi.pio.h
Normal file
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S ./libraries/SingleF
|
||||||
./libraries/SPISlave ./libraries/lwIP_ESPHost ./libraries/FatFS\
|
./libraries/SPISlave ./libraries/lwIP_ESPHost ./libraries/FatFS\
|
||||||
./libraries/FatFSUSB ./libraries/BluetoothAudio ./libraries/BluetoothHCI \
|
./libraries/FatFSUSB ./libraries/BluetoothAudio ./libraries/BluetoothHCI \
|
||||||
./libraries/BluetoothHIDMaster ./libraries/NetBIOS ./libraries/Ticker \
|
./libraries/BluetoothHIDMaster ./libraries/NetBIOS ./libraries/Ticker \
|
||||||
./libraries/VFS ./libraries/rp2350 ./libraries/SimpleMDNS ; do
|
./libraries/VFS ./libraries/rp2350 ./libraries/SimpleMDNS ./libraries/SoftwareSPI ; do
|
||||||
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
|
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
|
||||||
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
|
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
|
||||||
done
|
done
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue