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.

MSP432E401Y: GPTM Interval Load Register value appears 1 off when using an ADC

Part Number: MSP432E401Y
Other Parts Discussed in Thread: MSP-EXP432E401Y, SYSBIOS

I am using the MSP-EXP432E401Y LaunchPad kit which runs at 120MHz, and I am using the TI Drivers to operate either a periodic timer to signal an event, or an ADC to periodically sample at a specified frequency, which automatically uses a periodic timer.

When I directly use a periodic timer to signal an event (no ADC involved) and I look at the GPTM Interval Load Register (GPTMTAILR, a.k.a. TIMER_TAILR in the CCS debugger), I notice that the count is 1 less than I initially expected, until I read this section in the MSP432E4 Technical Reference Manual:


"When the timer is counting down and it reaches the time-out event (0x0), the timer reloads its start value
from the GPTMTnILR and the GPTMTnPR registers on the next cycle"


The timer does indeed count down, and if 0 is part of the count, then the Interval Load Register must be 1 less. For example if I want to signal an event at 8Hz, then the number seen in the Interval Load Register must be:


120MHz / 8Hz - 1 = 14999999 = 0xE4E1BF     and this is what I see in the CCS debugger


The confusion arises when I use an ADC to periodically sample at a specified frequency, say 10KHz. If I use the same "1 less" calculation as above, I get:


120MHz / 10KHz - 1 = 11999 = 0x2EDF


