Skip to content

Commit

Permalink
Add PIO-based SoftwareSPI enabling SPI on any pins (#2778)
Browse files Browse the repository at this point in the history
* 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.
earlephilhower authored Jan 27, 2025
1 parent a426fbf commit acf81f4
Showing 11 changed files with 1,110 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -148,7 +148,7 @@ The RP2040 PIO state machines (SMs) are used to generate jitter-free:
* I2S Input
* I2S Output
* Software UARTs (Serial ports)

* Software SPIs

# Installing via Arduino Boards Manager
## Windows-specific Notes
13 changes: 13 additions & 0 deletions docs/spi.rst
Original file line number Diff line number Diff line change
@@ -28,6 +28,19 @@ pin itself, as is the standard way in Arduino.

* 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)
====================
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.
}



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;
}
43 changes: 43 additions & 0 deletions libraries/SoftwareSPI/keywords.txt
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
10 changes: 10 additions & 0 deletions libraries/SoftwareSPI/library.properties
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
Loading

0 comments on commit acf81f4

Please sign in to comment.