raspberrypi / pico-examples

BSD 3-Clause "New" or "Revised" License
2.62k stars 779 forks source link

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

Open mbartlett21 opened 1 year ago

mbartlett21 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

@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 ```c // 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 commented 1 year ago

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.