This program shows the use of the Maxim DS3234 real-time clock via SPI. It makes use of the kernel-v1 to run two threads on core 0.

One thread periodically reads the date and time from the clock, and prints it to the serial terminal. It also reads the so called timestamp, which is the date and time encoded in a single 32 bit value, and prints the timestamp followed by the date and time decoded from it. In addition, the thread prints the serial clock rate, serial clock polarity, and txShift value used for each periodic run.

c0-t1   24  5 29   16  8 29  1635451421   24  5 29   16  8 29    500000  1  000000F0H
c0-t1   24  5 29   16  8 30  1635451422   24  5 29   16  8 30    500000  0  000000AAH
c0-t1   24  5 29   16  8 31  1635451423   24  5 29   16  8 31    500000  0  000000F0H
c0-t1   24  5 29   16  8 32  1635451424   24  5 29   16  8 32   1000000  0  000000F0H
c0-t1   24  5 29   16  8 33  1635451425   24  5 29   16  8 33    500000  1  000000F0H
c0-t1   24  5 29   16  8 34  1635451426   24  5 29   16  8 34    500000  0  000000AAH
c0-t1   24  5 29   16  8 35  1635451427   24  5 29   16  8 35    500000  0  000000F0H
c0-t1   24  5 29   16  8 36  1635451428   24  5 29   16  8 36   1000000  0  000000F0H


The program is implemented in one module SPIrtc.mod.

  • thread 0: heartbeat blinker, also writes a counter value to the eight LEDs via module LEDext
  • thread 1: periodically reads the values from the clock, and prints the results

Modules SPIdev and SPIdata

As with the UARTs, there’s a module SPIdev defining and implementing a representation of the basic device on the RP2040 chip. It mainly serves to initialise the corresponding data structure SPIdev.Device with all data required to operate the device, eg. from SPIdata, such as register addresses, and to configure and enable the device and GPIO hardware.

An SPI Device can talk to multiple connected peripherals, selected via CS (chip select), and to each peripheral using a different configuration regarding

  • serial clock rate (fsclk),
  • serial clock polarity and phase (cpol, cpha),
  • data size (8 or 16 bits),
  • txShift value, ie. the “dummy” transmit data to receive data.1

Hence, SPIdev allows for

  • basic configuration via Configure,
  • further run-time per-transaction (SPI tx and rx) configuration via PutRunCfg and DeviceRunCfg.

DeviceCfg is used to define a run-time configuration, via RECORD fields, but it would be wasteful to always convert the RECORD to the single hardware control register value for each transmit-receive transaction. DeviceRunCfg contains the converted register value, MakeRunCfg is used to make the conversion.

The SPI peripheral used here, the Maxim DS3234 real-time clock, can be used with different run-time configuration. This program uses four variants to test the new feature: different serial clock polarity, different serial clock frequencies, different transmit shift values (see SPIrtc.createCfgVariants).

Module SPIdata uses this device definition SPIdev.Device to transmit and receive data from the peripheral connected to the RP2040’s device, ie. the real-time clock in the example program.

SPIdev and SPIdata do not provide any support for selecting the peripheral via chip select (CS), which must be done by the client module or program, or is automated depending on the corresponding GPIO pin configuration, as explained in the next section.

The RP2040’s SPI devices always use a transmit and a receive FIFO buffer, 16 bits wide and 8 entries deep. The transmit FIFO is written from software, and emptied by the SPI hardware, the receive FIFO is written by the hardware and read from software. Each data transmit FIFO out causes a receive FIFO in with an SPI peripheral connected correctly. The software must ensure that writes and reads are balanced, on both the software and hardware ends of each FIFO, and in particular ensure that the receive FIFO does not overflow by data received via MISO from the peripheral. See the implementation of SPIdata.GetBytes and SPIdata.PutBytes.

Here are some of the run-time configurations the test code cyclically uses (seeSPIrtc.tc1).

  • red: CS signal (oscilloscope trigger source, negative edge)
  • yellow: serial clock
  • blue: MOSI
  • green: MISO


  • fsclk = 1,000 kHz (yellow, also as measured Freq' by the oscilloscope below traces)
  • cpol = 0 (yellow, serial clock low between transactions)
  • txShift = 0F0H (blue, once per register read)

We see the three transactions:

  • read time: write register start address (value = 0), read 3 registers,
  • read date: write register start address (value = 4), read 3 registers,
  • read timestamp: write register start address (value = 0), read 7 registers.


  • fsclk = 500 kHz (yellow)
  • cpol = 0 (yellow)
  • txShift = 0AAH (blue)


  • fsclk = 500 kHz (yellow)
  • cpol = 1 (yellow, serial clock high between transactions)
  • txShift = 0F0H

Chip Select Options (CS)

The two RP2040’s SPI devices do not provide any functionality to support multiple SPI peripherals connected to each of them, that is, for corresponding chip select (CS) signals. The SPI device’s signal SSPFSSOUT can be connected to a GPIO pin that can be configured for SPI CSn functionality, but it is automatically activated (low) when a transmission starts, and deactivated (high) after the transmission terminates. The software cannot control it. Consequently, it can only be used if just one SPI peripheral is connected to the SPI device. The corresponding GPIO pin must be configured for SPI operation in this case using the corresponding function number (GPIO.Fspi).

If we have several SPI peripherals connected to one SPI device, we need to use SIO via GPIO to operate one CS signal from software to select a specific peripheral. The software keeps the CS in the high state between transmissions, and sets the signal to low for the selected peripheral during transmissions. Note that any GPIO pin can be used, including the ones denoted for CSn SPI functionality. Such pin must be configured for SIO operation, not SPI (GPIO.Fsio).

Module SPIper implements the logic for both CS cases, which can be configured. The corresponding data structure represents a specific SPI peripheral, such as the real-time clock in the test program. For now, this module resides just in the example program directory, since honestly I am not yet sure if such a module is warranted in the module library.

CS Signal Timing

When operating the CS signal from SSPFSSOUT, as explained above, the time between the activation of CS (low) and the start of the serial clock signal is fixed to half a clock period. This can be too short for the corresponding minimum CS-to-SCLK set-up time of the peripheral, in which case the peripheral will not function as intended. Nothing the software can configure here, it’s literally hardwired.

When using an SIO-based CS signal, with the software explicitly activating it before the transmission, this time is typically longer.

Lowering the serial clock frequency will obviously extend the time from CS activation to first clock edge in the first case, but of course impact the overall performance due to the lower transmission clock rate.

Hence, depending on the timing specs and restrictions of the SPI peripheral, we can get higher overall performance with the SIO-based CS signal, since we can transmit at a higher serial clock rate.

Below the situation with a 2 MHz serial clock, SIO-based CS.

  • red: CS signal (oscilloscope trigger source, negative edge)
  • yellow: serial clock
  • blue: MOSI
  • green: MISO

The yellow vertical cursor lines on the left show the time between CS active low and serial clock start.

Note how the CS signal is kept low for the whole duration of a transaction “write timer register address (one byte), read timer data (three bytes)”. This works for this peripheral, but may not with others, in which case we would need to de- and re-activate CS accordingly via SIO in-between the write and the reads.

Below with the same 2 MHz serial clock, but SPI-based CS, as generated by the hardware (signal SSPFSSOUT). The clock peripheral does not work, ie. there’s no MISO data from the peripheral to the MCU (green). Note how the SPI device has automatically de- and re-activated the CS signal between putting the register address and reading the timer data.

Below with a serial clock lowered to 500 kHz, SPI-based CS. The longer clock period results in a longer CS-to-SCLK set-up time, and the clock peripheral operates again. (Note the changed time base of the oscilloscope to get useful data onto the screen.)

Peripheral Connections

The GPIO pins used for the wiring of the DS3234 real-time clock to the RP2040 are listed in module SPIrtc.

Output Terminal

See Set-up, one-terminal set-up.

Build and Run

Build module SPIrtc with Astrobe, and create and upload the UF2 file using abin2uf2.

Set Astrobe’s memory options as listed, and the library search path as explained. The program has been developed and tested with kernel-v1, which should be reflected in the library search path.


  1. Many SPI peripherals don’t care about this value, but some do, such as SD cards. For the DS3234 we use values that display well on an oscilloscope. 00H or 0FFH would not. ↩︎