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.

MSP430FR2476: Testing The Clock System ISR

Part Number: MSP430FR2476

Hello,

I am currently working on testing my Clock System driver and its associated ISR. I have attached the ISR implementation below for reference.

I would like to ask for your recommendation on how to properly test the fault flags DCOFFG and XT1OFFG. Would it be appropriate to manually set these flags in the registers to simulate the fault conditions, or is there a preferred method for triggering them during testing?

Any guidance on the recommended testing approach would be greatly appreciated.

#include "msp430fr2xx_4xx_hal_cs.h"
#include "msp430.h"
#include <stdbool.h>
#include <stddef.h> 
#include "framctl.h"

static volatile uint8_t count_DCO_fail = 0;

//------------------------------------------------------FRAM FLAG ----------------------------------------------------------------

// This flag tells init not to attempt to source XT1 again if it was already declared dead.
#pragma PERSISTENT (cs_xt1_faulty)
static volatile bool cs_xt1_faulty = false;

//----------------------------------------------------------FOR ISR - FLL AND DCO ---------------------------------------------------------------//

#define XT1_LF_FREQ_HZ      32768UL     // standard 32.768 kHz watch crystal 
#define FLL_REF_FREQ_HZ     32768UL     // FLL reference FLLREFDIV=1 // I am assuming LF XT1

// XT1 recalibration using the hw counter threshold
#define XT1_HW_RETRY_MAX        3

// DCO recalibration retries per NMI entry 
#define DCO_RECAL_RETRY_MAX     3

// Threshold for the amount of DCO fault flag tries
#define DCO_POR_THRESHOLD       10

//Variable that stores the csctl4 register
static uint16_t xt1_saved_csctl4 = 0;
//this one tells is if we saved the contents in the csctl4 and switched the ref clock to refo
static bool     xt1_csctl4_saved = false;

//This is the amount of time we wil be waiting for the xt1 to recver by itself using its HW counter
static uint32_t xt1_hw_wait_cycles(void)
{
    uint32_t mclk = CS_getMCLK();
    uint16_t csctl6 = HWREG16(CS_BASE + OFS_CSCTL6);

    if (!(csctl6 & XTS)) {
        /* LF mode: 8192 ticks / 32768 Hz = 0.25 s
         * cycles = 0.25 × MCLK = (8192 × MCLK) / 32768            */
        return (8192UL * mclk) / XT1_LF_FREQ_HZ;
    }

    return (8192UL * mclk) / XT1_LF_FREQ_HZ;
}

