Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example for bidirectional (3-wire) SPI communication #341

Open
mbartlett21 opened this issue Mar 6, 2023 · 8 comments
Open

Add an example for bidirectional (3-wire) SPI communication #341

mbartlett21 opened this issue Mar 6, 2023 · 8 comments

Comments

@mbartlett21
Copy link

For example, communicating with an SSD1677 eink display chip. (Datasheet)

Rather than having separate in/out data pins, they instead have a single SDA bidirectional pin, which depending on the command sent, is written to by either the display or the master. (SDI and SDO in the chart are actually a single pin)

3-Wire SPI SSD1677

@peterharperuk
Copy link
Contributor

In case it's useful, Pico W communicates with the wifi chip using SPI where MOSI and MISO are the same pin (also used as the interrupt line). It uses the pio to do this.

@mbartlett21
Copy link
Author

mbartlett21 commented Mar 6, 2023

@peterharperuk
So this would be the pertinent code?

https://github.com/raspberrypi/pico-sdk/blob/f396d05f8252d4670d4ea05c8b7ac938ef0cd381/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio#L20-L32
(comments added below with what I can tell)

.program spi_gap01_sample0
.side_set 1
; mb: this only handles the clock and the data pin
; mb: x and y store the transmit and receive bit counts respectively
; mb: clock is low on transmit.
; always transmit multiple of 32 bytes
lp: out pins, 1             side 0
    jmp x-- lp              side 1
public lp1_end:
    set pindirs, 0          side 0 ; mb: set it to read. the first bit read is discarded
    nop                     side 1
lp2:
    in pins, 1              side 0
    jmp y-- lp2             side 1
public end:
; mb: at the end, there isn't any data still waiting in the fifo, so we pause at lp
;     with the clock low
The C file also

And then it is driven from the C file (comments also added):

https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.c#L246-L280

        // mb: set up the pio
        pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, false);
        pio_sm_set_wrap(bus_data->pio, bus_data->pio_sm, bus_data->pio_offset, bus_data->pio_offset + SPI_OFFSET_END - 1);
        pio_sm_clear_fifos(bus_data->pio, bus_data->pio_sm);
        pio_sm_set_pindirs_with_mask(bus_data->pio, bus_data->pio_sm, 1u << DATA_OUT_PIN, 1u << DATA_OUT_PIN);
        pio_sm_restart(bus_data->pio, bus_data->pio_sm);
        pio_sm_clkdiv_restart(bus_data->pio, bus_data->pio_sm);
        // mb: put the no of transmitted bits in the bus
        pio_sm_put(bus_data->pio, bus_data->pio_sm, tx_length * 8 - 1);
        // mb: transfer the bus data to the x variable
        pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_x, 32));
        // mb: put the no of received bits in the bus
        pio_sm_put(bus_data->pio, bus_data->pio_sm, (rx_length - tx_length) * 8 - 1);
        // mb: transfer the bus data to the y variable
        pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_y, 32));
        // mb: jump to the start of the program
        pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_jmp(bus_data->pio_offset));
        // mb: reset the dma configuration
        dma_channel_abort(bus_data->dma_out);
        dma_channel_abort(bus_data->dma_in);

        // mb: set up the tx dma channel
        dma_channel_config out_config = dma_channel_get_default_config(bus_data->dma_out);
        channel_config_set_bswap(&out_config, true);
        channel_config_set_dreq(&out_config, pio_get_dreq(bus_data->pio, bus_data->pio_sm, true));

        // mb: the write is defaulting to read from the same address (i think)
        dma_channel_configure(bus_data->dma_out, &out_config, &bus_data->pio->txf[bus_data->pio_sm], tx, tx_length / 4, true);

        // mb: set up the rx dma channel
        dma_channel_config in_config = dma_channel_get_default_config(bus_data->dma_in);
        channel_config_set_bswap(&in_config, true);
        channel_config_set_dreq(&in_config, pio_get_dreq(bus_data->pio, bus_data->pio_sm, false));
        // mb: write to an array
        channel_config_set_write_increment(&in_config, true);
        // mb: read from the same address
        channel_config_set_read_increment(&in_config, false);
        dma_channel_configure(bus_data->dma_in, &in_config, rx + tx_length, &bus_data->pio->rxf[bus_data->pio_sm], rx_length / 4 - tx_length / 4, true);

        // mb: enable the pio
        pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, true);
        __compiler_memory_barrier();

        // mb: wait till all the data is transferred.
        // mb: note: I'm not sure we actually have to wait for the out channel,
        // mb:       since the in channel needs to wait for all the data to be
        // mb:       sent before the spi will write back.
        dma_channel_wait_for_finish_blocking(bus_data->dma_out);
        dma_channel_wait_for_finish_blocking(bus_data->dma_in);


        __compiler_memory_barrier();
        // mb: the first bytes are 0, since nothing was received simultaneously
        // mb: and it is the same sort of api as simultaneous bidi spi
        memset(rx, 0, tx_length); // make sure we don't have garbage in what would have been returned data if using real SPI

Just wondering, are any of the other PIO programs in that file used? There doesn't seem to be any references to them.

@peterharperuk
Copy link
Contributor

any of the other PIO programs in that file used

No - they were used for bringup and are just kept for reference. Different devices might behave differently and the pio program might need tweaking. The code is in cyw43_spi_transfer and as written currently only supports a) write b) write then read, but that could be fixed I imagine.

@matsobdev
Copy link

Hmmm, just crude idea. It might be possible with hardware SPI and tying Pico's MISO and MOSI with resistors (conecting Pico's MISO through a resistor to display data pin, and the same with Pico's MOSI).

@Slion
Copy link

Slion commented Oct 6, 2024

Looks like 3-wires half duplex SPI is very common for displays. Apparently those Pimoroni Display Pack with ST7789 are using it too. Having such an example would certainly benefit the community.

@peardox
Copy link

peardox commented Oct 6, 2024

The ST7789 uses the DC to signal Data/Command then and command issued determines the direction of the pin usually known as MOSI. Therefore when sending the command you'd have to set the direction of "MOSI" to the appropriate value in order to send/recieve data - i.e. it's 4 wire where DC sets direction

@lurch
Copy link
Contributor

lurch commented Oct 7, 2024

There's already an example for ST7789 🙂

@peardox
Copy link

peardox commented Oct 7, 2024

Yeah, trying to adapt it to do read ATM as read has useful features not exposed by FOSS libraries

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

No branches or pull requests

7 participants