nimphea/sys/system

Source   Edit  

System

Core system utilities for Daisy hardware platform.

This module provides access to essential system-level functionality including:

  • Clock configuration (400MHz or 480MHz CPU speeds)
  • Timing functions (milliseconds, microseconds, ticks)
  • Bootloader control (firmware updates via DFU/USB)
  • Memory region detection
  • Cache management (instruction and data caches)

The System class manages the STM32H750 microcontroller's clock tree, MPU (Memory Protection Unit), DMA initialization, and cache handling.

Basic Usage

Example 1: Default initialization (400MHz, caches enabled)

import nimphea/sys/system

var sys: System
sys.init()  # Uses default 400MHz configuration

Example 2: Boost mode initialization (480MHz)

import nimphea/sys/system

var sys: System
var cfg = boostSystemConfig()  # 480MHz preset
sys.init(cfg)

Example 3: Custom configuration

import nimphea/sys/system

var cfg = defaultSystemConfig()
cfg.cpu_freq = FREQ_480MHZ
cfg.use_dcache = true
cfg.use_icache = true

var sys: System
sys.init(cfg)

Timing Functions

The System class provides three timing mechanisms:

  1. Millisecond timing - 1kHz SysTick timer
  2. Microsecond timing - High-resolution internal timer
  3. CPU tick timing - Direct CPU cycle counting
let ms = getNow()        # Milliseconds since boot
let us = getUs()         # Microseconds (wraps every ~71 minutes)
let ticks = getTick()    # CPU ticks (frequency varies)

delay(1000)              # Wait 1 second
delayUs(500)             # Wait 500 microseconds

# Measure execution time
let start = getUs()
performOperation()
let duration = getUs() - start
echo "Operation took ", duration, " us"

Clock Information

Query the system clock frequencies:

echo "System Clock: ", getSysClkFreq(), " Hz"
echo "AHB Clock: ", getHClkFreq(), " Hz"
echo "APB1 Clock: ", getPClk1Freq(), " Hz"
echo "APB2 Clock: ", getPClk2Freq(), " Hz"

Bootloader Control

Return to bootloader for firmware updates:

# Option 1: STM32 DFU bootloader (default)
resetToBootloader(STM)

# Option 2: Daisy bootloader (if installed)
resetToBootloader(DAISY)

# Option 3: Daisy bootloader, skip timeout
resetToBootloader(DAISY_SKIP_TIMEOUT)

# Option 4: Daisy bootloader, infinite timeout
resetToBootloader(DAISY_INFINITE_TIMEOUT)

Memory Region Detection

Determine which memory region an address belongs to:

let region = getProgramMemoryRegion()
case region
of INTERNAL_FLASH:
  echo "Running from internal flash"
of QSPI:
  echo "Running from external QSPI flash"
else:
  echo "Running from RAM or other region"

See Also

  • sys/dma - DMA cache coherency functions
  • examples/system_control.nim - Complete usage example

Types

BootInfo {.importcpp: "daisy::System::BootInfo".} = object
  status*: BootInfoType      ## Boot command type
  data*: uint32              ## Additional data for boot command
  version*: BootloaderVersion ## Detected bootloader version

Boot information structure (internal use).

Stored in backup SRAM to persist across resets. Used for bootloader communication.

Source   Edit  
BootInfoType {.importcpp: "daisy::System::BootInfo::Type", size: 4.} = enum
  INVALID = 0,              ## No boot command
  JUMP = 3735928559,        ## Jump to bootloader
  SKIP_TIMEOUT = 1526640647, ## Skip bootloader timeout
  INF_TIMEOUT = 2953268986   ## Infinite bootloader timeout

Boot information type codes (internal use).

These magic values are written to backup SRAM to communicate between application and bootloader.

Source   Edit  
BootloaderMode {.importcpp: "daisy::System::BootloaderMode", size: 4.} = enum
  STM = 0,                  ## STM32 DFU bootloader (always available)
  DAISY,                    ## Daisy bootloader with timeout
  DAISY_SKIP_TIMEOUT,       ## Daisy bootloader, skip timeout window
  DAISY_INFINITE_TIMEOUT     ## Daisy bootloader, wait indefinitely