However what I see in the Interval Load Register in the CCS debugger is 1 more than this value: 0x2EE0! When I look at the Timer Mode Register this timer is indeed counting down, so why is the number not 0x2EDF? Could this be a bug in the TI Drivers software, or am I missing something here?

  • The following code illustrates the issue I am seeing.

    It is taken directly from the adcbuftemperature_MSP_EXP432E401Y_tirtos_ccs example in the simplelink_msp432e4_sdk_3_20_00_10 SDK, with my additions marked by /* MY ADD */ (2 places), so it should be easy to reconstruct with a different SDK and MCU.

    The ADC is sampling at 200Hz.  The processor is running at 120MHz.  A direct calculation of the timer load value, with 0 (the timeout value) being part of the sequence, should be:

    120MHz / 200Hz - 1 = 599999 = 0x927BF

    What I'm seeing is a value that is 1 greater: 600000 = 0x927C0
    But if 0 is part of the count sequence, then we have 600001 states in the count sequence, which is 1 greater than it should be. This doesn't occur when I set up a timer directly, so why the discrepancy?

    This may seem to be an insignificant issue, but we're doing some precise synchronization, and I would like to understand what is happening.


    The actual console output is:
    Starting the ADCBuf temperature example
    The average temperature is 32.0070C
    The load value is 0x927c0


    = = = = = = = adcBufTemperature.c = = = = = = =

    /*
     * Copyright (c) 2015-2017, Texas Instruments Incorporated
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     *
     * *  Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     *
     * *  Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     *
     * *  Neither the name of Texas Instruments Incorporated nor the names of
     *    its contributors may be used to endorse or promote products derived
     *    from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    /*
     *  ======== adcBufTemperature.c ========
     */
    #include <stdint.h>
    #include <stdio.h>

    /* Driver Header files */
    #include <ti/drivers/ADCBuf.h>
    #include <ti/drivers/GPIO.h>
    #include <ti/devices/msp432e4/driverlib/driverlib.h>       /* MY ADD */

    #include <semaphore.h>

    /* Display Header files */
    #include <ti/display/Display.h>
    #include <ti/display/DisplayUart.h>

    /* For sleep() */
    #include <unistd.h>

    /* Example/Board Header files */
    #include "Board.h"

    #define ADCBUFFERSIZE    (20)

    uint16_t sampleBufferOne[ADCBUFFERSIZE];
    float CBuffer[ADCBUFFERSIZE];
    float avgTemperature;

    /* Driver handles shared between the task and the callback function */
    ADCBuf_Handle adcBuf;
    ADCBuf_Conversion continuousConversion;

    /* Display Driver Handle */
    Display_Handle displayHandle;

    /* ADCBuf semaphore */
    sem_t adcbufSem;

    /*
     * This function is called whenever a buffer is full.
     * The content of the buffer is then averaged and converted into degrees Celsius format
     * and sent to the PC via the UART.
     */
    void adcBufCallback(ADCBuf_Handle handle, ADCBuf_Conversion *conversion,
        void *completedADCBuffer, uint32_t completedChannel) {
        uint_fast16_t i;
        uint16_t *rawTemperatureBuf = (uint16_t *) completedADCBuffer;

        avgTemperature = 0;

        /* Calculate average temperature */
        for (i = 0; i < ADCBUFFERSIZE; i++) {
            avgTemperature += rawTemperatureBuf[i];
        }
        avgTemperature = avgTemperature/ADCBUFFERSIZE;

        /* Convert ADC value to Celsius */
        avgTemperature = (1475*4096 - (75 * 33 * avgTemperature))/ 40960;

        /* post adcbuf semaphore */
        sem_post(&adcbufSem);
    }

    /*
     *  ======== gpioButtonFxn0 ========
     *  Callback function for the GPIO interrupt on Board_GPIO_BUTTON0.
     */
    void gpioButtonFxn0(uint_least8_t index)
    {
        /* Start converting. */
        if (ADCBuf_convert(adcBuf, &continuousConversion, 1) !=
            ADCBuf_STATUS_SUCCESS) {
            /* Did not start conversion process correctly. */
            while(1);
        }
    }

    /*
     *  ======== mainThread ========
     */
    void *mainThread(void *arg0)
    {
        ADCBuf_Params adcBufParams;
        Display_Params displayParams;

        /* Call driver init functions */
        ADCBuf_init();
        GPIO_init();
        Display_init();
        int32_t         status;

        /* Configure & open Display driver */
        Display_Params_init(&displayParams);
        displayParams.lineClearMode = DISPLAY_CLEAR_BOTH;
        displayHandle = Display_open(Display_Type_UART, &displayParams);
        if (displayHandle == NULL) {
            while (1);
        }

        /* Configure button pins and install Button callback */
        GPIO_setConfig(Board_GPIO_BUTTON0, GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_FALLING);
        GPIO_setCallback(Board_GPIO_BUTTON0, gpioButtonFxn0);
        GPIO_enableInt(Board_GPIO_BUTTON0);

        status = sem_init(&adcbufSem, 0, 0);
        if (status != 0) {
            Display_printf(displayHandle, 0, 0, "Error creating adcbufSem\n");
            while(1);
        }

        Display_printf(displayHandle, 0, 0, "Starting the ADCBuf temperature example");

        /* Set up an ADCBuf peripheral in ADCBuf_RECURRENCE_MODE_CONTINUOUS */
        ADCBuf_Params_init(&adcBufParams);
        adcBufParams.callbackFxn = adcBufCallback;
        adcBufParams.recurrenceMode = ADCBuf_RECURRENCE_MODE_ONE_SHOT;
        adcBufParams.returnMode = ADCBuf_RETURN_MODE_CALLBACK;
        adcBufParams.samplingFrequency = 200;

        adcBuf = ADCBuf_open(Board_ADCBUF0, &adcBufParams);

        if (!adcBuf){
            /* AdcBuf did not open correctly. */
            while(1);
        }

        /* Configure the conversion struct */
        continuousConversion.arg = NULL;
        continuousConversion.adcChannel = Board_ADCBUF0CHANNEL2;
        continuousConversion.sampleBuffer = sampleBufferOne;
        continuousConversion.sampleBufferTwo = sampleBufferOne;
        continuousConversion.samplesRequestedCount = ADCBUFFERSIZE;

        /*
         * Go to sleep in the foreground thread forever. The data will be collected
         * and transfered in the background thread
         */
        while(1) {
            /* Wait for semaphore and print average temperature */
            sem_wait(&adcbufSem);
           /* Print a message with average temperature */
            Display_printf(displayHandle, 0, 0, "The average temperature is %.3fC",
             avgTemperature);
            uint32_t loadValue = TimerLoadGet(TIMER1_BASE, TIMER_A);      /* MY ADD */
            Display_printf(displayHandle, 0, 0, "The load value is 0x%x", /* MY ADD */
             loadValue);                                                  /* MY ADD */
        }

    }

  • I need to correct an error in my original post.  I stated I was comparing the Timer Load Register value when operating an ADC at a specified sample rate (which uses a timer under the covers) vs running a timer directly (with no ADC) to periodically signal an event, both using the TI Drivers interface.  It turns out I was using the sysbios/hal interface for second scenario - running a timer directly.

    So I decided to run the timerled_MSP_EXP432E401Y_tirtos_ccs example in the simplelink_msp432e4_sdk_3_20_00_10 SDK, which uses the TI Drivers interface to operate the timer (to toggle an LED at 1 Hz), and check what it sets the Timer Load Register to, and it was also 1 greater than it should be!

    The example sets the period to 1000000 microseconds, or 1 second, and so the timer, clocked by a 120MHz system clock, must continuously cycle through 120,000,000 states.  Because 0 is part of the count sequence, the number in the Timer Load Register should be 1 less, or 119,999,999.  But when I checked the timer load register it was set to 0x07270E00 = 120,000,000.

    So I then reprogrammed the example to use the sysbios/hal interface rather than the TI Drivers interface, and the Timer Load Register value was as it should be: 0x07270DFF = 119,999,999.

    So it appears there is a bug in the TI Drivers software that affects both the ADC sampling frequency (and hence a timer) and directly setting up a timer for periodic signaling.  Or perhaps I'm missing something in my analysis.  In all cases the timer is counting down.

    TI, are you listening?

  • Hi Brian,

    Thank you for you exhaustive debug on this.  Yes, I will forward this to our MSP432 driverlib SW team.  It may take them some time to visit this issue and I honestly don't know when the next update to the 432 driverlib will be posted with the fix.

    I will close this thread for now, unless you wish to provide additional details.  If you wish to keep this thread open, please click the "This did NOT resolve my issue" button and reply to this thread with more information.
    If this thread locks, please click the "Ask a related question" button and in the new thread describe the current status of your issue and any additional details you may have to assist us in helping to solve your issues.

  • Actually it is the TI Drivers library, not the DriverLib library, that I believe has the bug.  (Don't know if DriverLib has this same issue.)  But you may proceed to close this thread, but it should be updated in the future if the bug has been fixed, mentioning the SDK version that contains the fix.

  • Hi Brian,

    Good point.  I'll dig into this and confirm the issue.  Once the SW team provides an estimate when a fix is available I'll update this thread.

  • Closing this thread for now.  Will re-open when new information is available.