Microchip® Advanced Software Framework

Quick Start Guide for I2S - DMA

In this use case, the I2S will be used to generate Master Clock (MCK), Serial Clock (SCK), Frame Sync (FS), and Serial Data (SD) signals.

Here MCK is set to the half of processor clock. SCK is set to a quarter of the frequency of processor. FS generates half-half square wave for left and right audio channel data. The output serial data of channels toggle from two values to generate square wave, if codec or DAC is connected.

The output SD is also fed back to another I2S channel by internal loop back, and transfer to values buffer by DMA.

The I2S module will be setup as follows:

  • GCLK generator 0 (GCLK main) clock source
  • MCK, SCK, and FS clocks outputs are enabled
  • MCK output divider set to 2
  • SCK generation divider set to 4
  • Each frame will contain two 32-bit slots
  • Data will be left adjusted and start transmit without delay

Quick Start

Prerequisites

There are no prerequisites for this use case.

Code

Add to the main application source file, before any functions:

#define CONF_I2S_MODULE I2S
#define CONF_I2S_MCK_PIN PIN_PA09G_I2S_MCK0
#define CONF_I2S_MCK_MUX MUX_PA09G_I2S_MCK0
#define CONF_I2S_SCK_PIN PIN_PA10G_I2S_SCK0
#define CONF_I2S_SCK_MUX MUX_PA10G_I2S_SCK0
#define CONF_I2S_FS_PIN PIN_PA11G_I2S_FS0
#define CONF_I2S_FS_MUX MUX_PA11G_I2S_FS0
#define CONF_I2S_SD_PIN PIN_PA07G_I2S_SD0
#define CONF_I2S_SD_MUX MUX_PA07G_I2S_SD0
#define CONF_RX_TRIGGER 0x2A
#define CONF_TX_TRIGGER 0x2B

Add to the main application source file, outside of any functions:

struct i2s_module i2s_instance;
uint16_t rx_values[4] = {0xEEEE, 0xEEEE, 0xEEEE, 0xEEEE};
struct dma_resource rx_dma_resource;
COMPILER_ALIGNED(16) DmacDescriptor rx_dma_descriptor;
uint16_t tx_values[4] = {0xF87F, 0x901F, 0, 0};
struct dma_resource tx_dma_resource;
COMPILER_ALIGNED(16) DmacDescriptor tx_dma_descriptor;

Copy-paste the following setup code to your user application:

static void _config_dma_for_rx(void)
{
struct dma_resource_config config;
config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
config.peripheral_trigger = CONF_RX_TRIGGER;
dma_allocate(&rx_dma_resource, &config);
struct dma_descriptor_config descriptor_config;
descriptor_config.block_transfer_count = 4;
descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
descriptor_config.step_selection = DMA_STEPSEL_SRC;
descriptor_config.src_increment_enable = false;
descriptor_config.destination_address =
(uint32_t)rx_values + sizeof(rx_values);
descriptor_config.source_address = (uint32_t)&CONF_I2S_MODULE->DATA[1];
dma_descriptor_create(&rx_dma_descriptor, &descriptor_config);
rx_dma_descriptor.DESCADDR.reg = (uint32_t)&rx_dma_descriptor;
dma_add_descriptor(&rx_dma_resource, &rx_dma_descriptor);
dma_start_transfer_job(&rx_dma_resource);
}
static void _config_dma_for_tx(void)
{
struct dma_resource_config config;
config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
config.peripheral_trigger = CONF_TX_TRIGGER;
dma_allocate(&tx_dma_resource, &config);
struct dma_descriptor_config descriptor_config;
descriptor_config.block_transfer_count = 4;
descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
descriptor_config.dst_increment_enable = false;
descriptor_config.source_address =
(uint32_t)tx_values + sizeof(tx_values);
descriptor_config.destination_address = (uint32_t)&CONF_I2S_MODULE->DATA[0];
dma_descriptor_create(&tx_dma_descriptor, &descriptor_config);
tx_dma_descriptor.DESCADDR.reg = (uint32_t)&tx_dma_descriptor;
dma_add_descriptor(&tx_dma_resource, &tx_dma_descriptor);
dma_start_transfer_job(&tx_dma_resource);
}
static void _configure_i2s(void)
{
i2s_init(&i2s_instance, CONF_I2S_MODULE);
struct i2s_clock_unit_config config_clock_unit;
config_clock_unit.clock.gclk_src = GCLK_GENERATOR_0;
config_clock_unit.clock.mck_src = I2S_MASTER_CLOCK_SOURCE_GCLK;
config_clock_unit.clock.mck_out_enable = true;
config_clock_unit.clock.mck_out_div = 2;
config_clock_unit.clock.sck_src = I2S_SERIAL_CLOCK_SOURCE_MCKDIV;
config_clock_unit.clock.sck_div = 4;
config_clock_unit.frame.number_slots = 2;
config_clock_unit.frame.slot_size = I2S_SLOT_SIZE_32_BIT;
config_clock_unit.frame.data_delay = I2S_DATA_DELAY_0;
config_clock_unit.frame.frame_sync.source = I2S_FRAME_SYNC_SOURCE_SCKDIV;
config_clock_unit.frame.frame_sync.width = I2S_FRAME_SYNC_WIDTH_HALF_FRAME;
config_clock_unit.mck_pin.enable = true;
config_clock_unit.mck_pin.gpio = CONF_I2S_MCK_PIN;
config_clock_unit.mck_pin.mux = CONF_I2S_MCK_MUX;
config_clock_unit.sck_pin.enable = true;
config_clock_unit.sck_pin.gpio = CONF_I2S_SCK_PIN;
config_clock_unit.sck_pin.mux = CONF_I2S_SCK_MUX;
config_clock_unit.fs_pin.enable = true;
config_clock_unit.fs_pin.gpio = CONF_I2S_FS_PIN;
config_clock_unit.fs_pin.mux = CONF_I2S_FS_MUX;
&config_clock_unit);
struct i2s_serializer_config config_serializer;
config_serializer.clock_unit = I2S_CLOCK_UNIT_0;
config_serializer.mode = I2S_SERIALIZER_TRANSMIT;
config_serializer.data_size = I2S_DATA_SIZE_16BIT;
config_serializer.data_pin.enable = true;
config_serializer.data_pin.gpio = CONF_I2S_SD_PIN;
config_serializer.data_pin.mux = CONF_I2S_SD_MUX;
&config_serializer);
config_serializer.loop_back = true;
config_serializer.mode = I2S_SERIALIZER_RECEIVE;
config_serializer.data_size = I2S_DATA_SIZE_16BIT;
config_serializer.data_pin.enable = false;
&config_serializer);
i2s_enable(&i2s_instance);
}

