nimphea/nimphea_sai

Source   Edit  

SAI (Serial Audio Interface) support for Nimphea

This module provides low-level SAI peripheral access for the Daisy Audio Platform. SAI is used for I2S audio communication with external codecs.

Note: For most applications, you should use the high-level audio API in nimphea.nim instead of using SAI directly. This module is provided for advanced use cases requiring custom audio configurations.

Example - Custom SAI configuration:

import nimphea, nimphea_sai

var sai: SaiHandle
var config = newSaiConfig()

# Configure SAI1 for 48kHz, 24-bit audio
config.periph = SAI_1
config.sr = SAI_48KHZ
config.bit_depth = SAI_24BIT
config.a_sync = MASTER
config.b_sync = SLAVE
config.a_dir = RECEIVE
config.b_dir = TRANSMIT

# Configure pins
config.pin_config.fs = initPin(PORTE, 4)
config.pin_config.mclk = initPin(PORTE, 2)
config.pin_config.sck = initPin(PORTE, 5)
config.pin_config.sa = initPin(PORTE, 6)
config.pin_config.sb = initPin(PORTE, 3)

# Initialize SAI
let result = sai.init(config)
if result != SAI_OK:
  # Handle error
  discard

# Define audio callback
proc audioCallback(inputBuf, outputBuf: ptr int32, size: csize_t) {.cdecl.} =
  # Process audio samples
  for i in 0..<size:
    outputBuf[i] = inputBuf[i]  # Pass through

# Allocate DMA buffers (must be in DMA-capable memory)
var rxBuffer: array[256, int32]
var txBuffer: array[256, int32]

# Start DMA transfer
discard sai.startDma(rxBuffer[0].addr, txBuffer[0].addr, 256, audioCallback)

Types

GPIOPort {.importcpp: "daisy::GPIOPort", size: 4.} = enum
  PORTA = 0, PORTB = 1, PORTC = 2, PORTD = 3, PORTE = 4, PORTF = 5, PORTG = 6,
  PORTH = 7, PORTI = 8, PORTJ = 9, PORTK = 10, PORTX = 255
Source   Edit  
Pin {.importcpp: "daisy::Pin", bycopy.} = object
  port* {.importcpp: "port".}: GPIOPort
  pin* {.importcpp: "pin".}: uint8
Source   Edit  
SaiBitDepth {.importcpp: "daisy::SaiHandle::Config::BitDepth", size: 4.} = enum
  SAI_16BIT, SAI_24BIT, SAI_32BIT
Source   Edit  
SaiCallback = proc (inputBuf, outputBuf: ptr int32; size: csize_t) {.cdecl.}
Source   Edit  
SaiConfig {.importcpp: "daisy::SaiHandle::Config", bycopy.} = object
  periph* {.importcpp: "periph".}: SaiPeripheral
  pin_config* {.importcpp: "pin_config".}: SaiPinConfig
  sr* {.importcpp: "sr".}: SaiSampleRate
  bit_depth* {.importcpp: "bit_depth".}: SaiBitDepth
  a_sync* {.importcpp: "a_sync".}: SaiSync
  b_sync* {.importcpp: "b_sync".}: SaiSync
  a_dir* {.importcpp: "a_dir".}: SaiDirection
  b_dir* {.importcpp: "b_dir".}: SaiDirection
Source   Edit  
SaiDirection {.importcpp: "daisy::SaiHandle::Config::Direction", size: 4.} = enum
  TRANSMIT, RECEIVE
Source   Edit  
SaiPeripheral {.importcpp: "daisy::SaiHandle::Config::Peripheral", size: 4.} = enum
  SAI_1, SAI_2
Source   Edit  
SaiPinConfig {.importcpp: "daisy::SaiHandle::Config::pin_config", bycopy.} = object
  mclk* {.importcpp: "mclk".}: Pin
  fs* {.importcpp: "fs".}: Pin
  sck* {.importcpp: "sck".}: Pin
  sa* {.importcpp: "sa".}: Pin
  sb* {.importcpp: "sb".}: Pin
Source   Edit  
SaiResult {.importcpp: "daisy::SaiHandle::Result", size: 4.} = enum
  SAI_OK = 0, SAI_ERR = 1
Source   Edit  
SaiSampleRate {.importcpp: "daisy::SaiHandle::Config::SampleRate", size: 4.} = enum
  SAI_8KHZ, SAI_16KHZ, SAI_32KHZ, SAI_48KHZ, SAI_96KHZ
Source   Edit  
SaiSync {.importcpp: "daisy::SaiHandle::Config::Sync", size: 4.} = enum
  MASTER, SLAVE
Source   Edit  

Procs

proc configurePinsStandard(config: var SaiConfig; port: GPIOPort) {....raises: [],
    tags: [], forbids: [].}

Configure SAI pins using a standard layout on a single GPIO port.

This is a convenience function for common pin configurations.

Standard pin layout (example for PORTE):

  • MCLK: port pin 2
  • FS: port pin 4
  • SCK: port pin 5
  • SA: port pin 6
  • SB: port pin 3

