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
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 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 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 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