Add to user application initialization (typically the start of main()):

_config_dma_for_rx();
_config_dma_for_tx();
_configure_i2s();

Workflow

Configure the DMAC module to obtain received value from I2S Serializer 1.

  1. Allocate and configure the DMA resource
    1. Create a DMA resource instance.
      struct dma_resource rx_dma_resource;
      Note
      This should never go out of scope as long as the resource is in use. In most cases, this should be global.
    2. Create a DMA resource configuration struct.
      struct dma_resource_config config;
    3. Initialize the DMA resource configuration struct with default values.
      Note
      This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
    4. Adjust the DMA resource configurations.
      config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
      config.peripheral_trigger = CONF_RX_TRIGGER;
    5. Allocate a DMA resource with the configurations.
      dma_allocate(&rx_dma_resource, &config);
  2. Prepare DMA transfer descriptor
    1. Create a DMA transfer descriptor.
      COMPILER_ALIGNED(16) DmacDescriptor rx_dma_descriptor;
      Note
      When multiple descriptors are linked. The linked item should never go out of scope before it's loaded (to DMA Write-Back memory section). In most cases, if more than one descriptors are used, they should be global except the very first one.
    2. Create a DMA transfer descriptor configuration struct, which can be filled out to adjust the configuration of a single DMA transfer.
      struct dma_descriptor_config descriptor_config;
    3. Initialize the DMA transfer descriptor configuration struct with default values.
      Note
      This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
    4. Adjust the DMA transfer descriptor configurations.
      descriptor_config.block_transfer_count = 4;
      descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
      descriptor_config.step_selection = DMA_STEPSEL_SRC;
      descriptor_config.src_increment_enable = false;
      descriptor_config.destination_address =
      (uint32_t)rx_values + sizeof(rx_values);
      descriptor_config.source_address = (uint32_t)&CONF_I2S_MODULE->DATA[1];
    5. Create the DMA transfer descriptor with configuration.
      dma_descriptor_create(&rx_dma_descriptor, &descriptor_config);
    6. Adjust the DMA transfer descriptor if multiple DMA transfer will be performed.
      rx_dma_descriptor.DESCADDR.reg = (uint32_t)&rx_dma_descriptor;
  3. Start DMA transfer job with prepared descriptor
    1. Add the DMA transfer descriptor to the allocated DMA resource.
      dma_add_descriptor(&rx_dma_resource, &rx_dma_descriptor);
    2. Start the DMA transfer job with the allocated DMA resource and transfer descriptor.
      dma_start_transfer_job(&rx_dma_resource);

Configure the DMAC module to transmit data through I2S serializer 0.

The flow is similar to last DMA configure step for receive.

  1. Allocate and configure the DMA resource
    struct dma_resource tx_dma_resource;
    struct dma_resource_config config;
    config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
    config.peripheral_trigger = CONF_TX_TRIGGER;
    dma_allocate(&tx_dma_resource, &config);
  2. Prepare DMA transfer descriptor
    COMPILER_ALIGNED(16) DmacDescriptor tx_dma_descriptor;
    struct dma_descriptor_config descriptor_config;
    descriptor_config.block_transfer_count = 4;
    descriptor_config.beat_size = DMA_BEAT_SIZE_HWORD;
    descriptor_config.dst_increment_enable = false;
    descriptor_config.source_address =
    (uint32_t)tx_values + sizeof(tx_values);
    descriptor_config.destination_address = (uint32_t)&CONF_I2S_MODULE->DATA[0];
    dma_descriptor_create(&tx_dma_descriptor, &descriptor_config);
    tx_dma_descriptor.DESCADDR.reg = (uint32_t)&tx_dma_descriptor;
  3. Start DMA transfer job with prepared descriptor
    dma_add_descriptor(&tx_dma_resource, &tx_dma_descriptor);
    dma_start_transfer_job(&tx_dma_resource);