Parameters: config: SAI configuration to modify port: GPIO port to use for all pins

Example:

var config = newSaiConfig()
config.configurePinsStandard(PORTE)

Source   Edit  
proc configureStandard48k24bit(config: var SaiConfig;
                               peripheral: SaiPeripheral = SAI_1) {....raises: [],
    tags: [], forbids: [].}

Configure SAI for standard 48kHz, 24-bit stereo audio.

This sets up common defaults:

  • Sample rate: 48kHz
  • Bit depth: 24-bit
  • Block A: Master, Receive (input)
  • Block B: Slave, Transmit (output)

Note: You still need to configure pins separately.

Parameters: config: SAI configuration to modify peripheral: SAI peripheral to use (default: SAI_1)

Example:

var config = newSaiConfig()
config.configureStandard48k24bit()
config.configurePinsStandard(PORTE)

Source   Edit  
proc deinit(sai: var SaiHandle): SaiResult {.inline, ...raises: [], tags: [],
    forbids: [].}

Deinitialize the SAI peripheral.

Returns: SAI_OK on success, SAI_ERR on failure

Source   Edit  
proc getBlockRate(sai: SaiHandle): float {.inline, ...raises: [], tags: [],
    forbids: [].}

Get the block rate of the current stream.

Based on buffer size and sample rate.

Returns: Block rate in Hz

Source   Edit  
proc getBlockSize(sai: SaiHandle): int {.inline, ...raises: [], tags: [],
    forbids: [].}

Get the number of samples per audio block.

Calculated as: Buffer Size / 2 / number of channels

Returns: Block size in samples

Source   Edit  
proc getConfig(sai: SaiHandle): SaiConfig {.inline, ...raises: [], tags: [],
    forbids: [].}

Get the current configuration of the SAI peripheral.

Returns: The current SAI configuration

Source   Edit  
proc getOffset(sai: SaiHandle): int {.inline, ...raises: [], tags: [], forbids: [].}

Get the current offset within the SAI buffer.

Returns: Offset (will be either 0 or size/2)

Source   Edit  
proc getSampleRate(sai: SaiHandle): float {.inline, ...raises: [], tags: [],
    forbids: [].}

Get the sample rate based on the current configuration.

Returns: Sample rate in Hz (e.g., 48000.0 for 48kHz)

Source   Edit  
proc init(sai: var SaiHandle; config: SaiConfig): SaiResult {.inline,
    ...raises: [], tags: [], forbids: [].}

Initialize the SAI peripheral with the given configuration.

Parameters: sai: The SAI handle to initialize config: Configuration structure

Returns: SAI_OK on success, SAI_ERR on failure

Example:

var sai: SaiHandle
var config = newSaiConfig()
config.periph = SAI_1
config.sr = SAI_48KHZ
config.bit_depth = SAI_24BIT
let result = sai.init(config)

Source   Edit  
proc initPin(port: GPIOPort; pin: uint8): Pin {.importcpp: "daisy::Pin(@)",
    constructor, header: "per/sai.h", ...raises: [], tags: [], forbids: [].}
Source   Edit  
proc isInitialized(sai: SaiHandle): bool {.inline, ...raises: [], tags: [],
    forbids: [].}

Check if the SAI peripheral is initialized.

Returns: true if initialized, false otherwise

Source   Edit  
proc newSaiConfig(): SaiConfig {.importcpp: "daisy::SaiHandle::Config()",
                                 constructor, header: "per/sai.h", ...raises: [],
                                 tags: [], forbids: [].}
Source   Edit  
proc startDma(sai: var SaiHandle; bufferRx, bufferTx: ptr int32; size: int;
              callback: SaiCallback): SaiResult {.inline, ...raises: [], tags: [],
    forbids: [].}

Start DMA-based audio transfer in circular buffer mode.

The callback will be called when half of the buffer is ready, processing size/2 samples per callback.

Important: Buffers must be allocated in DMA-capable memory. Use DSY_DMA_BUFFER_SECTOR pragma or ensure proper memory placement.

Parameters: sai: The initialized SAI handle bufferRx: Pointer to receive buffer (for input) bufferTx: Pointer to transmit buffer (for output) size: Total buffer size in samples callback: Function to call for audio processing

Returns: SAI_OK on success, SAI_ERR on failure

Example:

proc audioCallback(inputBuf, outputBuf: ptr int32, size: csize_t) {.cdecl.} =
  for i in 0..<size:
    outputBuf[i] = inputBuf[i]

var rxBuf: array[256, int32]
var txBuf: array[256, int32]
discard sai.startDma(rxBuf[0].addr, txBuf[0].addr, 256, audioCallback)

Source   Edit  
proc stopDma(sai: var SaiHandle): SaiResult {.inline, ...raises: [], tags: [],
    forbids: [].}

Stop the DMA audio transfer.

Returns: SAI_OK on success, SAI_ERR on failure

Source   Edit