This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM623: ecap userspace application code

Part Number: AM623


Tool/software:

Hi, TI expert!

I have a development environment for am6234, with our own evaluation board.

I have two questions that need help to answer:

1.What is the frequency range of the input signal that ecap supports capturing?

2.Is there a Linux user space testing program for ecap?

Using the following thread method, sometimes the period and duty cycle captured by ecap are inaccurate when the input signal frequency is high,

https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1242640/am625-ecap/4777458?tisearch=e2e-sitesearch&keymatch=am62%2525252520ecap#4777458

Regards,
Li

  • Hello Li,

    The test code and documentation that I am aware of are in your previous thread:
    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1242640/am625-ecap/4766153#4766153 

    1) What do you mean by "inaccurate"?

    Do you mean that the ecap timestamp itself is inaccurate? Or do you mean that when the ecap is capturing timestamps very quickly, Linux is unable to respond fast enough to read all of the timestamps before they get overwritten with new data, as discussed at the end of your previous thread? https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1242640/am625-ecap/4800670#4800670 

    The ECAP hardware has limits on the timing of the input signals, which are defined in the AM62x datasheet. However, in this case, I would expect that the Linux scheduler's ability to read the timestamps before they are overwritten is the limiting factor here.

    Please provide additional information about your usecase

    What are you measuring?

    What is the worst-case (i.e., fastest) pulses that you will be capturing?

    What is the AM62x doing with that information?

    Regards,

    Nick

  • Hello Nick,

    Thank you for your follow-up. 

    I used the test code in previous thread and added some other features.

    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1242640/am625-ecap/4766153#4766153

    Using ecap0 as PWM output and ecap1 as capture input, the pins of ecap0 and ecap1 are connected together through jumper caps for testing.

    The corresponding device tree configuration is as follows:

    // SPDX-License-Identifier: GPL-2.0
    /**
     * DT Overlay for enabling ECAP pwm mode on AM625-SK board.
     *
     * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/
     */
    
    #include <dt-bindings/pinctrl/k3.h>
    
    &main_pmx0 {
    	pruss_ecap0_pwm_pins_default: pruss-ecap0-pwm-pins-default {
    		pinctrl-single,pins = <
    			AM62X_IOPAD(0x00a4, PIN_INPUT, 1) /* (M22) GPMC0_DIR.PR0_ECAP0_IN_APWM_OUT */
    			AM62X_IOPAD(0x0180, PIN_INPUT, 5) /* (AD23) RGMII2_RXC.PR0_ECAP0_SYNC_IN */
    			AM62X_IOPAD(0x0178, PIN_OUTPUT, 5) /* (AC20) RGMII2_TD3.PR0_ECAP0_SYNC_OUT */
    		>;
    	};
    	main_ecap0_pwm_pins_default: main-ecap0-pwm-pins-default {
    		pinctrl-single,pins = <
    			AM62X_IOPAD(0x01b8, PIN_OUTPUT, 3) /* (C13) SPI0_CS1.ECAP0_IN_APWM_OUT */
    		>;
    	};
    	main_ecap1_pwm_pins_default: main-ecap1-pwm-pins-default {
    		pinctrl-single,pins = <
    			AM62X_IOPAD(0x019c, PIN_OUTPUT, 2) /* (B18) MCASP0_AXR1.ECAP1_IN_APWM_OUT */
    		>;
    	};
    	main_ecap2_pwm_pins_default: main-ecap2-pwm-pins-default {
    		pinctrl-single,pins = <
    			AM62X_IOPAD(0x01a4, PIN_OUTPUT, 2) /* (B20) MCASP0_ACLKX.ECAP2_IN_APWM_OUT */
    		>;
    	};
    };
    
    &ecap0_pwm {
    	pinctrl-names = "default";
    	pinctrl-0 = <&main_ecap0_pwm_pins_default>;
    };
    
    &ecap1_pwm {
    	pinctrl-names = "default";
    	pinctrl-0 = <&main_ecap1_pwm_pins_default>;
    };
    
    &ecap2_pwm {
    	pinctrl-names = "default";
    	pinctrl-0 = <&main_ecap2_pwm_pins_default>;
    };
    
    
    &ecap0_pwm {
    	status = "okay";
    };
    
    &ecap1_pwm {
    	status = "disabled";
    };
    
    &ecap2_pwm {
    	status = "disabled";
    };
    
    
    
    
    // SPDX-License-Identifier: GPL-2.0
    /**
     * DT Overlay for enabling ECAP capture mode on AM625-SK board.
     *
     * Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/
     */
    
    #include <dt-bindings/pinctrl/k3.h>
    
    &main_pmx0 {
    	main_ecap0_capture_pins_default: main-ecap0-capture-pins-default {
    		pinctrl-single,pins = <
    			AM62X_IOPAD(0x01b8, PIN_INPUT, 3) /* (C13) SPI0_CS1.ECAP0_IN_APWM_OUT */
    		>;
    	};
    	main_ecap1_capture_pins_default: main-ecap1-capture-pins-default {
    		pinctrl-single,pins = <
    			AM62X_IOPAD(0x019c, PIN_INPUT, 2) /* (B18) MCASP0_AXR1.ECAP1_IN_APWM_OUT */
    		>;
    	};
    	main_ecap2_capture_pins_default: main-ecap2-capture-pins-default {
    		pinctrl-single,pins = <
    			AM62X_IOPAD(0x01a4, PIN_INPUT, 2) /* (B20) MCASP0_ACLKX.ECAP2_IN_APWM_OUT */
    		>;
    	};
    };
    
    &ecap0_capture {
    	pinctrl-names = "default";
    	pinctrl-0 = <&main_ecap0_capture_pins_default>;
    };
    
    &ecap1_capture {
    	pinctrl-names = "default";
    	pinctrl-0 = <&main_ecap1_capture_pins_default>;
    };
    
    &ecap2_capture {
    	pinctrl-names = "default";
    	pinctrl-0 = <&main_ecap2_capture_pins_default>;
    };
    
    &ecap0_capture {
    	status = "disabled";
    };
    
    &ecap1_capture {
    	status = "okay";
    };
    
    &ecap2_capture {
    	status = "disabled";
    };
    
    
    
    

    The test code is as follows:

    /**
     * counter_example.c - ECAP PWM Test Tool
     *
     * Function: Test ECAP PWM output and capture functionality on AM62XX platform
     *   - Configure PWM output frequency and duty cycle
     *   - Measure actual output parameters using ECAP capture
     *   - Compare set values with measured values to determine test results
     *
     * Version: 1.0
     * Author: liweiyu@zlg.cn
     * Date: 2025/03/15
     *
     */
    
    #include <errno.h>
    #include <fcntl.h>
    #include <linux/counter.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <math.h>
    #include <getopt.h>
    #include <sys/stat.h>
    
    /*
     * Constant definitions
     */
    #define NUM_CAPTURES            4
    #define DEFAULT_TEST_TIME_MS    1000
    #define PWM_STABILIZE_TIME_US   100000
    #define DEFAULT_FREQUENCY_HZ    125000000
    #define ERROR_THRESHOLD_PERCENT 5.0
    #define MAX_PATH_LENGTH         256
    #define BUFFER_SIZE_SMALL       20
    #define BUFFER_SIZE_MEDIUM      64
    #define BUFFER_SIZE_LARGE       256
    
    /* Color code constants */
    #define COLOR_RED     "\033[0;31m"
    #define COLOR_GREEN   "\033[0;32m"
    #define COLOR_YELLOW  "\033[1;33m"
    #define COLOR_BLUE    "\033[0;34m"
    #define COLOR_RESET   "\033[0m"
    
    /* Path constants */
    #define PWM_DEVICE_PATH         "/sys/class/pwm/pwmchip0"
    #define PWM_CHANNEL_PATH        "/sys/class/pwm/pwmchip0/pwm0"
    #define COUNTER_DEVICE_PATH     "/sys/bus/counter/devices/counter0"
    #define COUNTER_DEV_NODE        "/dev/counter0"
    #define PWM_DEBUG_PATH          "/sys/kernel/debug/pwm"
    
    /**
     * Capture component definition macro - Create counter_watch structure
     *
     * @param _id: Capture component ID
     * @return: Initialized counter_watch structure
     */
    #define CAPTURE_WATCH(_id) \
    { \
        .component.type = COUNTER_COMPONENT_EXTENSION, \
        .component.scope = COUNTER_SCOPE_COUNT, \
        .component.parent = 0, \
        .component.id = _id, \
        .event = COUNTER_EVENT_CAPTURE, \
        .channel = _id, \
    }
    
    /**
     * Log level enumeration
     */
    typedef enum {
        LOG_LEVEL_DEBUG = 0,  /* Debug information */
        LOG_LEVEL_INFO  = 1,  /* General information */
        LOG_LEVEL_WARN  = 2,  /* Warning information */
        LOG_LEVEL_ERROR = 3   /* Error information */
    } LogLevel;
    
    /* Global variables */
    static LogLevel g_logLevel = LOG_LEVEL_INFO;
    static struct counter_watch g_watches[NUM_CAPTURES] = {
        CAPTURE_WATCH(0),
        CAPTURE_WATCH(1),
        CAPTURE_WATCH(2),
        CAPTURE_WATCH(3),
    };
    
    /**
     * Log output macro definition
     */
    #define LOG_DEBUG(fmt, ...) \
        do { \
            if (g_logLevel <= LOG_LEVEL_DEBUG) \
                printf("%s[DEBUG]%s " fmt "\n", COLOR_BLUE, COLOR_RESET, ##__VA_ARGS__); \
        } while(0)
    
    #define LOG_INFO(fmt, ...) \
        do { \
            if (g_logLevel <= LOG_LEVEL_INFO) \
                printf("%s[INFO]%s " fmt "\n", COLOR_GREEN, COLOR_RESET, ##__VA_ARGS__); \
        } while(0)
    
    #define LOG_WARN(fmt, ...) \
        do { \
            if (g_logLevel <= LOG_LEVEL_WARN) \
                printf("%s[WARN]%s " fmt "\n", COLOR_YELLOW, COLOR_RESET, ##__VA_ARGS__); \
        } while(0)
    
    #define LOG_ERROR(fmt, ...) \
        do { \
            if (g_logLevel <= LOG_LEVEL_ERROR) \
                printf("%s[ERROR]%s " fmt "\n", COLOR_RED, COLOR_RESET, ##__VA_ARGS__); \
        } while(0)
    
    /**
     * Set log level
     *
     * @param level: Log level to set
     */
    void setLogLevel(LogLevel level)
    {
        g_logLevel = level;
    }
    
    /**
     * Get counter frequency
     *
     * @return: Counter frequency (Hz), returns default value on error
     */
    unsigned long long getCounterFrequency(void)
    {
        FILE *fp;
        unsigned long long frequency = 0;
        char buffer[BUFFER_SIZE_MEDIUM];
        char path[MAX_PATH_LENGTH];
    
        snprintf(path, sizeof(path), "%s/signal0/frequency", COUNTER_DEVICE_PATH);
        fp = fopen(path, "r");
        if (fp == NULL) {
            LOG_ERROR("Cannot open frequency file: %s", strerror(errno));
            return DEFAULT_FREQUENCY_HZ;
        }
    
        if (fgets(buffer, sizeof(buffer), fp) != NULL) {
            frequency = strtoull(buffer, NULL, 10);
        } else {
            LOG_ERROR("Cannot read frequency: %s", strerror(errno));
            frequency = DEFAULT_FREQUENCY_HZ;
        }
    
        fclose(fp);
        return frequency;
    }
    
    /**
     * Format frequency value as a unit string
     *
     * @param frequency: Frequency value (Hz)
     * @param buffer: Output buffer
     * @param bufferSize: Buffer size
     */
    void formatFrequencyString(double frequency, char *buffer, size_t bufferSize)
    {
        if (frequency >= 1000000) {
            snprintf(buffer, bufferSize, "%.2f MHz", frequency/1000000);
        } else if (frequency >= 1000) {
            snprintf(buffer, bufferSize, "%.2f KHz", frequency/1000);
        } else {
            snprintf(buffer, bufferSize, "%.2f Hz", frequency);
        }
    }
    
    /**
     * Format duty cycle as percentage string
     *
     * @param duty: Duty cycle value (0-100)
     * @param buffer: Output buffer
     * @param bufferSize: Buffer size
     */
    void formatDutyString(double duty, char *buffer, size_t bufferSize)
    {
        snprintf(buffer, bufferSize, "%.2f%%", duty);
    }
    
    /**
     * Configure PWM output
     *
     * @param freqHz: Frequency (Hz)
     * @param dutyPercent: Duty cycle (%)
     * @return: Returns 0 on success, negative value on failure
     */
    int configurePwm(double freqHz, double dutyPercent)
    {
        FILE *fp;
        unsigned long long periodNs, dutyNs;
        char freqDisplay[BUFFER_SIZE_SMALL];
    
        /* Prepare log display */
        formatFrequencyString(freqHz, freqDisplay, sizeof(freqDisplay));
        LOG_INFO("Configuring PWM output: Frequency=%s, Duty Cycle=%.2f%%", freqDisplay, dutyPercent);
        
        /* Check if PWM device exists */
        struct stat st;
        if (stat(PWM_DEVICE_PATH, &st) != 0) {
            LOG_ERROR("PWM device does not exist: %s", strerror(errno));
            return -1;
        }
    
        /* Export PWM0 channel if it doesn't exist */
        if (stat(PWM_CHANNEL_PATH, &st) != 0) {
            fp = fopen(PWM_DEVICE_PATH "/export", "w");
            if (fp == NULL) {
                LOG_ERROR("Cannot export PWM0: %s", strerror(errno));
                return -1;
            }
            fprintf(fp, "0");
            fclose(fp);
            /* Wait for device node creation */
            usleep(100000);
        }
    
        /* Disable PWM */
        fp = fopen(PWM_CHANNEL_PATH "/enable", "w");
        if (fp == NULL) {
            LOG_ERROR("Cannot disable PWM: %s", strerror(errno));
            return -1;
        }
        fprintf(fp, "0");
        fclose(fp);
    
        /* Calculate period and duty cycle in nanoseconds */
        periodNs = (unsigned long long)(1000000000.0 / freqHz);
        dutyNs = (unsigned long long)(periodNs * dutyPercent / 100.0);
    
        /* Set period */
        fp = fopen(PWM_CHANNEL_PATH "/period", "w");
        if (fp == NULL) {
            LOG_ERROR("Cannot set PWM period: %s", strerror(errno));
            return -1;
        }
        fprintf(fp, "%llu", periodNs);
        fclose(fp);
    
        /* Set duty cycle */
        fp = fopen(PWM_CHANNEL_PATH "/duty_cycle", "w");
        if (fp == NULL) {
            LOG_ERROR("Cannot set PWM duty cycle: %s", strerror(errno));
            return -1;
        }
        fprintf(fp, "%llu", dutyNs);
        fclose(fp);
        
        /* Set polarity */
        fp = fopen(PWM_CHANNEL_PATH "/polarity", "w");
        if (fp == NULL) {
            LOG_ERROR("Cannot set PWM polarity: %s", strerror(errno));
            return -1;
        }
        fprintf(fp, "normal");
        fclose(fp);
    
        /* Enable PWM */
        fp = fopen(PWM_CHANNEL_PATH "/enable", "w");
        if (fp == NULL) {
            LOG_ERROR("Cannot enable PWM: %s", strerror(errno));
            return -1;
        }
        fprintf(fp, "1");
        fclose(fp);
        
        LOG_INFO("PWM configuration complete, Period=%llu ns, Duty Cycle=%llu ns", periodNs, dutyNs);
    
        printf("\n");
    
        /* Display PWM debug information (if available) */
        fp = fopen(PWM_DEBUG_PATH, "r");
        if (fp != NULL) {
            char line[BUFFER_SIZE_LARGE];
            LOG_INFO("PWM Debug Information:");
            while (fgets(line, sizeof(line), fp) != NULL) {
                printf("%s", line);
            }
            fclose(fp);
        }
    
        printf("\n");
    
        return 0;
    }
    
    /**
     * Clean up PWM configuration
     *
     * @return: Returns 0 on success, negative value on failure
     */
    int cleanupPwm(void)
    {
        FILE *fp;
    
        LOG_DEBUG("Cleaning up PWM configuration...");
    
        /* Disable PWM */
        fp = fopen(PWM_CHANNEL_PATH "/enable", "w");
        if (fp == NULL) {
            LOG_WARN("Cannot disable PWM: %s", strerror(errno));
            return -1;
        }
        fprintf(fp, "0");
        fclose(fp);
    
        LOG_DEBUG("PWM cleanup complete");
        return 0;
    }
    
    /**
     * Set ECAP capture event polarity
     *
     * @param polarityNum: Polarity number
     * @param polarityType: Polarity type string ("positive" or "negative")
     */
    void setEcapPolarity(int polarityNum, const char *polarityType)
    {
        FILE *fp;
        char path[MAX_PATH_LENGTH];
    
        snprintf(path, sizeof(path), "%s/signal1/polarity%d", COUNTER_DEVICE_PATH, polarityNum);
        fp = fopen(path, "w");
        if (fp != NULL) {
            fprintf(fp, "%s", polarityType);
            fclose(fp);
        } else {
            LOG_WARN("Cannot set polarity %d: %s", polarityNum, strerror(errno));
        }
    }
    
    /**
     * Reset ECAP counter
     *
     * @param counterType: Counter type ("num_overflows" or "count")
     */
    void resetEcapCounter(const char *counterType)
    {
        FILE *fp;
        char path[MAX_PATH_LENGTH];
    
        snprintf(path, sizeof(path), "%s/count0/%s", COUNTER_DEVICE_PATH, counterType);
        fp = fopen(path, "w");
        if (fp != NULL) {
            fprintf(fp, "0");
            fclose(fp);
        } else {
            LOG_WARN("Cannot reset %s: %s", counterType, strerror(errno));
        }
    }
    
    /**
     * Configure ECAP capture
     *
     * @return: Returns 0 on success, negative value on failure
     */
    int configureEcap(void)
    {
        FILE *fp;
        char path[MAX_PATH_LENGTH];
    
        LOG_INFO("Configuring ECAP capture...");
    
        /* Check if ECAP device exists */
        struct stat st;
        if (stat(COUNTER_DEVICE_PATH, &st) != 0) {
            LOG_ERROR("ECAP device does not exist: %s", strerror(errno));
            return -1;
        }
        
        /* Set capture event polarity */
        setEcapPolarity(0, "positive");
        setEcapPolarity(1, "negative");
        setEcapPolarity(2, "positive");
        setEcapPolarity(3, "negative");
    
        /* Reset counter */
        resetEcapCounter("num_overflows");
        resetEcapCounter("count");
    
        /* Enable ECAP capture */
        snprintf(path, sizeof(path), "%s/count0/enable", COUNTER_DEVICE_PATH);
        fp = fopen(path, "w");
        if (fp == NULL) {
            LOG_ERROR("Cannot enable ECAP capture: %s", strerror(errno));
            return -1;
        }
        fprintf(fp, "1");
        fclose(fp);
    
        LOG_INFO("ECAP capture configuration complete");
        return 0;
    }
    
    /**
     * Print usage instructions
     *
     * @param progName: Program name
     */
    void printUsage(const char *progName)
    {
        printf("\n=========================================================================================\n");
        printf("ECAP PWM Test Tool\n\n");
        printf("Version: 1.0\n");
        printf("\n");
        printf("Function: Test ECAP PWM output and capture functionality on AM62XX platform\n");
        printf("  - Set PWM output frequency and duty cycle based on input parameters\n");
        printf("  - Measure actual output frequency and duty cycle using capture\n");
        printf("  - Compare set values with measured values to determine test results\n");
        printf("\n");
        printf("Usage:\n");
        printf("  %s -f <frequency> -d <duty%%> [-t <test_time(ms)>] [-l <log_level>] [-h]\n\n", progName);
        printf("Parameters:\n");
        printf("  -f, --frequency  Set PWM frequency, units: Hz(default), K(KHz), M(MHz)\n");
        printf("                   Example: 1000, 1K, 1M\n");
        printf("  -d, --duty       Set PWM duty cycle, range 0-100\n");
        printf("  -t, --time       Set test time, unit milliseconds, default 1000\n");
        printf("  -l, --loglevel   Set log level, range 0-3 (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR)\n");
        printf("  -h, --help       Display this help information\n");
        printf("\n");
        printf("Example: %s -f 1K -d 30     # Set 1KHz, 30%% duty cycle\n", progName);
        printf("         %s -f 1M -d 50     # Set 1MHz, 50%% duty cycle\n", progName);
        printf("=========================================================================================\n\n");
    }
    
    /**
     * Parse frequency string with units
     *
     * @param freqStr: Frequency string, e.g. "1000", "1K", "1M"
     * @return: Parsed frequency value (Hz), returns -1 on failure
     */
    double parseFrequency(const char *freqStr)
    {
        char *endptr;
        double freq = strtod(freqStr, &endptr);
    
        /* Check unit */
        if (*endptr != '\0') {
            if ((*endptr == 'k' || *endptr == 'K') && *(endptr+1) == '\0') {
                freq *= 1000.0;  /* KHz */
            } else if ((*endptr == 'm' || *endptr == 'M') && *(endptr+1) == '\0') {
                freq *= 1000000.0;  /* MHz */
            } else {
                /* Invalid unit */
                return -1.0;
            }
        }
    
        return freq;
    }
    
    /**
     * Configure capture monitor
     *
     * @param fd: Counter device file descriptor
     * @return: Returns 0 on success, negative value on failure
     */
    int setupCaptureMonitor(int fd)
    {
        int ret;
        int i;
    
        LOG_INFO("Setting up capture monitor");
        for (i = 0; i < NUM_CAPTURES; i++) {
            ret = ioctl(fd, COUNTER_ADD_WATCH_IOCTL, &g_watches[i]);
            if (ret == -1) {
                LOG_ERROR("Add monitor[%d] error: %s", i, strerror(errno));
                return -1;
            }
        }
    
        ret = ioctl(fd, COUNTER_ENABLE_EVENTS_IOCTL);
        if (ret == -1) {
            LOG_ERROR("Enable events error: %s", strerror(errno));
            return -1;
        }
    
        LOG_INFO("Capture setup complete. Waiting for events...");
        return 0;
    }
    
    /**
     * Wait for and read capture data
     *
     * @param fd: Counter device file descriptor
     * @param eventData: Capture event data output buffer
     * @param testMode: Whether in test mode
     * @param testTimeMs: Test timeout time (milliseconds)
     * @return: Returns 0 on success, negative value on failure
     */
    int waitForCaptureData(int fd, struct counter_event *eventData, int testMode, int testTimeMs)
    {
        int ret;
    
        /* Set timeout in test mode */
        if (testMode) {
            fd_set readfds;
            struct timeval tv;
    
            /* Set select timeout */
            tv.tv_sec = testTimeMs / 1000;
            tv.tv_usec = (testTimeMs % 1000) * 1000;
    
            FD_ZERO(&readfds);
            FD_SET(fd, &readfds);
    
            /* Wait for data readable or timeout */
            ret = select(fd + 1, &readfds, NULL, NULL, &tv);
            if (ret == -1) {
                LOG_ERROR("Select error: %s", strerror(errno));
                return -1;
            } else if (ret == 0) {
                LOG_ERROR("Waiting for capture data timeout");
                return -1;
            }
        }
    
        /* Read data */
        ret = read(fd, eventData, sizeof(struct counter_event) * NUM_CAPTURES);
    
        if (ret == -1) {
            LOG_ERROR("Read event data failed: %s", strerror(errno));
            return -1;
        }
    
        if (ret != sizeof(struct counter_event) * NUM_CAPTURES) {
            LOG_ERROR("Read complete event data failed");
            return -EIO;
        }
    
        return 0;
    }
    
    /**
     * Calculate measurements
     *
     * @param eventData: Capture event data
     * @param frequency: Counter frequency
     * @param period: Output parameter, calculated period value
     * @param signalFrequency: Output parameter, calculated signal frequency
     * @param duty: Output parameter, calculated duty cycle
     */
    void calculateMeasurements(const struct counter_event *eventData, unsigned long long frequency,
                              double *period, double *signalFrequency, double *duty)
    {
        /* Period = (Capture 2 - Capture 0 - 1) / Frequency */
        *period = (double)(eventData[2].value - eventData[0].value - 1) / frequency;
    
        /* Frequency = 1 / Period */
        *signalFrequency = 1.0 / *period;
    
        /* Duty Cycle = (Capture 1 - Capture 0) / (Capture 2 - Capture 0 - 1) * 100 */
        *duty = (double)(eventData[1].value - eventData[0].value) / 
               (eventData[2].value - eventData[0].value - 1) * 100;
    }
    
    /**
     * Print capture values
     *
     * @param eventData: Capture event data
     */
    void printCaptureValues(const struct counter_event *eventData)
    {
        printf("\n--------------------\n");
        printf("Capture 0: %llu\n", eventData[0].value);
        printf("Capture 1: %llu\n", eventData[1].value);
        printf("Capture 2: %llu\n", eventData[2].value);
        printf("Capture 3: %llu\n", eventData[3].value);
        printf("--------------------\n");
    }
    
    /**
     * Validate measurement results
     *
     * @param eventData: Capture event data
     * @param period: Calculated period value
     * @param duty: Calculated duty cycle
     * @return: Returns 1 if valid, 0 if invalid
     */
    int validateMeasurement(const struct counter_event *eventData, double period, double duty)
    {
        /* Check capture sequence validity */
        if (eventData[2].value <= eventData[0].value || 
            eventData[1].value <= eventData[0].value ||
            eventData[1].value >= eventData[2].value) {
            LOG_WARN("Potentially invalid capture sequence detected");
            return 0;
        } 
    
        /* Check calculation value reasonability */
        if (period <= 0 || duty < 0 || duty > 100) {
            LOG_ERROR("Invalid calculated values (Period: %.9f seconds, Duty Cycle: %.2f%%)", period, duty);
            return 0;
        }
    
        LOG_DEBUG("Valid measurement values");
        return 1;
    }
    
    /**
     * Print measurement result comparison table
     *
     * @param setFreq: Set frequency value (Hz)
     * @param measFreq: Measured frequency value (Hz)
     * @param setDuty: Set duty cycle (%)
     * @param measDuty: Measured duty cycle (%)
     */
    void printResultTable(double setFreq, double measFreq, double setDuty, double measDuty)
    {
        char setFreqDisplay[BUFFER_SIZE_SMALL], measFreqDisplay[BUFFER_SIZE_SMALL];
        char setDutyDisplay[BUFFER_SIZE_SMALL], measDutyDisplay[BUFFER_SIZE_SMALL];
        char freqErrDisplay[BUFFER_SIZE_SMALL], dutyErrDisplay[BUFFER_SIZE_SMALL];
        double freqDiffPercent, dutyDiff;
    
        /* Calculate error */
        freqDiffPercent = fabs(measFreq - setFreq) / setFreq * 100.0;
        dutyDiff = fabs(measDuty - setDuty);
    
        /* Format display strings */
        formatFrequencyString(setFreq, setFreqDisplay, sizeof(setFreqDisplay));
        formatFrequencyString(measFreq, measFreqDisplay, sizeof(measFreqDisplay));
        formatDutyString(setDuty, setDutyDisplay, sizeof(setDutyDisplay));
        formatDutyString(measDuty, measDutyDisplay, sizeof(measDutyDisplay));
        snprintf(freqErrDisplay, sizeof(freqErrDisplay), "%.2f%%", freqDiffPercent);
        snprintf(dutyErrDisplay, sizeof(dutyErrDisplay), "%.2f%%", dutyDiff);
    
        /* Print table */
        printf("\n");
        LOG_INFO("Test Result Comparison:");
    
        printf("+---------------+----------------+----------------+----------+\n");
        printf("| Parameter     | Set Value      | Measured Value | Error    |\n");
        printf("+---------------+----------------+----------------+----------+\n");
        printf("| Frequency     | %-14s | %-14s | %-8s |\n", 
               setFreqDisplay, measFreqDisplay, freqErrDisplay);
        printf("+---------------+----------------+----------------+----------+\n");
        printf("| Duty Cycle    | %-14s | %-14s | %-8s |\n", 
               setDutyDisplay, measDutyDisplay, dutyErrDisplay);
        printf("+---------------+----------------+----------------+----------+\n");
    
        /* Determine test result */
        if (freqDiffPercent < ERROR_THRESHOLD_PERCENT && dutyDiff < ERROR_THRESHOLD_PERCENT) {
            LOG_INFO("Frequency and duty cycle errors within %.1f%% range", ERROR_THRESHOLD_PERCENT);
            printf("\n\n%s==============================================================%s\n", 
                   COLOR_GREEN, COLOR_RESET);
            printf("%s                      ECAP PWM Test: PASS      %s\n", 
                   COLOR_GREEN, COLOR_RESET);
            printf("%s==============================================================%s\n\n", 
                   COLOR_GREEN, COLOR_RESET);
        } else {
            LOG_ERROR("Frequency or duty cycle error exceeds %.1f%% range", ERROR_THRESHOLD_PERCENT);
            printf("\n\n%s==============================================================%s\n", 
                   COLOR_RED, COLOR_RESET);
            printf("%s                      ECAP PWM Test: FAIL      %s\n", 
                   COLOR_RED, COLOR_RESET);
            printf("%s==============================================================%s\n\n", 
                   COLOR_RED, COLOR_RESET);
        }
    }
    
    /**
     * Process command line arguments
     *
     * @param argc: Argument count
     * @param argv: Argument array
     * @param setFreq: Output parameter, set frequency value
     * @param setDuty: Output parameter, set duty cycle
     * @param testTime: Output parameter, test time
     * @return: Returns test mode (1) or capture mode (0), negative value on error
     */
    int processCommandLineArgs(int argc, char *argv[], double *setFreq, double *setDuty, int *testTime)
    {
        int c;
        int optionIndex = 0;
        int testMode = 0;
        static struct option longOptions[] = {
            {"frequency", required_argument, 0, 'f'},
            {"duty",      required_argument, 0, 'd'},
            {"time",      required_argument, 0, 't'},
            {"loglevel",  required_argument, 0, 'l'},
            {"help",      no_argument,       0, 'h'},
            {0, 0, 0, 0}
        };
    
        /* If no parameters, enter capture mode */
        if (argc == 1) {
            return 0;
        }
    
        /* Process command line arguments */
        while ((c = getopt_long(argc, argv, "f:d:t:l:h", longOptions, &optionIndex)) != -1) {
            switch (c) {
                case 'f':
                    *setFreq = parseFrequency(optarg);
                    if (*setFreq <= 0) {
                        LOG_ERROR("Frequency must be greater than 0 and correctly formatted (e.g.: 1000, 1K, 1M)");
                        return -1;
                    }
                    testMode = 1;
                    break;
                case 'd':
                    *setDuty = atof(optarg);
                    if (*setDuty < 0 || *setDuty > 100) {
                        LOG_ERROR("Duty cycle must be between 0-100");
                        return -1;
                    }
                    testMode = 1;
                    break;
                case 't':
                    *testTime = atoi(optarg);
                    if (*testTime <= 0) {
                        LOG_ERROR("Test time must be greater than 0");
                        return -1;
                    }
                    break;
                case 'l':
                    {
                        int level = atoi(optarg);
                        if (level >= LOG_LEVEL_DEBUG && level <= LOG_LEVEL_ERROR) {
                            setLogLevel((LogLevel)level);
                        } else {
                            LOG_ERROR("Log level must be between 0-3 (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR)");
                            return -1;
                        }
                    }
                    break;
                case 'h':
                    printUsage(argv[0]);
                    return -1;
                default:
                    printUsage(argv[0]);
                    return -1;
            }
        }
    
        /* Verify necessary parameters */
        if (testMode && (*setFreq == 0.0 || *setDuty == 0.0)) {
            LOG_ERROR("Test mode requires specifying frequency and duty cycle");
            printUsage(argv[0]);
            return -1;
        }
    
        return testMode;
    }
    
    /* Main function */
    int main(int argc, char *argv[])
    {
        int fd;
        int ret;
        struct counter_event eventData[NUM_CAPTURES];
        unsigned long long frequency;
        double period, duty, signalFrequency;
    
        /* Test mode variables */
        int testMode;
        double setFreq = 0.0;
        double setDuty = 0.0;
        int testTime = DEFAULT_TEST_TIME_MS;
        char freqDisplay[BUFFER_SIZE_SMALL];
    
        /* Process command line arguments */
        testMode = processCommandLineArgs(argc, argv, &setFreq, &setDuty, &testTime);
        if (testMode < 0) {
            return 1;  /* Parameter error */
        }
    
        /* Print program header */
        printf("\n");
        LOG_INFO("Starting ECAP PWM Analyzer");
    
        /* Configure PWM output if in test mode */
        if (testMode) {
            formatFrequencyString(setFreq, freqDisplay, sizeof(freqDisplay));
            LOG_INFO("Test Mode: Frequency=%s, Duty Cycle=%.2f%%", freqDisplay, setDuty);
    
            /* Configure PWM output */
            if (configurePwm(setFreq, setDuty) != 0) {
                LOG_ERROR("PWM configuration failed");
                return 1;
            }
    
            /* Configure ECAP capture */
            if (configureEcap() != 0) {
                LOG_ERROR("ECAP capture configuration failed");
                cleanupPwm();
                return 1;
            }
    
            /* Wait for PWM output to stabilize */
            LOG_INFO("Waiting for PWM output to stabilize...");
            usleep(PWM_STABILIZE_TIME_US);
        }
    
        /* Get counter frequency */
        frequency = getCounterFrequency();
        LOG_INFO("Counter frequency: %llu Hz", frequency);
    
        /* Open counter device */
        fd = open(COUNTER_DEV_NODE, O_RDWR);
        if (fd == -1) {
            LOG_ERROR("Cannot open %s: %s", COUNTER_DEV_NODE, strerror(errno));
            if (testMode) {
                cleanupPwm();
            }
            return 1;
        }
    
        /* Set up capture monitor */
        if (setupCaptureMonitor(fd) != 0) {
            close(fd);
            if (testMode) {
                cleanupPwm();
            }
            return 1;
        }
    
        /* Wait for and read capture data */
        ret = waitForCaptureData(fd, eventData, testMode, testTime);
        if (ret != 0) {
            close(fd);
            if (testMode) {
                cleanupPwm();
            }
            return 1;
        }
    
        /* Calculate measurements */
        calculateMeasurements(eventData, frequency, &period, &signalFrequency, &duty);
    
        /* Output capture values */
        printCaptureValues(eventData);
    
        /* Validate measurement results */
        validateMeasurement(eventData, period, duty);
    
        /* Compare set values with measured values in test mode */
        if (testMode) {
            /* Print comparison table */
            printResultTable(setFreq, signalFrequency, setDuty, duty);
    
            /* Clean up PWM */
            cleanupPwm();
        }
    
        /* Close device */
        close(fd);
        return 0;
    }
    

    The executable file generated after cross compilation is as follows:

    8400.counter_example.rar

    The usage method of counter_example is as follows:

    When the frequency of the input PWM is 10Khz and the duty cycle is 30%, the frequency and duty cycle captured by ECAP are correct:

    When the frequency of the input PWM is 100Khz and the duty cycle is 30%, the frequency and duty cycle captured by ECAP are incorrect:


    Carify the observed "inaccuracy" and provide context for our use case:

    1. Clarification on "Inaccuracy"
    The observed inaccuracy refers to Linux’s inability to read ECAP timestamps fast enough before they are overwritten, especially at higher frequencies. This aligns with the discussion in the previous thread.

    Key Evidence from Tests:

    10 KHz Test (Pass):

    • Set Frequency = 10 KHz → Measured Frequency = 10 KHz (0% error).
    • ECAP captures 4 timestamps (Capture 0 to Capture 3) with stable values.
    • Linux successfully read all timestamps without overflow.

    100 KHz Test (Fail):

    • Set Frequency = 100 KHz → Measured Frequency = 33.32 KHz (66.68% error).

    • Timestamp anomalies:
    • Capture 2 (12706097) > Capture 3 (12703970), indicating data overwrite due to delayed reads.

    • Root Cause:
    • At 100 KHz, ECAP updates timestamps every 10 µs, but Linux’s interrupt latency causes missed reads, leading to incorrect period/duty cycle calculations.

    2. Use Case Details


    Measurement Target:

    • We are measuring high-frequency PWM signals in the following scenarios:
      • Motor Control: Monitoring stability of PWM outputs from brushless motor drivers.  ◦ 
      • Power Management: Validating dynamic voltage regulation in DC-DC converters.

    Worst-Case Pulse Requirements:

    • Fastest Pulses: 1 MHz PWM with ≤10 ns edge accuracy.
    • Critical Parameters: Capturing transient frequency/duty cycle changes during load fluctuations.

    AM62x Role:

    • Real-Time Monitoring: ECAP captures PWM parameters for closed-loop control (e.g., adjusting motor speed).

    • Safety Protection: Trigger system shutdown if frequency/duty cycle exceeds safe thresholds (e.g., ±5%).

    You can use my cross compiled counter_example file to test on the TI evaluation board, and it should also be able to reproduce this issue.

    The test command is as follows:

    ./counter_example -f 1 -d 30
    ./counter_example -f 10 -d 30
    ./counter_example -f 100 -d 30
    ./counter_example -f 1K -d 30
    ./counter_example -f 10K -d 30
    ./counter_example -f 100K -d 30
    ./counter_example -f 1M -d 50

    Regards,
    Li

  • Hello Li,

    Ok. How is the motor control getting adjusted?

    What can you expect from each OS?

    This is not an issue with the ECAP driver, it is just how Linux behaves as an operating system. Linux is not real-time (i.e., you cannot guarantee that Linux will respond to an external stimulus within a fixed latency) - instead of guaranteeing specific performance metrics for a couple of tasks, the Linux scheduler is designed to give the highest overall performance for all tasks.

    RT Linux allows you to prioritize some software tasks so that they meet latency requirements almost every time (keep in mind RT Linux is NOT a hard real-time system. If your system will break or if someone will be put in danger if timings are not met 0.001% of the time, then you really need to be considering a true RTOS or a pure hardware implementation, no software). The tradeoff is that as some tasks are prioritized above others, the performance for all the other code running on the A53 cores will be less than you observed on regular Linux.

    RT Linux will be able to respond regularly more quickly than regular Linux, but it will not be able to handle a 1MHz signal. That signal has a period of only 1usec.

    For more information about ensuring that computations occur within a set cycle time, refer to
    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1085663/faq-sitara-multicore-system-design-how-to-ensure-computations-occur-within-a-set-cycle-time 

    Please read the FAQ I just linked above before you continue reading. These next sentences will make more sense after reading the FAQ.

    You probably will not be able to get RT Linux to respond consistently faster than about 100usec.

    You can see the interrupt response time we measured on RT Linux SDK 10.1 here: https://software-dl.ti.com/processor-sdk-linux/esd/AM62X/10_01_10_04/exports/docs/devices/AM62X/linux/RT_Linux_Performance_Guide.html 

    And you can find more information about testing your custom design's interrupt response time here:
    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1183526/faq-linux-how-do-i-test-the-real-time-performance-of-an-am3x-am4x-am6x-soc 

    The PRU cores might be one alternative to give the functionality you are looking for

    If you are not using PRU cores for anything else in your design, you could program the PRU cores to detect the incoming signals instead, and take action as needed. You would see the fastest performance with sending the PWM signals directly to the PRU GPI inputs.

    For more about PRU cores & PRU GPI / PRU GPO signals, refer to 
    https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1269989/faq-what-is-a-pru-core-why-are-pru-gpio-signals-different-from-regular-gpios

    Regards,

    Nick