Bootloader mode selection for firmware updates.

STM mode: Uses STM32's built-in DFU bootloader. Works on all boards, allows updating internal flash via USB.

DAISY modes: Require Daisy bootloader to be installed. Allow updating QSPI flash with larger programs (up to 8MB).

Source   Edit  
BootloaderVersion {.importcpp: "daisy::System::BootInfo::Version", size: 4.} = enum
  LT_v6_0 = 0,              ## Bootloader version < 6.0 (legacy)
  NONE,                     ## No Daisy bootloader present
  v6_0,                     ## Bootloader version 6.0
  v6_1,                     ## Bootloader version 6.1 or greater
  LAST                       ## Sentinel value (not a valid version)

Daisy bootloader version detection.

Used to check if Daisy bootloader is installed and which version.

Source   Edit  
MemoryRegion {.importcpp: "daisy::System::MemoryRegion", size: 4.} = enum
  INTERNAL_FLASH = 0,       ## Internal flash memory (128KB on STM32H750)
  ITCMRAM,                  ## Instruction Tightly Coupled Memory (64KB)
  DTCMRAM,                  ## Data Tightly Coupled Memory (128KB)
  SRAM_D1,                  ## SRAM in D1 domain (512KB)
  SRAM_D2,                  ## SRAM in D2 domain (288KB)
  SRAM_D3,                  ## SRAM in D3 domain (64KB)
  SDRAM,                    ## External SDRAM (if present on board)
  QSPI,                     ## External QSPI flash (8MB on Daisy Seed)
  INVALID_ADDRESS            ## Address does not map to known region

Memory regions available on Daisy Seed hardware.

The STM32H750 has multiple memory regions with different characteristics (speed, size, persistence).

Typical program locations:

  • INTERNAL_FLASH: Small programs (<128KB)
  • QSPI: Large programs via Daisy bootloader
  • SRAM_D1/D2/D3: RAM-based programs (lost on power cycle)
Source   Edit  
SysClkFreq {.importcpp: "daisy::System::Config::SysClkFreq", size: 4.} = enum
  FREQ_400MHZ,              ## 400MHz system clock (default, recommended)
  FREQ_480MHZ                ## 480MHz system clock (boost mode, max performance)

System clock frequency options for the STM32H750.

The system clock drives all peripheral buses (AHB, APB1, APB2).

  • FREQ_400MHZ: Default safe speed, lower power consumption
  • FREQ_480MHZ: Maximum speed, higher performance, higher power draw

Note: Some Daisy Seed boards may not reliably run at 480MHz. If you experience instability, use 400MHz.

Source   Edit  
System {.importcpp: "daisy::System".} = object

System controller for Daisy hardware.

Manages clock configuration, timing, bootloader access, and system-level initialization.

Note: Most methods are static - you can call them without creating a System object. The object is only needed for initialization (init(), deInit()).

Source   Edit  
SystemConfig {.importcpp: "daisy::System::Config".} = object
  cpu_freq*: SysClkFreq      ## System clock frequency selection
  use_dcache*: bool          ## Enable data cache (D-cache)
  use_icache*: bool          ## Enable instruction cache (I-cache)
  skip_clocks*: bool         ## Skip clock configuration (advanced use only)

Configuration structure for System initialization.

Controls clock frequency and cache settings.

Default values (via defaultSystemConfig()):

  • cpu_freq: FREQ_400MHZ
  • use_dcache: true (data cache enabled)
  • use_icache: true (instruction cache enabled)
  • skip_clocks: false (configure clocks normally)

Boost values (via boostSystemConfig()):

  • cpu_freq: FREQ_480MHZ (maximum speed)
  • Caches enabled
Source   Edit  

Consts

kQspiBootloaderOffset = 0x00040000'u32

Daisy bootloader offset from QSPI base address (0x90000000).

The first 256KB (0x40000 bytes) of QSPI flash is reserved for the Daisy bootloader. User programs start at 0x90040000.

Important: When writing data to QSPI, avoid the first 256KB to preserve the bootloader.

Source   Edit  

