Atmosic PWM FIFO Mode

Overview

The Atmosic PWM FIFO mode provides advanced waveform generation capabilities beyond standard PWM functionality. It enables complex pattern generation with dual carrier frequencies, automatic sequencing, and interrupt-driven operation.

This feature is particularly useful for:

  • Ringtone generation with multiple tones

  • IR signal transmission with complex patterns

  • Audio signal generation with varying frequencies

  • Any application requiring precise timing of multiple waveform segments

Key Features

  • Dual Carrier Support: Two independent carrier frequencies can be configured

  • 16-Entry FIFO: Hardware command queue for autonomous pattern execution

  • Interrupt-Driven: Alert and completion callbacks for efficient CPU usage

  • DMA Support: Bulk command loading for high-performance applications

  • Flexible Patterns: Each command can specify 1-16,384 carrier cycles

  • Hardware Acceleration: Offloads pattern generation from CPU

Architecture

The PWM FIFO mode extends the standard Zephyr PWM API with Atmosic-specific functionality. It uses the upper 8 bits of pwm_flags_t for mode control while maintaining compatibility with existing PWM applications.

Configuration

The PWM driver and FIFO mode support are enabled by default. The following configurations are automatically set and do not need to be explicitly mentioned when building:

CONFIG_PWM_ATM=y
CONFIG_PWM_ATM_FIFO=y

Device Tree

The PWM FIFO mode uses the same device tree configuration as standard PWM:

&pwm0 {
    status = "okay";
    pinctrl-0 = <&pwm0_default>;
    pinctrl-names = "default";
};

/ {
    aliases {
        pwm-0 = &pwm0;
    };
};

Usage Examples

Basic Ringtone Generation

#include <zephyr/drivers/pwm_atm_fifo.h>

static void fifo_done_callback(const struct device *dev, uint32_t channel, int error)
{
    if (error < 0) {
        printk("Ringtone error: %d\n", error);
    } else {
        printk("Ringtone completed\n");
    }
}

int generate_ringtone(void)
{
    const struct device *pwm_dev = DEVICE_DT_GET(DT_ALIAS(pwm_0));
    int ret;

    /* Configure carriers */
    static const struct pwm_atm_carrier_config carrier1 = {
        .freq_hz = 1000,  /* 1 kHz */
        .duty_cycle = 50,
    };

    static const struct pwm_atm_carrier_config carrier2 = {
        .freq_hz = 2000,  /* 2 kHz */
        .duty_cycle = 50,
    };

    /* FIFO configuration */
    static const struct pwm_atm_fifo_config config = {
        .carrier1 = &carrier1,
        .carrier2 = &carrier2,
        .polarity = 0,
        .fifo_alert_threshold = 8,
        .fifo_done_callback = fifo_done_callback,
    };

    /* Initialize FIFO mode with error handling */
    ret = pwm_atm_fifo_init(pwm_dev, 0, &config);
    if (ret < 0) {
        if (ret == -EINVAL) {
            printk("Invalid FIFO configuration\n");
        } else if (ret == -EBUSY) {
            printk("FIFO mode already in use\n");
        }
        return ret;
    }

    /* Create ringtone pattern */
    struct pwm_atm_fifo_cmd pattern[] = {
        {.carrier = 0, .carrier_on = true, .carrier_count = 100},
        {.carrier = 0, .carrier_on = false, .carrier_count = 50},
        {.carrier = 1, .carrier_on = true, .carrier_count = 100},
        {.carrier = 1, .carrier_on = false, .carrier_count = 50},
    };

    /* Write commands with validation */
    ret = pwm_atm_fifo_write_cmds(pwm_dev, 0, pattern, ARRAY_SIZE(pattern));
    if (ret < 0) {
        if (ret == -ENODEV) {
            printk("Carrier not configured for commands\n");
        }
        goto cleanup;
    }

    /* Start execution */
    ret = pwm_atm_fifo_start(pwm_dev, 0);
    if (ret < 0) {
        goto cleanup;
    }

    /* Wait for completion... */

cleanup:
    /* Important: Clean up FIFO mode */
    pwm_atm_fifo_deinit(pwm_dev, 0);
    return ret;
}

DMA Mode Operation

For high-performance applications, use DMA mode with pre-formatted commands:

void generate_pattern_dma(void)
{
    const struct device *pwm_dev = DEVICE_DT_GET(DT_ALIAS(pwm_0));

    /* Pre-formatted DMA commands */
    static const uint16_t dma_cmds[] = {
        /* Format: bit[15]=carrier, bit[14]=on/off, bit[13:0]=count-1 */
        (0 << 15) | (1 << 14) | 99,  /* Carrier1 ON, 100 cycles */
        (1 << 15) | (0 << 14) | 49,  /* Carrier2 OFF, 50 cycles */
    };

    pwm_atm_fifo_run_dma(pwm_dev, 0, dma_cmds, ARRAY_SIZE(dma_cmds));
}

Limitations

  • Only one PWM channel can use FIFO mode at a time

  • FIFO depth is limited to 16 commands

  • Maximum carrier count per command is 16,384 cycles

  • At least one carrier must be configured during initialization

  • Requires Atmosic hardware with PWM FIFO support

Performance Considerations

  • Use FIFO alert callbacks to refill the queue for continuous operation

  • DMA mode provides the highest performance for bulk command loading

  • Consider carrier frequency limits based on system clock (16 MHz)

  • FIFO mode automatically manages power constraints during operation

Troubleshooting

Common Issues

FIFO Overflow: Occurs when commands are written faster than they can be executed. Use pwm_atm_fifo_get_free_slots() to check available space.

Callback Not Called: Ensure interrupts are enabled and the callback functions are properly registered in the configuration structure.

Invalid Frequency: Carrier frequencies must be within the supported range (123 Hz to 8 MHz). Check system clock configuration.

Channel Busy: Only one channel can use FIFO mode at a time. Call pwm_atm_fifo_deinit() on the current channel before initializing another.

Carrier Not Configured: Commands fail with -ENODEV when trying to use a carrier that was not configured during initialization. Ensure both carriers are configured if your commands use both carrier=0 and carrier=1.

Both Carriers NULL: Initialization fails with -EINVAL if both carrier1 and carrier2 are NULL. At least one carrier must be configured.

Sequential Device Issues: If a second device fails to initialize after the first device completed, ensure pwm_atm_fifo_deinit() was called on the first device.

Debug Tips

  • Enable PWM debug logging: CONFIG_PWM_LOG_LEVEL_DBG=y

  • Use pwm_atm_fifo_is_empty() and pwm_atm_fifo_is_full() to monitor FIFO state

  • Verify device tree PWM configuration and pin assignments

  • Check that the PWM device is ready before calling FIFO functions

  • Always call pwm_atm_fifo_deinit() for proper cleanup and sequential device usage

  • Check return values from all FIFO functions for proper error handling

  • Verify carrier configuration matches command usage (carrier=0 needs carrier1, carrier=1 needs carrier2)

  • Use proper error code handling (-EINVAL, -EBUSY, -ENODEV, and -EOVERFLOW) for robust applications