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(¶m);
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(¶m);
// 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(¶m);
// 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;
}
}