//-------------------------------------------------------------- DCO--------------------------------------------------------------------------
static bool CS_recalibrateDCO_ISR(void)
{
    /* Steps 11–12: track the configuration closest to midrange (256) */
    uint16_t bestCsCtl0 = 0;
    uint16_t bestCsCtl1 = 0;
    uint16_t bestDelta = 0xFFFFu;

    uint16_t dcoftrim = (HWREG16(CS_BASE + OFS_CSCTL1) >> 4) & 0x07u;
    uint8_t  attempts = 0;
    bool     fllLocked = false;

    // Step 1: Disable the FLL
    __bis_SR_register(SCG0);

    // Step 2: I did made like a global variable for the ref clock - in our application we will only be using the XT1 in LF mode as the ref clock for FLL

    // Step 3: Enable DCOFTRIMEN so the trim takes effect.
    HWREG16(CS_BASE + OFS_CSCTL1) |= DCOFTRIMEN;

    // Step 5: Three NOPs BEFORE re-enabling the FLL.
    __no_operation();
    __no_operation();
    __no_operation();

    // Step 6: Re-enable FLL. 
    __bic_SR_register(SCG0);

    // Steps 7–14 :  trim search loop. 
    while (attempts < DCO_RECAL_RETRY_MAX)
    {
        // Step 7: Preset DCO tap = 256 (midrange)
        HWREG16(CS_BASE + OFS_CSCTL0) = DCO8;

        // Step 8: Clear DCOFFG BEFORE the wait so the hardware
        HWREG8(CS_BASE + OFS_CSCTL7_L) &= ~DCOFFG;
        HWREG8(SFR_BASE + OFS_SFRIFG1) &= ~OFIFG;

        // Step 9: Wait ≥ 24 / fFLLREF for FLLUNLOCK to stabilise. - (24 × MCLK) / FLLREF.
        // __delay_cycles((24UL * CS_getMCLK()) / FLL_REF_FREQ_HZ);

        // More accurate — accounts for ~3 cycles per iteration ((decrement + compare + branch))
        uint32_t delay_val = ((24UL * CS_getMCLK()) / FLL_REF_FREQ_HZ) / 3u;
        while (delay_val--) {
            __no_operation();
        }

        // Step 10: Poll - spin until locked OR DCO fault re-fires.   
        while ((HWREG16(CS_BASE + OFS_CSCTL7) & (FLLUNLOCK0 | FLLUNLOCK1)) &&
            !(HWREG16(CS_BASE + OFS_CSCTL7) & DCOFFG));

        // Step 11: Read settled tap, compute delta from midrange.    
        uint16_t tap = HWREG16(CS_BASE + OFS_CSCTL0) & 0x01FFu;
        uint16_t delta = (tap >= 256u) ? (tap - 256u) : (256u - tap);

        // Step 12: Record best (closest to 256) CSCTL0/CSCTL1.      
        if (delta < bestDelta) {
            bestDelta = delta;
            bestCsCtl0 = HWREG16(CS_BASE + OFS_CSCTL0);
            bestCsCtl1 = HWREG16(CS_BASE + OFS_CSCTL1);
        }

        // Check if FLL locked cleanly (DCOFFG did not re-fire).      
        if (!(HWREG8(CS_BASE + OFS_CSCTL7_L) & DCOFFG)) {
            fllLocked = true;
            break;
        }

        // Step 13: Adjust DCOFTRIM toward midrange.
        if (tap < 256u && dcoftrim > 0u) {
            dcoftrim--;
        }
        else if (tap >= 256u && dcoftrim < 7u) {
            dcoftrim++;
        }
        else {
            break;   // already at boundary, cannot adjust further 
        }

        // Write updated DCOFTRIM, preserving all other CSCTL1 bits.  
        HWREG16(CS_BASE + OFS_CSCTL1) =
            (HWREG16(CS_BASE + OFS_CSCTL1) & ~(0x07u << 4)) |
            ((uint16_t)dcoftrim << 4);

        // Step 14: Repeat from step 7. 
        attempts++;
    }

    // Step 15 — Reload best CSCTL0/CSCTL1 recorded across iterations. 
    if (bestDelta < 0xFFFFu) {
        HWREG16(CS_BASE + OFS_CSCTL0) = bestCsCtl0;
        HWREG16(CS_BASE + OFS_CSCTL1) = bestCsCtl1;
    }

    return fllLocked;
}

//--------------------------------------Functions using TI DriverLib ------------------------------------------------------------//