Procs

proc boost(this: var SystemConfig) {.importcpp: "#.Boost()",
                                     header: "sys/system.h", ...raises: [],
                                     tags: [], forbids: [].}

Set configuration to boost mode.

Initializes the configuration with:

  • CPU frequency: 480MHz (maximum)
  • Data cache: Enabled
  • Instruction cache: Enabled
  • Skip clocks: false

Warning: Some Daisy Seed boards may not run reliably at 480MHz. If you experience instability, use defaults() instead.

Example:

var cfg: SystemConfig
cfg.boost()

Source   Edit  
proc boostSystemConfig(): SystemConfig {....raises: [], tags: [], forbids: [].}

Create SystemConfig with boost settings (480MHz).

Returns a configuration struct initialized to:

  • CPU frequency: 480MHz (maximum)
  • Data cache: Enabled
  • Instruction cache: Enabled
  • Skip clocks: false

Returns: SystemConfig with boost settings

Warning: Some Daisy Seed boards may not run reliably at 480MHz. If you experience instability, use defaultSystemConfig() instead.

Example:

var cfg = boostSystemConfig()
var sys: System
sys.init(cfg)

Source   Edit  
proc defaults(this: var SystemConfig) {.importcpp: "#.Defaults()",
                                        header: "sys/system.h", ...raises: [],
                                        tags: [], forbids: [].}

Set configuration to default values.

Initializes the configuration with:

  • CPU frequency: 400MHz
  • Data cache: Enabled
  • Instruction cache: Enabled
  • Skip clocks: false

Example:

var cfg: SystemConfig
cfg.defaults()

Source   Edit  
proc defaultSystemConfig(): SystemConfig {....raises: [], tags: [], forbids: [].}

Create SystemConfig with default settings.

Returns a configuration struct initialized to:

  • CPU frequency: 400MHz
  • Data cache: Enabled
  • Instruction cache: Enabled
  • Skip clocks: false

Returns: SystemConfig with defaults

Example:

var cfg = defaultSystemConfig()
cfg.cpu_freq = FREQ_480MHZ  # Override to boost mode
sys.init(cfg)

Source   Edit  
proc deInit(this: var System) {.importcpp: "#.DeInit()", ...raises: [], tags: [],
                                forbids: [].}

Deinitialize System and all peripherals.

Reverses the initialization performed by init(). Rarely needed in embedded applications (no OS to return to).

Source   Edit  
proc delay(delay_ms: uint32) {.importcpp: "daisy::System::Delay(@)", ...raises: [],
                               tags: [], forbids: [].}

Blocking delay in milliseconds.

Uses SysTick timer (1ms resolution).

Parameters:

  • delay_ms - Time to delay in milliseconds

Warning: This is a blocking delay. Audio processing will stop. Only use during initialization or non-critical sections.

Example:

echo "Waiting 1 second..."
delay(1000)
echo "Done!"

Source   Edit  
proc delayTicks(delay_ticks: uint32) {.importcpp: "daisy::System::DelayTicks(@)",
                                       ...raises: [], tags: [], forbids: [].}

Blocking delay in CPU ticks.

Most precise delay mechanism, but tick frequency varies with clock config.

Parameters:

  • delay_ticks - Number of ticks to delay

Example:

let freq = getTickFreq()
let ticksFor1ms = freq div 1000
delayTicks(ticksFor1ms)  # Delay 1ms using ticks

Source   Edit  
proc delayUs(delay_us: uint32) {.importcpp: "daisy::System::DelayUs(@)",
                                 ...raises: [], tags: [], forbids: [].}

Blocking delay in microseconds.

Uses internal timer for higher precision than delay().

Parameters:

  • delay_us - Time to delay in microseconds

Warning: Blocking delay. Audio processing will stop.

Example:

delayUs(500)  # Wait 500 microseconds

Source   Edit  
proc getBootloaderVersion(): BootloaderVersion {.
    importcpp: "daisy::System::GetBootloaderVersion()", ...raises: [], tags: [],
    forbids: [].}

Check which version of Daisy bootloader is installed (if any).

Returns: BootloaderVersion enum

