Repository navigation
Guide DMA
Writing a full OLED frame or reading a block of flash keeps the CPU busy for milliseconds. With
DMA the hardware moves the bytes while your C# keeps running. This guide goes from the first
DmaBuffer to a display loop that draws the next frame while the last one is sent. The
reference is DMA and asynchronous transfers.
Works on every board running MicroCS 1.12+: ESP32 and RP2 really run SPI (and memory copies) in
the background; elsewhere the same calls finish immediately, so the script does not change.
The PC simulator (mcs --sim --sim-virtual) runs every example below.
In MicroCS Studio open Templates (Alt+T) → DMA for seven ready-to-run
examples (DmaBuffer, SPI in the background, double-buffered display, I2C / UART / QSPI reads,
memory copy), or type the snippets dmabuf and spiasync.
Console.WriteLine($"DMA {Hal.Has("DMA")} SPI {Hal.Has("DMA.SPI")} memory {Hal.Has("DMA.Memory")}");
Console.WriteLine($"channels {Dma.Channels}, alignment {Dma.Alignment}, in flight {Dma.MaxTransfers}");var frame = new DmaBuffer(1024); // 128 x 64 pixels, 1 bit each
frame.Fill(0x00);
frame[0] = 0xFF;
frame.CopyFrom(new byte[] { 1, 2, 3 }, 10);
Console.WriteLine(frame); // DmaBuffer[1024]A DmaBuffer lives in memory the DMA engine can reach (on ESP32: internal RAM). Use it like a
byte[].
SPI.Open(1, new SpiConfig(20_000_000, 0, 6, 7) { Cs = 10 });
var t = SPI.WriteAsync(1, frame); // returns at once
int work = 0;
while (!t.IsCompleted) work++; // the CPU is free meanwhile
Console.WriteLine($"{t.Result} bytes sent, {work} loops done meanwhile");Instead of polling you can wait (t.Wait(), t.Wait(100) with a timeout) or get a callback:
SPI.WriteAsync(1, frame).OnComplete(n => Console.WriteLine($"frame out ({n} bytes)"));Callbacks run between statements and while the script sleeps — never inside an interrupt.
var a = new DmaBuffer(1024);
var b = new DmaBuffer(1024);
DmaTransfer sending = null;
for (int f = 0; f < 100; f++) {
var draw = (f % 2 == 0) ? a : b;
draw.Fill(0);
draw[f % 1024] = 0xFF; // ... render the next frame here
if (sending != null) sending.Wait(); // the previous frame is out
sending = SPI.WriteAsync(1, draw);
}
sending.Wait();While a transfer uses a buffer, writing to it throws InvalidOperationException — that is why
the loop draws into the other buffer.
var regs = new DmaBuffer(6);
I2C.WriteReadAsync(0, 0x68, new byte[] { 0x3B }, regs).Wait(); // MPU-6050 accelerometer
var id = new DmaBuffer(3);
QSPI.ReadAsync(0, 0x9F, -1, id).Wait(); // flash JEDEC id
UART.Open(1, 9600);
var gps = new DmaBuffer(64);
var line = UART.ReadAsync(1, gps); // completes when 64 bytes arrived
if (!line.Wait(2000)) line.Cancel();A failed transfer (an I²C NACK, a timeout) throws when you Wait() for it; Succeeded and
Error tell you without an exception.
var copy = new DmaBuffer(1024);
Dma.Copy(copy, frame); // DMA engine where there is one, CPU copy elsewhere
Dma.Fill(copy, 0x55, 0, 512);
Dma.CopyAsync(copy, frame).Wait();-
DmaBuffer is in use by a DMA transfer—Wait()for the transfer before writing to its buffer, or use a second buffer (step 4). -
too many DMA transfers in flight— more thanDma.MaxTransfers(MCS_HAL_MAX_DMA) were started without waiting;Dma.WaitAll()or raiseMCS_HAL_MAX_DMA(1–8). -
OutOfMemoryException: no DMA-capable memory— smaller buffers; ESP32 DMA memory is the internal RAM. -
Nothing is faster —
Hal.Has("DMA.SPI")isfalseon this board: transfers are synchronous there. - Bus settings (pins,
Dma = false,MaxTransfer): peripheral settings guide.
Generated from the MicroCS repository - edit the files there, not in the wiki.
Start here
Step-by-step guides
- Guides
- Guide PC Simulator
- Guide ESP32
- Guide ESP32 C3 OLED
- Guide Raspberry Pi Pico
- Guide STM32
- Guide Arduino
- Guide Zephyr
- Guide Peripheral Settings
- Guide DMA
Hardware
- Hardware API
- Peripheral Configuration
- DMA
- Device Drivers
- Wi Fi and Networking
- U8g2 Displays
- Filesystem
- Threads
- Scheduler
Ports
- Porting and Build Systems
- Port ESP32
- Port RP2040 RP2350
- Port STM32
- Port STM32 Firmware
- Port Zephyr
- Port Arduino
C# and the runtime
Embedding and tuning
Internals
Project