#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=UNMI_VECTOR
__interrupt void UNMI_ISR(void)
#elif defined(__GNUC__)
void __attribute__((interrupt(UNMI_VECTOR))) UNMI_ISR(void)
#else
#error Compiler not supported!
#endif
{
    switch (__even_in_range(HWREG16(__MSP430_BASEADDRESS_SYS__ + OFS_SYSUNIV), SYSUNIV__OFIFG)) {

    case SYSUNIV__NONE: //OFFSET 0
        break;

    case SYSUNIV__NMIIFG: //OFFSET 2
        break;

    case SYSUNIV__OFIFG: {  //OFFSET 4

        uint8_t csctl7 = HWREG8(CS_BASE + OFS_CSCTL7_L);
        if (csctl7 & DCOFFG) {

            bool recal_ok = CS_recalibrateDCO_ISR();

            if (recal_ok) {
                count_DCO_fail = 0;     /* recovery succeeded */
            }
            else {
                count_DCO_fail++;

                HWREG8(CS_BASE + OFS_CSCTL7_L) &= ~DCOFFG;
                HWREG8(SFR_BASE + OFS_SFRIFG1) &= ~OFIFG;

                if (count_DCO_fail >= DCO_POR_THRESHOLD) {
                    HWREG16(PMM_BASE + OFS_PMMCTL0) =
                        PMMPW | PMMSWPOR;
                }
            }
        }


        else if (csctl7 & XT1OFFG)
        {

            HWREG8(CS_BASE + OFS_CSCTL7_L) |= ENSTFCNT1;

            // Save CSCTL4 before we change SELMS/SELA. So once the xt1 recovers we use the old settings
            if (!xt1_csctl4_saved) {
                xt1_saved_csctl4 = HWREG16(CS_BASE + OFS_CSCTL4);
                xt1_csctl4_saved = true;
            }

            bool    xt1_recovered = false;
            uint8_t hw_retry = 0;

            while (hw_retry < XT1_HW_RETRY_MAX)
            {
                HWREG8(CS_BASE + OFS_CSCTL7_L) &= ~XT1OFFG;
                HWREG8(SFR_BASE + OFS_SFRIFG1) &= ~OFIFG;

                // __delay_cycles(xt1_hw_wait_cycles());
                // REPLACEMENT:
                uint32_t xt1_wait = xt1_hw_wait_cycles() / 3u;
                while (xt1_wait--) {
                    __no_operation();
                }

                if (!(HWREG8(CS_BASE + OFS_CSCTL7_L) & XT1OFFG)) {
                    xt1_recovered = true;
                    break;
                }
                hw_retry++;
            }

            if (xt1_recovered)
            {
                // we check if the csclt4 contents were saved
                if (xt1_csctl4_saved) {
                    HWREG16(CS_BASE + OFS_CSCTL4) = xt1_saved_csctl4;
                    xt1_csctl4_saved = false;
                    cs_xt1_faulty = false;
                }

                HWREG8(CS_BASE + OFS_CSCTL7_L) &= ~XT1OFFG;
                HWREG8(SFR_BASE + OFS_SFRIFG1) &= ~OFIFG;
            }
            else
            {
                /* XT1 did not recover — commit REFO fallback.   */


                if ((HWREG16(CS_BASE + OFS_CSCTL4) & SELMS) == CS_XT1CLK_SELECT) {
                    HWREG16(CS_BASE + OFS_CSCTL4) &= ~SELMS;
                    HWREG16(CS_BASE + OFS_CSCTL4) |= CS_REFOCLK_SELECT;
                }
                if ((HWREG16(CS_BASE + OFS_CSCTL4) & SELA) == SELA__XT1CLK) {
                    HWREG16(CS_BASE + OFS_CSCTL4) &= ~SELA;
                    HWREG16(CS_BASE + OFS_CSCTL4) |= SELA__REFOCLK;
                }

                // FRAM: survives POR — blocks XT1 on next boot. 
                cs_xt1_faulty = true;

                HWREG8(CS_BASE + OFS_CSCTL7_L) &= ~XT1OFFG;
                HWREG8(SFR_BASE + OFS_SFRIFG1) &= ~OFIFG;
            }
        }
        break;
    }

    default:
        break;
    }
}
  • Hi Ahlam,

    Overall the code looks good to me. But i do recommended Clear both DCOFFG and XT1OFFG first , then clear the OFIFG flag to verify if the OFIFG is staying cleared to ensure the stability of the oscillators. 

    Best regards,

    Bill

  • 1) You should be able to trigger XT1OFFG by turning off the XT1 oscillator, i.e. setting P2SEL0.0/.1 to =0. I expect the recovery for XT1OFFG would be to restart XT1 by setting those PSELs first to =0 then to =1, then wait (hope) for it to start up.

    2) You can trigger DCOFFG by setting CSCTL2:FLLN to something preposterous, e.g. 0x1FF, which the DCO can't tune to within DCORSEL. The recovery for this case would presumably be to set FLLN/DCORSEL (back) to what you want, then see if the FLL settles.

    [Edit: Fixed PSEL register name.]

  • Use the debugger.

    Set a breakpoint somewhere appropriate. (And in your ISR!) Start execution and wait for the breakpoint.

    Use the debugger to set an interrupt flag (if interrupt enable isn't set, do that first)  and then continue execution. See what happens. Repeat as required for other interrupts.

**Attention** This is a public forum