Example:

let ver = getBootloaderVersion()
case ver
of NONE:
  echo "No Daisy bootloader (STM DFU only)"
of v6_1:
  echo "Daisy bootloader v6.1+ installed"
else:
  echo "Older bootloader version"

Source   Edit  
proc getClockInfo(): tuple[sysclk, hclk, pclk1, pclk2, tickFreq: uint32] {.
    ...raises: [], tags: [], forbids: [].}

Get all clock frequencies as a tuple.

Convenience function for displaying all clock information.

Returns: Tuple with all clock frequencies in Hz

Example:

let clocks = getClockInfo()
echo "System: ", clocks.sysclk div 1_000_000, " MHz"
echo "AHB: ", clocks.hclk div 1_000_000, " MHz"
echo "APB1: ", clocks.pclk1 div 1_000_000, " MHz"
echo "APB2: ", clocks.pclk2 div 1_000_000, " MHz"
echo "Tick: ", clocks.tickFreq div 1_000_000, " MHz"

Source   Edit  
proc getConfig(this: System): SystemConfig {.importcpp: "#.GetConfig()",
    ...raises: [], tags: [], forbids: [].}

Get the current System configuration.

Returns a copy of the configuration used during initialization.

Returns: SystemConfig struct with current settings

Example:

let cfg = sys.getConfig()
echo "Running at: ", cfg.cpu_freq
echo "D-cache: ", cfg.use_dcache

Source   Edit  
proc getHClkFreq(): uint32 {.importcpp: "daisy::System::GetHClkFreq()",
                             ...raises: [], tags: [], forbids: [].}

Get AHB (HCLK) frequency in Hz.

The HCLK clocks the CPU, memory, and DMA controllers.

Returns: Frequency in Hz

Source   Edit  
proc getMemoryRegion(address: uint32): MemoryRegion {.
    importcpp: "daisy::System::GetMemoryRegion(@)", ...raises: [], tags: [],
    forbids: [].}

Determine which memory region an address belongs to.

Parameters:

  • address - Memory address to check

Returns: MemoryRegion enum or INVALID_ADDRESS

Example:

let data = [1, 2, 3, 4]
let address = cast[uint32](data[0].addr)
let region = getMemoryRegion(address)
echo "Data is in: ", region

Source   Edit  
proc getNow(): uint32 {.importcpp: "daisy::System::GetNow()", ...raises: [],
                        tags: [], forbids: [].}

Get milliseconds since system boot.

Uses 1kHz SysTick timer. Wraps every ~49.7 days.

Returns: Milliseconds since boot (uint32)

Example:

let startTime = getNow()
performTask()
let elapsed = getNow() - startTime
echo "Task took ", elapsed, " ms"

Source   Edit  
proc getPClk1Freq(): uint32 {.importcpp: "daisy::System::GetPClk1Freq()",
                              ...raises: [], tags: [], forbids: [].}

Get APB1 peripheral clock frequency in Hz.

Many peripherals use this clock (I2C, UART, SPI, timers).

Note: Some timers run at PCLK1 * 2.

Returns: Frequency in Hz

Source   Edit  
proc getPClk2Freq(): uint32 {.importcpp: "daisy::System::GetPClk2Freq()",
                              ...raises: [], tags: [], forbids: [].}

Get APB2 peripheral clock frequency in Hz.

Some peripherals use this faster clock.

Note: Some timers run at PCLK2 * 2.

Returns: Frequency in Hz

Source   Edit  
proc getProgramMemoryRegion(): MemoryRegion {.
    importcpp: "daisy::System::GetProgramMemoryRegion()", ...raises: [], tags: [],
    forbids: [].}

Detect which memory region the program is executing from.

Returns: MemoryRegion enum (INTERNAL_FLASH, QSPI, etc.)

Example:

let region = getProgramMemoryRegion()
case region
of INTERNAL_FLASH:
  echo "Running from internal flash (< 128KB)"
of QSPI:
  echo "Running from QSPI flash (Daisy bootloader)"
else:
  echo "Running from RAM"

