Skip to content

rp2: allow SPI transmit-only without SDI#5437

Open
rdon-key wants to merge 2 commits into
tinygo-org:devfrom
rdon-key:fix-rp2-spi-nopin
Open

rp2: allow SPI transmit-only without SDI#5437
rdon-key wants to merge 2 commits into
tinygo-org:devfrom
rdon-key:fix-rp2-spi-nopin

Conversation

@rdon-key
Copy link
Copy Markdown
Contributor

@rdon-key rdon-key commented May 30, 2026

This allows SPI transmit-only configurations on RP2040/RP2350 with SDI set to machine.NoPin.

The RP2040/RP2350 SPI implementation already supports transmit-only SPI through spi.Tx(tx, nil), where only SDO is used and received data is ignored. However, Configure still required SDI to be a valid physical pin. As a result, SDI: machine.NoPin returned invalid SPI SDI pin, even for transmit-only SPI.

This change keeps SCK and SDO validation unchanged, allows SDI: machine.NoPin, and skips SDI pin configuration when NoPin is used.

This PR intentionally only covers the transmit-only case. It does not change SDO handling.

Testing

Tested on Raspberry Pi Pico / RP2040:

SPI0: SCK=GP18, SDO=GP19, SDI=machine.NoPin: OK
SPI0: SCK=GP18, SDO=GP19, SDI=GP0: OK
SPI1: SCK=GP14, SDO=GP15, SDI=machine.NoPin: OK
SPI1: SCK=GP14, SDO=GP15, SDI=GP12: OK
SPI1 invalid SDI check: 
SDI=GP0 is still rejected
spi.Tx(tx, nil): OK

Tested on Raspberry Pi Pico 2 / RP2350A:

SPI0: SCK=GP18, SDO=GP19, SDI=machine.NoPin: OK
SPI0: SCK=GP18, SDO=GP19, SDI=GP0: OK
SPI1: SCK=GP14, SDO=GP15, SDI=machine.NoPin: OK
SPI1: SCK=GP14, SDO=GP15, SDI=GP12: OK
SPI1 invalid SDI check: SDI=GP0 is still rejected
spi.Tx(tx, nil): OK

test code

RP2040 version:

Details
//go:build rp2040

package main

import (
        "machine"
        "time"
)

func testSPI(name string, spi *machine.SPI, sck, sdo, sdi machine.Pin, sckName, sdoName, sdiName string) bool {
        println("test:", name)
        println("  SCK:", sckName, "SDO:", sdoName, "SDI:", sdiName)
        err := spi.Configure(machine.SPIConfig{Frequency: 100_000, SCK: sck, SDO: sdo, SDI: sdi, Mode: 0})
        if err != nil {
                println("FAIL configure:", err.Error())
                return false
        }
        println("OK configure")
        err = spi.Tx([]byte{0x00, 0x55, 0xAA, 0xFF}, nil)
        if err != nil {
                println("FAIL tx:", err.Error())
                return false
        }
        println("OK tx")
        return true
}

func testInvalidSPI(name string, spi *machine.SPI, sck, sdo, sdi machine.Pin, sckName, sdoName, sdiName string) bool {
        println("test invalid:", name)
        println("  SCK:", sckName, "SDO:", sdoName, "SDI:", sdiName)
        err := spi.Configure(machine.SPIConfig{Frequency: 100_000, SCK: sck, SDO: sdo, SDI: sdi, Mode: 0})
        if err == nil {
                println("FAIL invalid pin accepted")
                return false
        }
        println("OK invalid pin rejected:", err.Error())
        return true
}

func main() {
        time.Sleep(2 * time.Second)
        println("RP2040 SPI SDI test")

        ok := true
        ok = testSPI("SPI0 NoPin", machine.SPI0, machine.GP18, machine.GP19, machine.NoPin, "GP18", "GP19", "NoPin") && ok
        ok = testSPI("SPI0 valid SDI", machine.SPI0, machine.GP18, machine.GP19, machine.GP0, "GP18", "GP19", "GP0") && ok
        ok = testSPI("SPI1 NoPin", machine.SPI1, machine.GP14, machine.GP15, machine.NoPin, "GP14", "GP15", "NoPin") && ok
        ok = testSPI("SPI1 valid SDI", machine.SPI1, machine.GP14, machine.GP15, machine.GP12, "GP14", "GP15", "GP12") && ok
        ok = testInvalidSPI("SPI1 invalid SDI", machine.SPI1, machine.GP14, machine.GP15, machine.GP0, "GP14", "GP15", "GP0") && ok

        if ok {
                println("PASS")
        } else {
                println("FAIL")
        }
        for {
                time.Sleep(time.Second)
        }
}

RP2350 version:

Details
//go:build rp2350 

package main

import (
        "machine"
        "time"
)

func testSPI(name string, spi *machine.SPI, sck, sdo, sdi machine.Pin, sckName, sdoName, sdiName string) bool {
        println("test:", name)
        println("  SCK:", sckName, "SDO:", sdoName, "SDI:", sdiName)
        err := spi.Configure(machine.SPIConfig{Frequency: 100_000, SCK: sck, SDO: sdo, SDI: sdi, Mode: 0})
        if err != nil {
                println("FAIL configure:", err.Error())
                return false
        }
        println("OK configure")
        err = spi.Tx([]byte{0x00, 0x55, 0xAA, 0xFF}, nil)
        if err != nil {
                println("FAIL tx:", err.Error())
                return false
        }
        println("OK tx")
        return true
}

func testInvalidSPI(name string, spi *machine.SPI, sck, sdo, sdi machine.Pin, sckName, sdoName, sdiName string) bool {
        println("test invalid:", name)
        println("  SCK:", sckName, "SDO:", sdoName, "SDI:", sdiName)
        err := spi.Configure(machine.SPIConfig{Frequency: 100_000, SCK: sck, SDO: sdo, SDI: sdi, Mode: 0})
        if err == nil {
                println("FAIL invalid pin accepted")
                return false
        }
        println("OK invalid pin rejected:", err.Error())
        return true
}

func main() {
        time.Sleep(2 * time.Second)
        println("RP2350A SPI SDI test")

        ok := true
        ok = testSPI("SPI0 NoPin", machine.SPI0, machine.GP18, machine.GP19, machine.NoPin, "GP18", "GP19", "NoPin") && ok
        ok = testSPI("SPI0 valid SDI", machine.SPI0, machine.GP18, machine.GP19, machine.GP0, "GP18", "GP19", "GP0") && ok
        ok = testSPI("SPI1 NoPin", machine.SPI1, machine.GP14, machine.GP15, machine.NoPin, "GP14", "GP15", "NoPin") && ok
        ok = testSPI("SPI1 valid SDI", machine.SPI1, machine.GP14, machine.GP15, machine.GP12, "GP14", "GP15", "GP12") && ok
        ok = testInvalidSPI("SPI1 invalid SDI", machine.SPI1, machine.GP14, machine.GP15, machine.GP0, "GP14", "GP15", "GP0") && ok

        if ok {
                println("PASS")
        } else {
                println("FAIL")
        }
        for {
                time.Sleep(time.Second)
        }
}

Copy link
Copy Markdown
Contributor

@eliasnaur eliasnaur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but why not do the same for SDO?

@rdon-key
Copy link
Copy Markdown
Contributor Author

@eliasnaur
Thank you for your review.

I limited this PR to SDI=NoPin because I could verify the transmit-only case on real hardware with a MAX7219 LED matrix.

I don't currently have a concrete use case or hardware test setup for SDO=NoPin. If there is a common receive-only SPI use case, I would appreciate a pointer and can look into it as a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants