Direct Memory Access (DMA) promises big potential to relieve the processor, and consequently, the control program from babysitting peripheral devices. This program is a preparatory step towards integrating DMA into the RTK framework. It implements and demonstrates the basic infrastructure to use DMA for text output to the serial terminal in a next step.1

The program StringBufOut shows how a program can write to a string buffer in lieu of directly to a serial terminal, and then flush the buffer in one go to the serial terminal.

DMA can empty a whole character buffer “into” a UART without any further interaction with the processor. However, as long as such a transfer from the buffer memory to the peripheral is in flight, no new output can be done, that is, the processor has to await the end of the transfer. If every, say, Out.Int is written to the UART via DMA, the program has to wait until the corresponding character data has been completely transmitted to the peripheral terminal, and then for the subsequent Out.* operation again. And again. We don’t gain too much.

The whole operation is much more efficient and effective, if we direct all our Out.* operations towards a buffer first, and then ask DMA to transfer the whole in one go, thusly emptying the buffer. While the DMA handles this transfer and transmission, our program can do other stuff, or with the kernel, the thread can transfer back control to the scheduler, allowing other threads to run. The thread does not have to babysit the text output.

DMA itself is not employed yet. The modules introduced below are the first step to then plug in DMA-enabled writing to the terminal.

Program Description

Apart from the main program StringBufOut, there are:

  • module StringDev: a TextIO-compatible Device, implementing a string buffer, which can be written to using a TextIO-compatible PutString procedure, just as a UART; StringBuffers uses PutString to create the TextIO.Writer to write to the buffer from the program, eg. using module Out;

  • module StringBuffers: implements a “virtual” analogue to module Terminals, that is, two exported TextIO.Writer, which can be used to write to two corresponding StringDev.Device, just as Terminals provides two Writer to write to two UARTs.

The text buffer in StringDev.Device is emptied by flushing its contents using another TextIO.Writer, for example as provided by Terminals. Hence, we can rewire the usual text output set-up as follows:

  • Module Out uses StringBuffers.W[0] and StringBuffers.W[1], in lieu of the usual Terminals.W[0] and Terminals.W[1], respectively. All output operations from module Out now write into the text buffers.

  • Module StringBuffers sets the output TextIO.Writer for its two StringDev.Device to Terminals.W[0] and Terminals.W[1], respectively.

Hence, we have linked the two StringBuffers.W[x] between Out and Terminals. The software will write all output into the buffers, and then flush the buffer through Terminals. Right now, this example program just uses the old, usual UARTstr.PutString for the terminals, but in a next step, we can replace this with a DMA-supported version of the same functionality, say, UARTdmaStr.PutString. This will be implemented – or so I hope – in the next example program, when I have figured out the DMA stuff, and created a corresponding library module.


The program is implemented in one module StringBufOut.mod.

  • thread 0: heartbeat blinker, also writes two counter values to the eight LEDs via module LEDext, using the new LEDext.SetLedBits procedure.
  • thread 1: writes test output to the serial terminal, via the string buffer as explained above.


  • This program makes use of the kernel-v1 to run two threads on core 0, but the use of the kernel is not essential for the demonstrated functionality.
  • The naming of modules StringDev and StringBuffers is tentative. Also, for now, they are just part of the example program, ie. not yet “promoted” to library modules.

Output Terminal

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

Build and Run

Build module StringBufOut 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. Better understanding the RP2040’s DMA functionality and creating corresponding library modules is also a prep step for further work on kernel-v2. ↩︎