Configure the I2S.

  1. Create I2S module software instance structure for the I2S module to store the I2S driver state while it is in use.
    struct i2s_module i2s_instance;
    Note
    This should never go out of scope as long as the module is in use. In most cases, this should be global.
  2. Configure the I2S module.
    1. Initialize the I2S module.
      i2s_init(&i2s_instance, CONF_I2S_MODULE);
    2. Initialize the I2S Clock Unit.
      1. Create a I2S module configuration struct, which can be filled out to adjust the configuration of a physical I2S Clock Unit.
        struct i2s_clock_unit_config config_clock_unit;
      2. Initialize the I2S Clock Unit configuration struct with the module's default values.
        Note
        This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
      3. Alter the I2S Clock Unit settings to configure the general clock source, MCK, SCK, and FS generation.
        config_clock_unit.clock.gclk_src = GCLK_GENERATOR_0;
        config_clock_unit.clock.mck_src = I2S_MASTER_CLOCK_SOURCE_GCLK;
        config_clock_unit.clock.mck_out_enable = true;
        config_clock_unit.clock.mck_out_div = 2;
        config_clock_unit.clock.sck_src = I2S_SERIAL_CLOCK_SOURCE_MCKDIV;
        config_clock_unit.clock.sck_div = 4;
        config_clock_unit.frame.number_slots = 2;
        config_clock_unit.frame.slot_size = I2S_SLOT_SIZE_32_BIT;
        config_clock_unit.frame.data_delay = I2S_DATA_DELAY_0;
        config_clock_unit.frame.frame_sync.source = I2S_FRAME_SYNC_SOURCE_SCKDIV;
        config_clock_unit.frame.frame_sync.width = I2S_FRAME_SYNC_WIDTH_HALF_FRAME;
      4. Alter the I2S Clock Unit settings to configure the MCK, SCK, and FS output on physical device pins.
        config_clock_unit.mck_pin.enable = true;
        config_clock_unit.mck_pin.gpio = CONF_I2S_MCK_PIN;
        config_clock_unit.mck_pin.mux = CONF_I2S_MCK_MUX;
        config_clock_unit.sck_pin.enable = true;
        config_clock_unit.sck_pin.gpio = CONF_I2S_SCK_PIN;
        config_clock_unit.sck_pin.mux = CONF_I2S_SCK_MUX;
        config_clock_unit.fs_pin.enable = true;
        config_clock_unit.fs_pin.gpio = CONF_I2S_FS_PIN;
        config_clock_unit.fs_pin.mux = CONF_I2S_FS_MUX;
      5. Configure the I2S Clock Unit with the desired settings.
        &config_clock_unit);
    3. Initialize the I2S Serializers.
      1. Create a I2S Serializer configuration struct, which can be filled out to adjust the configuration of a physical I2S Serializer.
        struct i2s_serializer_config config_serializer;
      2. Initialize the I2S Serializer configuration struct with the module's default values.
        Note
        This should always be performed before using the configuration struct to ensure that all values are initialized to known default settings.
      3. Alter the I2S Serializer settings to configure the SD transmit generation.
        config_serializer.clock_unit = I2S_CLOCK_UNIT_0;
        config_serializer.mode = I2S_SERIALIZER_TRANSMIT;
        config_serializer.data_size = I2S_DATA_SIZE_16BIT;
      4. Alter the I2S Serializer settings to configure the SD transmit on a physical device pin.
        config_serializer.data_pin.enable = true;
        config_serializer.data_pin.gpio = CONF_I2S_SD_PIN;
        config_serializer.data_pin.mux = CONF_I2S_SD_MUX;
      5. Configure the I2S Serializer 0 with the desired transmit settings.
        &config_serializer);
      6. Alter the I2S Serializer settings to configure the SD receive.
        config_serializer.loop_back = true;
        config_serializer.mode = I2S_SERIALIZER_RECEIVE;
        config_serializer.data_size = I2S_DATA_SIZE_16BIT;
      7. Alter the I2S Serializer settings to configure the SD receive on a physical device pin (here it's disabled since we use internal loopback).
        config_serializer.data_pin.enable = false;
      8. Configure the I2S Serializer 1 with the desired transmit settings.
        &config_serializer);
    4. Enable the I2S module, the Clock Unit and Serializer to start the clocks and ready to transmit data.

Use Case

Code

Copy-paste the following code to your user application:

while (true) {
/* Infinite loop */
}

Workflow

  1. Enter an infinite loop while the signals are generated via the I2S module.
    while (true) {
    /* Infinite loop */
    }