External SDRAM support for libDaisy Nim wrapper
This module provides support for the external SDRAM chip on Daisy Seed. The SDRAM provides 64MB of additional RAM for audio buffers, delay lines, wavetables, and other large data structures.
Basic Usage:
import nimphea import nimphea/sys/sdram var hw = newDaisySeed() var sdram = newSdramHandle() # Large buffer in SDRAM var delayBuffer {.codegenDecl: "$# $# __attribute__((section(\".sdram_bss\")))".}: array[480000, float32] proc main() = hw.init() # Initialize SDRAM if sdram.init() != SDRAM_OK: return # Now you can use delayBuffer and other SDRAM variables for i in 0..<len(delayBuffer): delayBuffer[i] = 0.0 while true: hw.delayMs(100)
SDRAM Memory Details:
- Base Address: 0xC0000000
- Size: 64MB (on Daisy Seed)
- Speed: 100MHz
- Chip: AS4C16M16SA or equivalent
Important Notes:
- SDRAM must be initialized before accessing SDRAM variables
- Use codegenDecl pragma with section attribute for SDRAM placement
- SDRAM is not suitable for real-time audio processing (use internal RAM)
- Perfect for buffers, wavetables, delay lines, sample storage
Delay Line Example:
const SAMPLE_RATE = 48000 const MAX_DELAY_SEC = 10 var delayLine {.sdramBss.}: array[SAMPLE_RATE * MAX_DELAY_SEC, float32] var delayIndex = 0 proc audioCallback(input: ptr ptr cfloat, output: ptr ptr cfloat, size: csize_t) {.cdecl.} = for i in 0..<size: # Write to delay line delayLine[delayIndex] = input[0][i] # Read from delay line (simple) output[0][i] = delayLine[delayIndex] delayIndex = (delayIndex + 1) mod len(delayLine)
Wavetable Example:
const TABLE_SIZE = 2048 const NUM_TABLES = 64 var wavetables {.sdramBss.}: array[NUM_TABLES * TABLE_SIZE, float32] proc initWavetables() = for tableNum in 0..<NUM_TABLES: for i in 0..<TABLE_SIZE: let phase = float32(i) / float32(TABLE_SIZE) let idx = tableNum * TABLE_SIZE + i wavetables[idx] = sin(phase * 2.0 * PI * float32(tableNum + 1))
Sample Buffer Example:
# Store 60 seconds of stereo audio at 48kHz const BUFFER_SIZE = 48000 * 60 * 2 var sampleBuffer {.sdramBss.}: array[BUFFER_SIZE, float32] var recordPos = 0 var playPos = 0 proc recordSample(left, right: float32) = if recordPos < BUFFER_SIZE - 1: sampleBuffer[recordPos] = left sampleBuffer[recordPos + 1] = right recordPos += 2 proc playSample(): tuple[left, right: float32] = if playPos < recordPos - 1: result.left = sampleBuffer[playPos] result.right = sampleBuffer[playPos + 1] playPos += 2 else: result = (0.0'f32, 0.0'f32)
Types
SdramHandle {.importcpp: "SdramHandle".} = object
- Source Edit
SdramMemoryInfo = object totalBytes*: int usedBytes*: int freeBytes*: int usedMegabytes*: float freeMegabytes*: float percentUsed*: float
- Source Edit
SdramResult {.importcpp: "SdramHandle::Result", size: 4.} = enum SDRAM_OK = 0, SDRAM_ERR
- Source Edit
Consts
SDRAM_BASE_ADDRESS = 0xC0000000'u32
- Base address of SDRAM Source Edit
SDRAM_SIZE = 67108864
- Total SDRAM size (64MB) Source Edit
SDRAM_SPEED = 100000000
- SDRAM clock speed (100MHz) Source Edit
Procs
proc calcDelayBufferSize(sampleRate: float; delayTimeSeconds: float): int {. header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Calculate the number of samples needed for a delay buffer Example: let size = calcDelayBufferSize(48000.0, 5.0) # 5 second delay Source Edit
proc calcWavetableSize(tableSize: int; numTables: int): int {. header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Calculate total size for multiple wavetables Example: let size = calcWavetableSize(2048, 64) # 64 wavetables of 2048 samples Source Edit
proc clearSdramBss() {.header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
-
Clear all SDRAM BSS memory to zero. This should be called after SDRAM initialization if you want to ensure all SDRAM variables start at zero.
Safety: The pointer arithmetic here is guaranteed safe because:
- _ssdram_bss and _esdram_bss are linker symbols defined in the linker script and represent the start/end of the SDRAM BSS section
- Both symbols are aligned to 4-byte (uint32) boundaries by the linker, enabling safe iteration by uint32 increments
- The loop terminates at theEnd, guaranteeing no out-of-bounds access
Performance: This is a slow operation (may take several milliseconds on 64MB) but necessary for zeroing uninitialized SDRAM variables.
Source Edit proc deInit(this: var SdramHandle): SdramResult {.importcpp: "#.DeInit(@)", header: "dev/sdram.h", header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Source Edit
proc getMemoryInfo(): SdramMemoryInfo {.header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Get information about SDRAM memory usage Source Edit
proc getSdramAddress(): uint32 {.header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Get the base address of SDRAM Source Edit
proc getSdramBssSize(): int {.inline, header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Get the size of used SDRAM BSS memory Source Edit
proc getSdramSize(): int {.header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Get the total size of SDRAM in bytes Source Edit
proc init(this: var SdramHandle): SdramResult {.importcpp: "#.Init(@)", header: "dev/sdram.h", header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Source Edit
proc megabytesToSamples(megabytes: float; bytesPerSample: int = 4): int {. header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Convert megabytes to number of samples (assumes float32 by default) Example: let samples = megabytesToSamples(10.0) # 10MB of float32 samples Source Edit
proc newSdramHandle(): SdramHandle {.importcpp: "SdramHandle()", constructor, header: "dev/sdram.h", header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Source Edit
proc samplesToMegabytes(samples: int; bytesPerSample: int = 4): float {. header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
- Convert number of samples to megabytes (assumes float32 by default) Example: let mb = samplesToMegabytes(1000000) Source Edit
Templates
template sdramArray(T: typedesc; size: int): untyped {..}
- Create an array in SDRAM BSS section Example: var buffer = sdramArray(float32, 100000) Source Edit