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.

MSP430FR5994: ADC12B: Switching between DMA based conversions and software controlled conversions.

Part Number: MSP430FR5994
Other Parts Discussed in Thread: MSP430DRIVERLIB

I am working on an application that requires DMA to drive ADC conversions during normal operation, but uses other channels to occasionally acquire other ADC measurements. To take software controlled measurements, the  DMA transfers are disabled and the conversion sequence mode is set to ADC12_B_SAMPLEHOLDSOURCE_SC.  The software then switches back to DMA based transfers once the software controlled measurements are taken.

I found that the DMA transfers stop after switching back to using the DMA to drive the conversions.   This forum post (https://e2e.ti.com/support/microcontrollers/msp430/f/166/p/401588/1429066#1429066) indicates there is an internal hardware signal within the ADC that needs to be reset in order for the conversion trigger to start.  I implemented a separate channel triggering on a high level on a small demo version that functions when the work-around is enabled.  However, section 11.2.3.2 of the User's guide indicates  the use of a high level trigger  can only be used when the external trigger DMAE0 is selected.

My  questions are:

1. Is there any TI documentation on this behavior I can refer to to indicate why this is being done and why the work-around works. No TI employees responded to the linked forum post describing the issue and the current errata document does not contain any information on this issue.

2. Can TI confirm that this work-around is appropriate and will not impact the system in other unintended ways?

Work around code is below.

    // Setp a channel to clear a stuck adc when switching back to dma from software conversions. 
    param.channelSelect = DMA_CHANNEL_1;                        // Unused DMA Channel
    param.transferModeSelect = DMA_TRANSFER_SINGLE;             // One time transfer
    param.transferSize = 3;                // Analyze block size
    param.triggerSourceSelect = DMA_TRIGGERSOURCE_26;           // ADC12 end of conversion
    param.transferUnitSelect = DMA_SIZE_SRCWORD_DSTWORD;        // 16 bits
    param.triggerTypeSelect = DMA_TRIGGER_HIGH;                 // Trigger on high level
    DMA_init(&param);


static void ClearStuckAdcDma(void)
{
    //e2e.ti.com/.../1429066
    //Enable DMA channel 1 interrupt
#ifdef ENABLE_STUCK_DMA_HANDLING
    DMA_clearInterrupt(DMA_CHANNEL_1);
    DMA_enableInterrupt(DMA_CHANNEL_1);

    //Enable transfers on DMA channel 1
    DMA_enableTransfers(DMA_CHANNEL_1);
    DMA_disableTransfers(DMA_CHANNEL_1);
#endif
}

The full demo application  used to test and confirm the work-around requires the msp430 driverlib. It should run on an MSP430FR5994 Development Kit.Comment Out the definition of ENABLE_STUCK_DMA_HANDLING to reproduce the stuck dma issue.  Software will hang waiting for a DMA interrupt that never occurs. 

I used version msp430driverlib version 2.91.13.01 and  IAR version 7.20.1 to compile.

#include <assert.h>
#include "gpio.h"
#include "gpio.h"
#include "adc12_b.h"
#include "dma.h"
#include "timer_a.h"
#include "framctl_a.h"
#include "cs.h"
#include "pmm.c"

#define CLK_RSEL        CS_DCORSEL_1
#define CLK_FSEL        CS_DCOFSEL_4

#define DMA_SIZE 3

// The stuck dma handling enables a single conversion on channel 0, with the trigger set to high.
#define ENABLE_STUCK_DMA_HANDLING


static const GPIO_PinDef_t AdcPin[2] =
{
//    Port,            Pin,            Alternate Fx,                 Direction,      Pull,          Irq En, Irq Edge
    { GPIO_PORT_P1,    GPIO_PIN1,      GPIO_TERNARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE },
    { GPIO_PORT_P1,    GPIO_PIN0,      GPIO_TERNARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE },
};

static const GPIO_PinDef_t XtalPin[2] =
{
//    Port,       Pin,       Alternate Fx,                 Direction,      Pull,          Irq En, Irq Edge
    { GPIO_PORT_PJ, GPIO_PIN4, GPIO_PRIMARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE },
    { GPIO_PORT_PJ, GPIO_PIN5, GPIO_PRIMARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE }
};


void boardInit(void)
{
    // Setup Default PINS - Note that some get overwritten with new settings.
    P1SEL0 = 0; P1SEL1 = 0; P2SEL0 = 0; P2SEL1 = 0; P3SEL0 = 0; P3SEL1 = 0; P4SEL0 = 0; P4SEL1 = 0; P5SEL0 = 0; P5SEL1 = 0; P6SEL0 = 0;   P6SEL1 = 0; P7SEL0 = 0; P7SEL1 = 0; P8SEL0 = 0; P8SEL1 = 0;   // All lines GPIO
    P1DIR = 0xFF;           P2DIR = 0xF7;           P3DIR = 0xFF;           P4DIR = 0xFA;           P5DIR = 0xFF;           P6DIR = 0xFC;             P7DIR = 0xFE;           P8DIR = 0xFF;             // Input and Output per board
    P1REN = 0x00;           P2REN = 0x00;           P3REN = 0x00;           P4REN = 0x00;           P5REN = 0x00;           P6REN = 0x00;             P7REN = 0x00;           P8REN = 0x00;             // Disable all pulls
    P1OUT = 0x00;           P2OUT = 0x00;           P3OUT = 0x00;           P4OUT = 0x00;           P5OUT = 0x00;           P6OUT = 0x00;             P7OUT = 0x00;           P8OUT = 0x00;             // Set pulls/outputs to low
    P1IE = 0x00;            P2IE = 0x00;            P3IE = 0x00;            P4IE = 0x00;            P5IE = 0x00;            P6IE = 0x00;              P7IE = 0x00;            P8IE = 0x00;              // Disable all interrupts

    PJOUT = 0;
    PJSEL0 = BIT4 | BIT5;                   // For XT1
    PJDIR = 0xFFFF;

    PMM_unlockLPM5();   // Put pins in configured states

    // Init clocks

    // Set Pins for XT1 in
    GPIO_InitPin(&XtalPin[0].Pin, &XtalPin[0].Init);
    GPIO_InitPin(&XtalPin[1].Pin, &XtalPin[1].Init);

    //Initialize Clocks

    // Configure FRAM waitstate as required by the device datasheet for MCLK
    // operation at operating freq _before_ configuring the clock system.
    FRAMCtl_A_configureWaitStateControl(FRAMCTL_A_ACCESS_TIME_CYCLES_1);

    // Set up clock sources
    CS_setExternalClockSource(32768, 0);
    if(CS_turnOnLFXTWithTimeout(CS_LFXT_DRIVE_0, 100000) == STATUS_FAIL)
    {
        assert(false);
    }
    CS_setDCOFreq(CLK_RSEL, CLK_FSEL);
    CS_initClockSignal(CS_ACLK,  CS_LFXTCLK_SELECT, CS_CLOCK_DIVIDER_1);
    CS_initClockSignal(CS_MCLK,  CS_DCOCLK_SELECT,  CS_CLOCK_DIVIDER_1);
    CS_initClockSignal(CS_SMCLK, CS_DCOCLK_SELECT,  CS_CLOCK_DIVIDER_1);
}


static uint16_t dmaBuffer[DMA_SIZE];
static uint32_t UnusedDmaData = 0;


volatile bool dmaTransferComplete = false;
volatile bool Mem0Complete = false;
volatile bool Mem3Complete = false;

static void InitDma(void)
{
    DMA_initParam param = {0};
    param.channelSelect = DMA_CHANNEL_0;
    param.transferModeSelect = DMA_TRANSFER_REPEATED_SINGLE;    // Repeated block transfers
    param.transferSize = DMA_SIZE;                // Analyze block size
    param.triggerSourceSelect = DMA_TRIGGERSOURCE_26;           // ADC12 end of conversion
    param.transferUnitSelect = DMA_SIZE_SRCWORD_DSTWORD;        // 16 bits
    param.triggerTypeSelect = DMA_TRIGGER_RISINGEDGE;           // Rising edge of trigger
    DMA_init(&param);

    // Set data source, do not increment address
    DMA_setSrcAddress(DMA_CHANNEL_0, ADC12_B_getMemoryAddressForDMA(ADC12_B_BASE, ADC12_B_MEMORY_3), DMA_DIRECTION_UNCHANGED);

    // Set data destination, increment address
    DMA_setDstAddress(DMA_CHANNEL_0, (uint32_t)&dmaBuffer, DMA_DIRECTION_INCREMENT);

    //Enable DMA channel 0 interrupt
    DMA_clearInterrupt(DMA_CHANNEL_0);
    DMA_enableInterrupt(DMA_CHANNEL_0);

    //Enable transfers on DMA channel 0
    DMA_enableTransfers(DMA_CHANNEL_0);


    // Setup clearing the "stuck" ADC after manual sampling
    // We need to run a DMA cycle on any DMA channel triggered by a high level on the ADC DMA trigger line
    ADC12_B_disableConversions(ADC12_B_BASE, ADC12_B_PREEMPTCONVERSION);

    param.channelSelect = DMA_CHANNEL_1;                        // Unused DMA Channel
    param.transferModeSelect = DMA_TRANSFER_SINGLE;             // One time transfer
    param.transferSize = 3;                // Analyze block size
    param.triggerSourceSelect = DMA_TRIGGERSOURCE_26;           // ADC12 end of conversion
    param.transferUnitSelect = DMA_SIZE_SRCWORD_DSTWORD;        // 16 bits
    param.triggerTypeSelect = DMA_TRIGGER_HIGH;                 // Trigger on high level
    DMA_init(&param);

    // Set data source, do not increment address
    DMA_setSrcAddress(DMA_CHANNEL_1, ADC12_B_getMemoryAddressForDMA(ADC12_B_BASE, ADC12_B_MEMORY_3), DMA_DIRECTION_UNCHANGED);

    // Set data destination, increment address
    DMA_setDstAddress(DMA_CHANNEL_1, (uint32_t)&UnusedDmaData, DMA_DIRECTION_UNCHANGED);
}

static void InitAdc(void)
{
    //Initialize the ADC12B Module
    ADC12_B_initParam initParam = {0};
    initParam.sampleHoldSignalSourceSelect = ADC12_B_SAMPLEHOLDSOURCE_7;    // TA4 CCR1 - default for Filter
    initParam.clockSourceSelect = ADC12_B_CLOCKSOURCE_ADC12OSC;             // Note: Keep the ADC12_B_CLOCKSOURCE_ADCOSC clock. While the SMCLK uses less power, it doens't sample correctly.
    initParam.clockSourceDivider = ADC12_B_CLOCKDIVIDER_1;
    initParam.clockSourcePredivider = ADC12_B_CLOCKPREDIVIDER__1;
    initParam.internalChannelMap = ADC12_B_NOINTCH;                         // No internal channel
    ADC12_B_init(ADC12_B_BASE, &initParam);
    ADC12_B_setAdcPowerMode(ADC12_B_BASE, ADC12_B_LOWPOWERMODE);
    ADC12_B_setupSamplingTimer(ADC12_B_BASE, ADC12_B_CYCLEHOLD_4_CYCLES, ADC12_B_CYCLEHOLD_4_CYCLES, ADC12_B_MULTIPLESAMPLESDISABLE);

    //Enable the ADC12B module
    ADC12_B_enable(ADC12_B_BASE);
}


static void ClearStuckAdcDma(void)
{
    //e2e.ti.com/.../1429066
    //Enable DMA channel 1 interrupt
#ifdef ENABLE_STUCK_DMA_HANDLING
    DMA_clearInterrupt(DMA_CHANNEL_1);
    DMA_enableInterrupt(DMA_CHANNEL_1);

    //Enable transfers on DMA channel 1
    DMA_enableTransfers(DMA_CHANNEL_1);
    DMA_disableTransfers(DMA_CHANNEL_1);
#endif
}

void StartDmaBasedAdc(void)
{
    ADC12_B_disableInterrupt(ADC12_B_BASE, 0xFF, 0, 0);
    ADC12_B_disable(ADC12_B_BASE);
    DMA_disableTransfers(DMA_CHANNEL_0);
    ClearStuckAdcDma();

    DMA_clearInterrupt(DMA_CHANNEL_0);
    DMA_enableInterrupt(DMA_CHANNEL_0);
    DMA_enableTransfers(DMA_CHANNEL_0);

}

void StarTriggeredAdc(void)
{
    ADC12_B_disableConversions(ADC12_B_BASE, ADC12_B_PREEMPTCONVERSION);
    DMA_disableTransfers(DMA_CHANNEL_0);   // Disable Filter DMA
}

void StartAdcConversion(uint16_t MemorySlot, uint16_t InputChannel, uint8_t ConvType, uint16_t Trigger )
{
    // Make sure adc is not busy
    while(ADC12_B_isBusy())
    {
        ;
    }

    ADC12_B_configureMemoryParam configureMemoryParam = {0};
    configureMemoryParam.memoryBufferControlIndex = MemorySlot;
    configureMemoryParam.inputSourceSelect = InputChannel;
    configureMemoryParam.refVoltageSourceSelect = ADC12_B_VREFPOS_AVCC_VREFNEG_VSS;
    configureMemoryParam.endOfSequence = ADC12_B_NOTENDOFSEQUENCE;
    configureMemoryParam.windowComparatorSelect = ADC12_B_WINDOW_COMPARATOR_DISABLE;
    configureMemoryParam.differentialModeSelect = ADC12_B_DIFFERENTIAL_MODE_DISABLE;
    ADC12_B_disableConversions(ADC12_B_BASE, ADC12_B_PREEMPTCONVERSION);
    ADC12_B_configureMemory(ADC12_B_BASE, &configureMemoryParam);
    ADC12_B_clearInterrupt(ADC12_B_BASE, 0, 0xFF);          // 0 for MEM0-MEM15


    if(Trigger == ADC12_B_SAMPLEHOLDSOURCE_SC)
    {
        DMA_disableTransfers(DMA_CHANNEL_0);
        ADC12_B_enableInterrupt(ADC12_B_BASE, (ADC12_B_IE0 | ADC12_B_IE3), 0, 0);
    }
    else
    {
        ADC12_B_disableInterrupt(ADC12_B_BASE, 0xFF, 0, 0);
        ClearStuckAdcDma();
        DMA_clearInterrupt(DMA_CHANNEL_0);
        DMA_enableInterrupt(DMA_CHANNEL_0);
        DMA_enableTransfers(DMA_CHANNEL_0);
    }

    // Start conversion
    ADC12_B_enable(ADC12_B_BASE);
    ADC12_B_startConversion(ADC12_B_BASE, MemorySlot >> 1, ConvType, Trigger);

}
void InitTimer(void)
{
        // Setup Filter timer (A4)
    Timer_A_initUpModeParam uParam;
    uParam.clockSource = TIMER_A_CLOCKSOURCE_ACLK;
    uParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1;
    uParam.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;
    uParam.timerClear = TIMER_A_DO_CLEAR;
    uParam.startTimer = true;
    uParam.timerPeriod = 0;     // Will be set in Filter init routine
    uParam.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CCIE_CCR0_INTERRUPT_DISABLE;


    Timer_A_initUpMode(TA4_BASE, &uParam);
    Timer_A_setOutputMode(TA4_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_1, TIMER_A_OUTPUTMODE_SET_RESET);


    Timer_A_updatePeriod(TA4_BASE, 1500);
    Timer_A_setCompareValue(TA4_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_1, 1500 >> 1);


}


volatile uint32_t AllConversionComplete = 0;
int main()
{
  // Stop watchdog timer to prevent time out reset
  WDTCTL = WDTPW + WDTHOLD;

  boardInit();

  // Init ADC Pins
  GPIO_InitPin(&AdcPin[0].Pin, &AdcPin[0].Init);
  GPIO_InitPin(&AdcPin[1].Pin, &AdcPin[1].Init);
   __enable_interrupt();

  InitAdc();
  InitTimer();

  InitDma();
  while (1 )
  {
      // // Section A Kick off DMA Conversions
      StartDmaBasedAdc();
      StartAdcConversion(ADC12_B_MEMORY_3 , ADC12_B_INPUT_A1, ADC12_B_REPEATED_SINGLECHANNEL, ADC12_B_SAMPLEHOLDSOURCE_7);
       // Wait for dma to complete
       while( !dmaTransferComplete) { ; }

      // Section B Switch to use Single Channel conversion, Memory 0 Channel 0
      StarTriggeredAdc();
      StartAdcConversion(ADC12_B_MEMORY_0 , ADC12_B_INPUT_A0, ADC12_B_SINGLECHANNEL, ADC12_B_SAMPLEHOLDSOURCE_SC);
      while( !Mem0Complete ) { ; }

      // Section C Switch to use Single Channel conversion, Memory 3 Channel 1
      StartAdcConversion(ADC12_B_MEMORY_3 , ADC12_B_INPUT_A1, ADC12_B_SINGLECHANNEL, ADC12_B_SAMPLEHOLDSOURCE_SC);
      while( !Mem3Complete ) { ; }

      // All conversions completed successfully, reset the tracking variables and loop to repeat the conversions.
      AllConversionComplete++;
      dmaTransferComplete = false;
      Mem0Complete = false;
      Mem3Complete = false;

  }

//  while ( 1 )
//  {
//      ;
//  }

  return 0;
}

// DMA interrupt service routine
#pragma vector=DMA_VECTOR
static __interrupt void DMA_ISR(void)
{
    switch(__even_in_range(DMAIV, 16))
    {
    case 0: break;
    case 2:             //DMA0IFG = DMA Channel 0 - Filter
        dmaTransferComplete = true;
        break;
    case 4: break;      //DMA1IFG = DMA Channel 1
    case 6: break;      //DMA2IFG = DMA Channel 2
    case 8: break;      //DMA3IFG = DMA Channel 3
    case 10: break;     //DMA4IFG = DMA Channel 4
    case 12: break;     //DMA5IFG = DMA Channel 5
    case 14: break;     //DMA6IFG = DMA Channel 6
    case 16: break;     //DMA7IFG = DMA Channel 7
    default: break;
    }
}

//ADC interrupt service routine
//__attribute__((section("RAMCODE"), noinline))     // Use this convention for functions
uint16_t AdcRes;

#pragma vector=ADC12_B_VECTOR
static __interrupt void ADC12_B_ISR(void)
{
    switch (__even_in_range(ADC12IV, 0x4C))
    {
    case ADC12IV__NONE: break;          // No interrupt
    case ADC12IV__ADC12TOVIFG:          // Conversion started before previous conversion completed
    case ADC12IV__ADC12OVIFG:           // Data reg written before last result read
    case ADC12IV__ADC12HIIFG:           // Hi Threshold interrupt
    case ADC12IV__ADC12LOIFG:           // Low Threshold interrupt
    case ADC12IV__ADC12INIFG:           // Within Thresholds interrupt
        assert(false);
        break;
    case ADC12IV__ADC12IFG0:            // Conversion done on MEM0 (ECG during streaming)
        AdcRes = ADC12_B_getResults(ADC12_B_BASE, ADC12_B_MEMORY_0);
        Mem0Complete = true;
        break;
    case ADC12IV__ADC12IFG3:            // Conversion done on MEM3 (FILTER) - Runs when streaming (ECG or LI)
        // Get ADC result and add it to the storage buffer
        AdcRes = ADC12_B_getResults(ADC12_B_BASE, ADC12_B_MEMORY_3);
        Mem3Complete = true;
        break;
    // Goes up to IFG31
    default:
        break;
    }

}


  • This was also observed independently over here:

    https://e2e.ti.com/support/microcontrollers/msp430/f/166/t/692027

    Mr. Cowsill's explanation is a little better than mine. I haven't seen any final answer for either.

    I've used DMALEVEL=1 (judiciously) with triggers other than DMAE0. I've never seen an explanation for that warning in the User Guide. Maybe I should put in a "Documentation Error" request to remove it and see how they respond.

  • Hello Jay,

    please let us know, whether you still need support on this. Many thanks in advance.

    Best regards

    Peter

  • Yes I still have not gotten answers to my original questions.  Bruce linked another user with the similar observations, but as he stated "I haven't seen any final answer for either."

    1. Is there any TI documentation on this behavior I can refer to to indicate why this is being done and why the work-around works. No TI employees responded to the linked forum post describing the issue and the current errata document does not contain any information on this issue.

    2. Can TI confirm that this work-around is appropriate and will not impact the system in other unintended ways?

  • Hello Jay,

    as far as I could see, you're clocking the ADC using its own oscillator, while the CPU and thus DMA are clocked from DCO. Could you please do me the favor and try using the DCO for the ADC operation as well? I know, this is probably not the target for your application, but I would like to understand whether this has any effect on the behavior you're observing.

    Best regards

    Peter

  • The clock source change did not impact the issue.

     Without the ENABLE_STUCK_DMA_HANDLING code, I still see the adc sampling loop completing once and then the DMA interrupt does not trigger on subsequent iterations.

    The snippet below shows the changes to uses SMCLK instead of the internal oscillator.  It also commented out setting of the ADC low power mode. 

    static void InitAdc(void)
    {
        //Initialize the ADC12B Module
        ADC12_B_initParam initParam = {0};
        initParam.sampleHoldSignalSourceSelect = ADC12_B_SAMPLEHOLDSOURCE_7;
        //initParam.clockSourceSelect = ADC12_B_CLOCKSOURCE_ADC12OSC;
        initParam.clockSourceSelect = ADC12_B_CLOCKSOURCE_SMCLK; // Update to test if ADC behavior changes with SMCLK
    
        initParam.clockSourceDivider = ADC12_B_CLOCKDIVIDER_1;
        initParam.clockSourcePredivider = ADC12_B_CLOCKPREDIVIDER__1;
        initParam.internalChannelMap = ADC12_B_NOINTCH;                         // No internal channel
        ADC12_B_init(ADC12_B_BASE, &initParam);
        //ADC12_B_setAdcPowerMode(ADC12_B_BASE, ADC12_B_LOWPOWERMODE);
        ADC12_B_setupSamplingTimer(ADC12_B_BASE, ADC12_B_CYCLEHOLD_4_CYCLES, ADC12_B_CYCLEHOLD_4_CYCLES, ADC12_B_MULTIPLESAMPLESDISABLE);
    
        //Enable the ADC12B module
        ADC12_B_enable(ADC12_B_BASE);
    }

  • Hi Jay,

    if I get it right you use edge triggered DMA mode while ADC is running from MODOSC and CPU from DCO. If the DMA misses the trigger and do not transfer anymore you need to reconfigure the DMA correct?

    This sounds slightly  similiar to a behavior found on FR5739 but never be reproduced, seen or reported on FR5994. Please take a look to DMA14 in the following ERRATA sheet:

    What wonders me is that it seems you tried running ADC and DMA from same clock source and had not success to eliminate the behavior is this correct?

    So the workaround you apply seems to be similar to DMA14 workaround but the conditions are different. Therefore I want to know if this happens immediately or only after reconfiguration? If it only appears after reconfig I strongly recommend to reinit ADC and DMA completely new to ensure no old trigger residues stay somewhere in the queue. The point is that all the reconfig is not described in detail above.

    So please let me know if you need further assistance.

  • One additional note it has to be ensured that SMCLK=MCLK=DCO not sure if this is considered while driving ADC from SMCLK.

  • Hi Dietmar,  Thanks for responding.  The issue repeats 100% of the time  with the code posted in the original post.   

    I would like confirmation that the workaround of  forcing a dma transfer by running the DMA with a High trigger level is acceptable.  

    Additionally, if there is any documentation available to point to as to why the work-around is effective that would be helpful too.

    I will try to answer all of the questions you raised.  

    "if I get it right you use edge triggered DMA mode while ADC is running from MODOSC and CPU from DCO."

    Yes, although the issue remained when everything was running on SMCLK sourced from DCO, 

    "If the DMA misses the trigger and do not transfer anymore you need to reconfigure the DMA correct?"

    Thie issue occurs after switching from the software controlled triggers back to dma based triggers. It looks like the ADC stops converting, because the ADC12OVIFG flag is not set.

    "This sounds slightly  similiar to a behavior found on FR5739 but never be reproduced, seen or reported on FR5994. Please take a look to DMA14 in the following ERRATA sheet:"
    What wonders me is that it seems you tried running ADC and DMA from same clock source and had not success to eliminate the behavior is this correct?"

    Agreed that they are similar, but the work-around proposed for DMA14 did not work to solve this issue and this issue occurs even when all modules are running on the same clock. See code at the end of this post for the implementation that uses SMCLK for the ADC.

    "So the workaround you apply seems to be similar to DMA14 workaround but the conditions are different. Therefore I want to know if this happens immediately or only after reconfiguration? If it only appears after reconfig I strongly recommend to reinit ADC and DMA completely new to ensure no old trigger residues stay somewhere in the queue. The point is that all the reconfig is not described in detail above."

    I tried adding in just the reconfig, but that doesn't seem to fix it.  I had to include the Level based dma transfer to force a read of the ADC memory before conversions started again 

    "One additional note it has to be ensured that SMCLK=MCLK=DCO not sure if this is considered while driving ADC from SMCLK."

    Yes, MCLK and SMCLK dividers were set to CS_CLOCK_DIVIDER_!

    Here is the code that I used to test the proposed DMA14 errata, controlled by using ENABLE_DMA_14_WORKAROUND.  

    #include <assert.h>
    #include "gpio.h"
    #include "adc12_b.h"
    #include "dma.h"
    #include "timer_a.h"
    #include "framctl_a.h"
    #include "cs.h"
    #include "pmm.c"
    
    #define CLK_RSEL        CS_DCORSEL_1
    #define CLK_FSEL        CS_DCOFSEL_4
    
    #define DMA_SIZE 3
    
    
    void InitTimer(void);
    
    
    // When tested on the MSP430FR59941, the only way found to continue DMA operation after switching to software conversions
    // is to ENABLE_STUCK_DMA_HANDLING
    
    // The stuck dma handling enables a single conversion on channel 0, with the trigger set to high.
    //#define ENABLE_STUCK_DMA_HANDLING
    #define ENABLE_DMA_14_WORKAROUND
    #define USE_SMCLK
    
    
    static const GPIO_PinDef_t AdcPin[2] =
    {
    //    Port,            Pin,            Alternate Fx,                 Direction,      Pull,          Irq En, Irq Edge
        { GPIO_PORT_P1,    GPIO_PIN1,      GPIO_TERNARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE },
        { GPIO_PORT_P1,    GPIO_PIN0,      GPIO_TERNARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE },
    };
    
    static const GPIO_PinDef_t XtalPin[2] =
    {
    //    Port,       Pin,       Alternate Fx,                 Direction,      Pull,          Irq En, Irq Edge
        { GPIO_PORT_PJ, GPIO_PIN4, GPIO_PRIMARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE },
        { GPIO_PORT_PJ, GPIO_PIN5, GPIO_PRIMARY_MODULE_FUNCTION, GPIO_DONT_CARE, GPIO_DONT_CARE, false, GPIO_DONT_CARE }
    };
    
    
    void boardInit(void)
    {
        // Setup Default PINS - Note that some get overwritten with new settings.
        P1SEL0 = 0; P1SEL1 = 0; P2SEL0 = 0; P2SEL1 = 0; P3SEL0 = 0; P3SEL1 = 0; P4SEL0 = 0; P4SEL1 = 0; P5SEL0 = 0; P5SEL1 = 0; P6SEL0 = 0;   P6SEL1 = 0; P7SEL0 = 0; P7SEL1 = 0; P8SEL0 = 0; P8SEL1 = 0;   // All lines GPIO
        P1DIR = 0xFF;           P2DIR = 0xF7;           P3DIR = 0xFF;           P4DIR = 0xFA;           P5DIR = 0xFF;           P6DIR = 0xFC;             P7DIR = 0xFE;           P8DIR = 0xFF;             // Input and Output per board
        P1REN = 0x00;           P2REN = 0x00;           P3REN = 0x00;           P4REN = 0x00;           P5REN = 0x00;           P6REN = 0x00;             P7REN = 0x00;           P8REN = 0x00;             // Disable all pulls
        P1OUT = 0x00;           P2OUT = 0x00;           P3OUT = 0x00;           P4OUT = 0x00;           P5OUT = 0x00;           P6OUT = 0x00;             P7OUT = 0x00;           P8OUT = 0x00;             // Set pulls/outputs to low
        P1IE = 0x00;            P2IE = 0x00;            P3IE = 0x00;            P4IE = 0x00;            P5IE = 0x00;            P6IE = 0x00;              P7IE = 0x00;            P8IE = 0x00;              // Disable all interrupts
    
        PJOUT = 0;
        PJSEL0 = BIT4 | BIT5;                   // For XT1
        PJDIR = 0xFFFF;
    
        PMM_unlockLPM5();   // Put pins in configured states
    
        // Init clocks
    
        // Set Pins for XT1 in
        GPIO_InitPin(&XtalPin[0].Pin, &XtalPin[0].Init);
        GPIO_InitPin(&XtalPin[1].Pin, &XtalPin[1].Init);
    
        //Initialize Clocks
    
        // Configure FRAM waitstate as required by the device datasheet for MCLK
        // operation at operating freq _before_ configuring the clock system.
        FRAMCtl_A_configureWaitStateControl(FRAMCTL_A_ACCESS_TIME_CYCLES_1);
    
        // Set up clock sources
        CS_setExternalClockSource(32768, 0);
        if(CS_turnOnLFXTWithTimeout(CS_LFXT_DRIVE_0, 100000) == STATUS_FAIL)
        {
            assert(false);
        }
        CS_setDCOFreq(CLK_RSEL, CLK_FSEL);
        CS_initClockSignal(CS_ACLK,  CS_LFXTCLK_SELECT, CS_CLOCK_DIVIDER_1);
        CS_initClockSignal(CS_MCLK,  CS_DCOCLK_SELECT,  CS_CLOCK_DIVIDER_1);
        CS_initClockSignal(CS_SMCLK, CS_DCOCLK_SELECT,  CS_CLOCK_DIVIDER_1);
    }
    
    
    static uint16_t dmaBuffer[DMA_SIZE];
    static uint32_t UnusedDmaData = 0;
    
    
    volatile bool dmaTransferComplete = false;
    volatile bool Mem0Complete = false;
    volatile bool Mem3Complete = false;
    
    static void InitDma(void)
    {
        DMA_initParam param = {0};
        param.channelSelect = DMA_CHANNEL_0;
        param.transferModeSelect = DMA_TRANSFER_REPEATED_SINGLE;    // Repeated block transfers
        param.transferSize = DMA_SIZE;                // Analyze block size
        param.triggerSourceSelect = DMA_TRIGGERSOURCE_26;           // ADC12 end of conversion
        param.transferUnitSelect = DMA_SIZE_SRCWORD_DSTWORD;        // 16 bits
        param.triggerTypeSelect = DMA_TRIGGER_RISINGEDGE;           // Rising edge of trigger
        DMA_init(&param);
    
        // Set data source, do not increment address
        DMA_setSrcAddress(DMA_CHANNEL_0, ADC12_B_getMemoryAddressForDMA(ADC12_B_BASE, ADC12_B_MEMORY_3), DMA_DIRECTION_UNCHANGED);
    
        // Set data destination, increment address
        DMA_setDstAddress(DMA_CHANNEL_0, (uint32_t)&dmaBuffer, DMA_DIRECTION_INCREMENT);
    
        //Enable DMA channel 0 interrupt
        DMA_clearInterrupt(DMA_CHANNEL_0);
        DMA_enableInterrupt(DMA_CHANNEL_0);
    
        //Enable transfers on DMA channel 0
        DMA_enableTransfers(DMA_CHANNEL_0);
    
    
        // Setup clearing the "stuck" ADC after manual sampling
        // We need to run a DMA cycle on any DMA channel triggered by a high level on the ADC DMA trigger line
        ADC12_B_disableConversions(ADC12_B_BASE, ADC12_B_PREEMPTCONVERSION);
    
        param.channelSelect = DMA_CHANNEL_1;                        // Unused DMA Channel
        param.transferModeSelect = DMA_TRANSFER_SINGLE;             // One time transfer
        param.transferSize = 3;                // Analyze block size
        param.triggerSourceSelect = DMA_TRIGGERSOURCE_26;           // ADC12 end of conversion
        param.transferUnitSelect = DMA_SIZE_SRCWORD_DSTWORD;        // 16 bits
        param.triggerTypeSelect = DMA_TRIGGER_HIGH;                 // Trigger on high level
        DMA_init(&param);
    
        // Set data source, do not increment address
        DMA_setSrcAddress(DMA_CHANNEL_1, ADC12_B_getMemoryAddressForDMA(ADC12_B_BASE, ADC12_B_MEMORY_3), DMA_DIRECTION_UNCHANGED);
    
        // Set data destination, increment address
        DMA_setDstAddress(DMA_CHANNEL_1, (uint32_t)&UnusedDmaData, DMA_DIRECTION_UNCHANGED);
    }
    
    static void InitAdc(void)
    {
        //Initialize the ADC12B Module
        ADC12_B_initParam initParam = {0};
        initParam.sampleHoldSignalSourceSelect = ADC12_B_SAMPLEHOLDSOURCE_7;
    
    #ifdef USE_SMCLK
        initParam.clockSourceSelect = ADC12_B_CLOCKSOURCE_SMCLK; // Update to test if ADC behavior changes with SMCLK
    #else
        initParam.clockSourceSelect = ADC12_B_CLOCKSOURCE_ADC12OSC;
    #endif
    
    
        initParam.clockSourceDivider = ADC12_B_CLOCKDIVIDER_1;
        initParam.clockSourcePredivider = ADC12_B_CLOCKPREDIVIDER__1;
        initParam.internalChannelMap = ADC12_B_NOINTCH;                         // No internal channel
        ADC12_B_init(ADC12_B_BASE, &initParam);
        //ADC12_B_setAdcPowerMode(ADC12_B_BASE, ADC12_B_LOWPOWERMODE);
        ADC12_B_setupSamplingTimer(ADC12_B_BASE, ADC12_B_CYCLEHOLD_4_CYCLES, ADC12_B_CYCLEHOLD_4_CYCLES, ADC12_B_MULTIPLESAMPLESDISABLE);
    
        //Enable the ADC12B module
        ADC12_B_enable(ADC12_B_BASE);
    }
    
    
    static void ClearStuckAdcDma(void)
    {
        //e2e.ti.com/.../1429066
        //Enable DMA channel 1 interrupt
    #ifdef ENABLE_STUCK_DMA_HANDLING
        DMA_clearInterrupt(DMA_CHANNEL_1);
        DMA_enableInterrupt(DMA_CHANNEL_1);
    
        //Enable transfers on DMA channel 1
        DMA_enableTransfers(DMA_CHANNEL_1);
        DMA_disableTransfers(DMA_CHANNEL_1);
    
    #endif
    
    //    Other MSP processors have errata for DMA 14, which applies when the dma trigger runs on a diffrent clock
    //    than the dma.   To recover, clear dma trigger source interrupt, re-initialize dma
    //    and the module for the trigger source.
    #ifdef ENABLE_DMA_14_WORKAROUND
        ADC12_B_disable(ADC12_B_BASE);
        DMA_disableInterrupt(DMA_CHANNEL_0);
        DMA_clearInterrupt(DMA_CHANNEL_0);
    
        Timer_A_clear(TA4_BASE);
    
        InitAdc();
        InitTimer();
        InitDma();
    #endif
    
    }
    
    void StartDmaBasedAdc(void)
    {
        ADC12_B_disableInterrupt(ADC12_B_BASE, 0xFF, 0, 0);
        ADC12_B_disable(ADC12_B_BASE);
        DMA_disableTransfers(DMA_CHANNEL_0);
        ClearStuckAdcDma();
    
        DMA_clearInterrupt(DMA_CHANNEL_0);
        DMA_enableInterrupt(DMA_CHANNEL_0);
        DMA_enableTransfers(DMA_CHANNEL_0);
    
    }
    
    void StarTriggeredAdc(void)
    {
        ADC12_B_disableConversions(ADC12_B_BASE, ADC12_B_PREEMPTCONVERSION);
        DMA_disableTransfers(DMA_CHANNEL_0);   // Disable Filter DMA
    }
    
    void StartAdcConversion(uint16_t MemorySlot, uint16_t InputChannel, uint8_t ConvType, uint16_t Trigger )
    {
        // Make sure adc is not busy
        while(ADC12_B_isBusy())
        {
            ;
        }
    
        ADC12_B_configureMemoryParam configureMemoryParam = {0};
        configureMemoryParam.memoryBufferControlIndex = MemorySlot;
        configureMemoryParam.inputSourceSelect = InputChannel;
        configureMemoryParam.refVoltageSourceSelect = ADC12_B_VREFPOS_AVCC_VREFNEG_VSS;
        configureMemoryParam.endOfSequence = ADC12_B_NOTENDOFSEQUENCE;
        configureMemoryParam.windowComparatorSelect = ADC12_B_WINDOW_COMPARATOR_DISABLE;
        configureMemoryParam.differentialModeSelect = ADC12_B_DIFFERENTIAL_MODE_DISABLE;
        ADC12_B_disableConversions(ADC12_B_BASE, ADC12_B_PREEMPTCONVERSION);
        ADC12_B_configureMemory(ADC12_B_BASE, &configureMemoryParam);
        ADC12_B_clearInterrupt(ADC12_B_BASE, 0, 0xFF);          // 0 for MEM0-MEM15
    
    
        if(Trigger == ADC12_B_SAMPLEHOLDSOURCE_SC)
        {
            DMA_disableTransfers(DMA_CHANNEL_0);
            ADC12_B_enableInterrupt(ADC12_B_BASE, (ADC12_B_IE0 | ADC12_B_IE3), 0, 0);
        }
        else
        {
            ADC12_B_disableInterrupt(ADC12_B_BASE, 0xFF, 0, 0);
            ClearStuckAdcDma();
            DMA_clearInterrupt(DMA_CHANNEL_0);
            DMA_enableInterrupt(DMA_CHANNEL_0);
            DMA_enableTransfers(DMA_CHANNEL_0);
        }
    
        // Start conversion
        ADC12_B_enable(ADC12_B_BASE);
        ADC12_B_startConversion(ADC12_B_BASE, MemorySlot >> 1, ConvType, Trigger);
    
    }
    void InitTimer(void)
    {
            // Setup Filter timer (A4)
        Timer_A_initUpModeParam uParam;
        uint16_t period;
    
    #ifdef USE_SMCLK
        uParam.clockSource = TIMER_A_CLOCKSOURCE_SMCLK;
        uParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_64; // 16Mhz/64 250 kHz
        period  = 11445;
    #else
        uParam.clockSource = TIMER_A_CLOCKSOURCE_ACLK;
        uParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1;
        period  = 1500;
    #endif
    
        uParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1;
        uParam.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;
        uParam.timerClear = TIMER_A_DO_CLEAR;
        uParam.startTimer = true;
        uParam.timerPeriod = 0;     // Will be set in Filter init routine
        uParam.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CCIE_CCR0_INTERRUPT_DISABLE;
    
    
        Timer_A_initUpMode(TA4_BASE, &uParam);
        Timer_A_setOutputMode(TA4_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_1, TIMER_A_OUTPUTMODE_SET_RESET);
    
    
        Timer_A_updatePeriod(TA4_BASE, period);
        Timer_A_setCompareValue(TA4_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_1, period >> 1);
    
    
    }
    
    
    volatile uint32_t AllConversionComplete = 0;
    int main()
    {
      // Stop watchdog timer to prevent time out reset
      WDTCTL = WDTPW + WDTHOLD;
    
      boardInit();
    
      // Init ADC Pins
      GPIO_InitPin(&AdcPin[0].Pin, &AdcPin[0].Init);
      GPIO_InitPin(&AdcPin[1].Pin, &AdcPin[1].Init);
       __enable_interrupt();
    
      InitAdc();
      InitTimer();
    
      InitDma();
      while (1 )
      {
          // // Section A Kick off DMA Conversions
          StartDmaBasedAdc();
          StartAdcConversion(ADC12_B_MEMORY_3 , ADC12_B_INPUT_A1, ADC12_B_REPEATED_SINGLECHANNEL, ADC12_B_SAMPLEHOLDSOURCE_7);
           // Wait for dma to complete
           while( !dmaTransferComplete) { ; }
    
          // Section B Switch to use Single Channel conversion, Memory 0 Channel 0
          StarTriggeredAdc();
          StartAdcConversion(ADC12_B_MEMORY_0 , ADC12_B_INPUT_A0, ADC12_B_SINGLECHANNEL, ADC12_B_SAMPLEHOLDSOURCE_SC);
          while( !Mem0Complete ) { ; }
    
          // Section C Switch to use Single Channel conversion, Memory 3 Channel 1
          StartAdcConversion(ADC12_B_MEMORY_3 , ADC12_B_INPUT_A1, ADC12_B_SINGLECHANNEL, ADC12_B_SAMPLEHOLDSOURCE_SC);
          while( !Mem3Complete ) { ; }
    
          // All conversions completed successfully, reset the tracking variables and loop to repeat the conversions.
          AllConversionComplete++;
          dmaTransferComplete = false;
          Mem0Complete = false;
          Mem3Complete = false;
    
      }
    
      return 0;
    }
    
    // DMA interrupt service routine
    #pragma vector=DMA_VECTOR
    static __interrupt void DMA_ISR(void)
    {
        switch(__even_in_range(DMAIV, 16))
        {
        case 0: break;
        case 2:             //DMA0IFG = DMA Channel 0 - Filter
            dmaTransferComplete = true;
            break;
        case 4: break;      //DMA1IFG = DMA Channel 1
        case 6: break;      //DMA2IFG = DMA Channel 2
        case 8: break;      //DMA3IFG = DMA Channel 3
        case 10: break;     //DMA4IFG = DMA Channel 4
        case 12: break;     //DMA5IFG = DMA Channel 5
        case 14: break;     //DMA6IFG = DMA Channel 6
        case 16: break;     //DMA7IFG = DMA Channel 7
        default:
            assert(false);
            break;
        }
    }
    
    //ADC interrupt service routine
    //__attribute__((section("RAMCODE"), noinline))     // Use this convention for functions
    uint16_t AdcRes;
    
    #pragma vector=ADC12_B_VECTOR
    static __interrupt void ADC12_B_ISR(void)
    {
        switch (__even_in_range(ADC12IV, 0x4C))
        {
        case ADC12IV__NONE: break;          // No interrupt
        case ADC12IV__ADC12TOVIFG:          // Conversion started before previous conversion completed
        case ADC12IV__ADC12OVIFG:           // Data reg written before last result read
        case ADC12IV__ADC12HIIFG:           // Hi Threshold interrupt
        case ADC12IV__ADC12LOIFG:           // Low Threshold interrupt
        case ADC12IV__ADC12INIFG:           // Within Thresholds interrupt
            assert(false);
            break;
        case ADC12IV__ADC12IFG0:            // Conversion done on MEM0 (ECG during streaming)
            AdcRes = ADC12_B_getResults(ADC12_B_BASE, ADC12_B_MEMORY_0);
            Mem0Complete = true;
            break;
        case ADC12IV__ADC12IFG3:            // Conversion done on MEM3 (FILTER) - Runs when streaming (ECG or LI)
            // Get ADC result and add it to the storage buffer
            AdcRes = ADC12_B_getResults(ADC12_B_BASE, ADC12_B_MEMORY_3);
            Mem3Complete = true;
            break;
        // Goes up to IFG31
        default:
            break;
        }
    
    }
    
    
    

  • Jay,

    so far TI can not guarantee that the workaround always works without knowing all the details but based on your testing you can decide to do so.

    Some more questions.

    1. Once you let the DMA trigger ADC conversion intially it works right?

    2. Then you swtich to SW controlled DMA transfers and this still works?

    3. Afterwards you want to switch back to DAM triggered ADC transfers and here it goes stuck correct?


    --> so how the ADC and DMA channel is stopped for point #1 and #2? Are you sure that you did not trigger another ADC conversion and point #3 while a previous ADC conversion in ongoing? In such case it might fall into ADC42 ERRATA.

    Also a more general question did you consider all existing ERRATAs in your current SW?

  • Jay,

    one more thing is this behavior constant over temperature?

    Also can you please clear all ADC interrupt flags and all DMA flags before you re-trigger the DMA triggered ADC conversions?

  • 1.  Yes.  The initial DMA conversion completes.

    2. The software controlled ADC conversions complete.  These are not using DMA, but the software reads from the ADC memory.

    3. Yes.

    "so how the ADC and DMA channel is stopped for point #1 and #2? Are you sure that you did not trigger another ADC conversion and point #3 while a previous ADC conversion in ongoing? In such case it might fall into ADC42 ERRATA."

    I do not believe this to be caused by ADC 42 for two reasons.

    1. I do not believe the conditions to trigger ADC42 are present.  The software conversions check for isbusy prior to starting a conversion.  The DMA is stopped before starting a software conversion.

    2. The work-around to recover from ADC 42 is to Disable ADC module and Re-enable the module.  This method does not recover, as the DMA is still stuck.

    Also a more general question did you consider all existing ERRATAs in your current SW?

    I scrubbed the errata document and did not find any existing errata that would cause this behavior.

  • "one more thing is this behavior constant over temperature?"

    I am not sure if there is a temperature effect, but the issue reproduces at room temperature.

    Also can you please clear all ADC interrupt flags and all DMA flags before you re-trigger the DMA triggered ADC conversions?

    The software is clearing all adc interrupts when switching to dma. 

    It also disables dma transfers , clears dma interrupts, enables dma interrupts, then enables dma transfers.

    Here is the code used to switch back to using dma.

    void StartDmaBasedAdc(void)
    {
        ADC12_B_disableInterrupt(ADC12_B_BASE, 0xFF, 0xFF, 0xFF);
        ADC12_B_disable(ADC12_B_BASE);
        DMA_disableTransfers(DMA_CHANNEL_0);
    
        // Make sure the ADC is not sampling to prevent ADC42
        __delay_cycles(1000);
    
        ClearStuckAdcDma();
    
        DMA_clearInterrupt(DMA_CHANNEL_0);
        DMA_enableInterrupt(DMA_CHANNEL_0);
        DMA_enableTransfers(DMA_CHANNEL_0);
    
    }
    

    d DMA flags are being cleared.  

**Attention** This is a public forum