Source   Edit  
proc getSysClkFreq(): uint32 {.importcpp: "daisy::System::GetSysClkFreq()",
                               ...raises: [], tags: [], forbids: [].}

Get system clock frequency in Hz.

Returns the core system clock that feeds all peripheral buses.

Returns: Frequency in Hz (400000000 or 480000000)

Example:

let freq = getSysClkFreq()
echo "System clock: ", freq div 1_000_000, " MHz"

Source   Edit  
proc getTick(): uint32 {.importcpp: "daisy::System::GetTick()", ...raises: [],
                         tags: [], forbids: [].}

Get current CPU tick count.

Increments at getTickFreq() Hz (typically PCLK1 * 2). Useful for precise performance measurement.

Returns: Tick count (uint32)

Example:

let ticksStart = getTick()
someFunction()
let ticksElapsed = getTick() - ticksStart
let freq = getTickFreq()
echo "Function took ", ticksElapsed, " ticks"
echo "At ", freq, " Hz = ", ticksElapsed.float / freq.float, " seconds"

Source   Edit  
proc getTickFreq(): uint32 {.importcpp: "daisy::System::GetTickFreq()",
                             ...raises: [], tags: [], forbids: [].}

Get tick timer frequency in Hz.

Returns the rate at which getTick() increments. Typically PCLK1 * 2.

Returns: Frequency in Hz (e.g., 200000000 for 200MHz)

Source   Edit  
proc getUs(): uint32 {.importcpp: "daisy::System::GetUs()", ...raises: [],
                       tags: [], forbids: [].}

Get microseconds from internal timer.

Higher resolution than getNow() but wraps faster (~71 minutes).

Returns: Microseconds (uint32, wraps around)

Example:

let start = getUs()
criticalOperation()
let duration = getUs() - start
echo "Operation: ", duration, " us"

Source   Edit  
proc init(this: var System) {.importcpp: "#.Init()", ...raises: [], tags: [],
                              forbids: [].}

Initialize System with default configuration.

Default settings:

  • CPU frequency: 400MHz
  • Data cache: Enabled
  • Instruction cache: Enabled

This is equivalent to:

var cfg = defaultSystemConfig()
sys.init(cfg)

Example:

var sys: System
sys.init()
echo "System initialized at ", getSysClkFreq(), " Hz"

Source   Edit  
proc init(this: var System; config: SystemConfig) {.importcpp: "#.Init(#)",
    ...raises: [], tags: [], forbids: [].}

Initialize System with custom configuration.

Parameters:

  • config - SystemConfig with desired clock and cache settings

Example:

var cfg = boostSystemConfig()  # 480MHz preset
var sys: System
sys.init(cfg)

Source   Edit  
proc initBackupSram() {.importcpp: "daisy::System::InitBackupSram()",
                        ...raises: [], tags: [], forbids: [].}

Initialize backup SRAM region.

Backup SRAM retains data when powered by VBAT (coin cell battery). Used for bootloader communication and persistent state.

Note: Usually called automatically by System.init().

Source   Edit  
proc jumpToQspi(this: var System) {.importcpp: "#.JumpToQspi()", ...raises: [],
                                    tags: [], forbids: [].}

Jump to code at external QSPI flash base address (0x90000000).

Warning: If no valid code exists at that address, the CPU will likely fall into an infinite loop or fault.

This is used for advanced multi-stage boot scenarios.

Source   Edit  
proc resetToBootloader(mode: BootloaderMode = STM) {.
    importcpp: "daisy::System::ResetToBootloader(@)", ...raises: [], tags: [],
    forbids: [].}

Reset and enter bootloader mode for firmware updates.

Parameters:

  • mode - Bootloader mode (default: STM)

Modes:

  • STM: STM32 DFU bootloader (always available, updates internal flash)
  • DAISY: Daisy bootloader (if installed, updates QSPI flash)
  • DAISY_SKIP_TIMEOUT: Daisy bootloader, skip timeout
  • DAISY_INFINITE_TIMEOUT: Daisy bootloader, wait forever

Example:

# User holds button for 3 seconds
if buttonHeldFor3Seconds():
  resetToBootloader(STM)  # Enter DFU mode

Source   Edit