/******************************************************************************* MODULE : Adc.c Starting with JADE ADC is configured to run in EM2 without any CPU intervention. LETIMER0 is used to trigger ADC via PRS and LDMA transfers results into adcBuffer. LETIMER0 runs at 1kHz. So every 1ms new ADC samples are transferd to the buffer. Based on github adc_scan_letimer_prs_dma sample ToDo: Check aquisition time, accuracy and influence from neighbor channels 20110530 AB Initial 20150407 AB Adapted for USV_BatteryMonitor Slave, remove LEDs and Testpins 20191212 AB JADE EFM32JG1B test with lot of channels *******************************************************************************/ // --- Includes --------------------------------------------------------------- #define LOGGING_TOKEN ADC_LOGGING #include "TraceLeUart.h" #include "em_device.h" #include #include #include //#include #include #include #include "em_letimer.h" #include "em_ldma.h" #include "em_prs.h" #include "types.h" #include "Adc.h" #include "Leds.h" // --- Pin Definitions -------------------------------------------------------- // Testpin PF6, pinX (for search in project - Testpin_on) #define PORT gpioPortF #define PORT_BIT 6 #define TESTPIN_INIT() { GPIO_PinModeSet(PORT, PORT_BIT, gpioModePushPull, 0);} #define TESTPIN( x ) { if ( x ) GPIO->P[PORT].DOUT |= ( 1 << PORT_BIT ); \ else GPIO->P[PORT].DOUT &= ~( 1 << PORT_BIT ); } // --- Defines ---------------------------------------------------------------- #define ADC_BUFFER_SIZE 14 // Change this to set number of samples per interrupt // AB usually same as number ob ADC channels #define ADC_DVL 1 // Change this to set how many samples get sent at once // AB means sample sets, 1 = one time ADC_BUFFER_SIZE channels // AB and then sent via DMA to BUFFER #define ADC_FREQ 1000000 // 16000000 is max ADC clock for Series 1 #define letimerDesired 1000 // Desired letimer interrupt frequency (in Hz) // AB max: 2300 with channels=14, FREQ=1MHz, AquTim=4 // AB max: ~490 with channels=14, FREQ=1MHz, AquTim=64 // AB max: ~1600 with channels=14, FREQ=1MHz, AquTim=32 // AB --> SM-C channels=10, ok #define ADC_MV( x ) ( ((adcBuffer[ x ] + 1) * 1250 << 8) / ( 4095 << 8 ) ) // 32 bit needed #define LDMA_CHANNEL 0 #define PRS_CHANNEL 0 LDMA_TransferCfg_t trans; LDMA_Descriptor_t descr; // --- Typedefs --------------------------------------------------------------- // --- PUBLIC Variables ------------------------------------------------------- // --- Variables -------------------------------------------------------------- static volatile BOOL bAdcIsRunning; static int iAdc[16]; // filtered values static BOOL bAdcContinousTrace; uint32_t adcBuffer[ADC_BUFFER_SIZE]; uint32_t topValue; // --- Macros ----------------------------------------------------------------- // --- Functionprototypes ----------------------------------------------------- void AdcTrace (int iLevel); ; // --- Code ------------------------------------------------------------------- /**************************************************************************//** * @brief LDMA Handler *****************************************************************************/ void LDMA_IRQHandler(void) { // Clear interrupt flag TESTPIN(1); LDMA_IntClear((1 << LDMA_CHANNEL) << _LDMA_IFC_DONE_SHIFT); TESTPIN(0); // check for DMA error EFM_ASSERT( (LDMA_IntGet() & 0x80000000) == 0); } /**************************************************************************//** * @brief LETIMER initialization *****************************************************************************/ void initLetimer(void) { // Start LFRCO and wait until it is stable CMU_OscillatorEnable(cmuOsc_LFRCO, true, true); // Enable clock to the interface of the low energy modules CMU_ClockEnable(cmuClock_HFLE, true); // Route the LFRCO clock to LFA (LETIMER0) CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFRCO); // Enable clock for LETIMER0 CMU_ClockEnable(cmuClock_LETIMER0, true); LETIMER_Init_TypeDef letimerInit = LETIMER_INIT_DEFAULT; // Reload COMP0 on underflow, pulse output, and run in repeat mode letimerInit.comp0Top = true; letimerInit.ufoa0 = letimerUFOAPulse; letimerInit.repMode = letimerRepeatFree; // Initialize LETIMER LETIMER_Init(LETIMER0, &letimerInit); // Need REP0 != 0 to pulse on underflow LETIMER_RepeatSet(LETIMER0, 0, 1); // calculate the topValue topValue = CMU_ClockFreqGet(cmuClock_LETIMER0) / letimerDesired; // Compare on wake-up interval count LETIMER_CompareSet(LETIMER0, 0, topValue); // Use LETIMER0 as async PRS to trigger ADC in EM2 CMU_ClockEnable(cmuClock_PRS, true); PRS_SourceAsyncSignalSet(PRS_CHANNEL, PRS_CH_CTRL_SOURCESEL_LETIMER0, PRS_CH_CTRL_SIGSEL_LETIMER0CH0); } /**************************************************************************//** * @brief LDMA initialization *****************************************************************************/ void initLdma(void) { // Enable LDMA clock CMU_ClockEnable(cmuClock_LDMA, true); // Basic LDMA configuration LDMA_Init_t ldmaInit = LDMA_INIT_DEFAULT; LDMA_Init(&ldmaInit); // Transfer triggers on ADC Scan conversion complete trans = (LDMA_TransferCfg_t)LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_ADC0_SCAN); descr = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_LINKREL_P2M_BYTE( &(ADC0->SCANDATA), // source adcBuffer, // destination ADC_BUFFER_SIZE, // data transfer size 0); // link relative offset (links to self) descr.xfer.blockSize = ADC_DVL-1; // transfers ADC_DVL number of units per arbitration cycle descr.xfer.ignoreSrec = true; // ignores single requests to reduce energy usage descr.xfer.size = ldmaCtrlSizeWord; // transfers words instead of bytes // Initialize LDMA transfer LDMA_StartTransfer(LDMA_CHANNEL, &trans, &descr); // Clear pending and enable interrupts for channel NVIC_ClearPendingIRQ(LDMA_IRQn); NVIC_SetPriority(LDMA_IRQn, 6); NVIC_EnableIRQ(LDMA_IRQn); } /****************************************************************************** void AdcInit (void) Initializes ADC module ******************************************************************************/ void AdcInit (void) { uint32_t i; TRACE("ADC: init"); TESTPIN_INIT(); // Declare init structs ADC_Init_TypeDef init = ADC_INIT_DEFAULT; ADC_InitScan_TypeDef initScan = ADC_INITSCAN_DEFAULT; // Enable ADC clock CMU_ClockEnable(cmuClock_ADC0, true); CMU_ClockEnable(cmuClock_HFPER, true); // Select AUXHFRCO for ADC ASYNC mode so it can run in EM2 CMU->ADCCTRL = CMU_ADCCTRL_ADC0CLKSEL_AUXHFRCO; // Set AUXHFRCO frequency and use it to setup the ADC CMU_AUXHFRCOFreqSet(cmuAUXHFRCOFreq_4M0Hz); init.timebase = ADC_TimebaseCalc(CMU_AUXHFRCOBandGet()); init.prescale = ADC_PrescaleCalc(ADC_FREQ, CMU_AUXHFRCOBandGet()); // Let the ADC enable its clock on demand in EM2 init.em2ClockConfig = adcEm2ClockOnDemand; // add ADC channels, sorted by groups and channels, this is the same order as they show // up in the destination ADC buffer // PORT to GROUP assignment is restricted by chip hardware, see reference manual and // check assertEFM() in ADC_ScanSingleEndedInputAdd() (debug builds only) ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3XCH8); /* PA0 */ // option ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3YCH9); /* PA1 */ // Ext. Driven on STK //option ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3XCH10); /* PA2 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3YCH11); /* PA3 */ // Ext. Driven on STK ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3XCH12); /* PA4 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3YCH13); /* PA5 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup1, adcPosSelAPORT3YCH1); /* PD9 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup1, adcPosSelAPORT3XCH2); /* PD10 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup1, adcPosSelAPORT3YCH3); /* PD11 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup2, adcPosSelAPORT1XCH6); /* PC6 */ // option ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup2, adcPosSelAPORT1YCH7); /* PC7 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1XCH8); /* PC8 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1YCH9); /* PC9 */ ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1XCH10); /* PC10 */ // Ext. Driven on STK // not possible, no more groups ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1XCH22); /* PF6 */ // option // not possible, no more groups ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1YCH21); /* PF5 */ // option // not possible, no more groups ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1YCH23); /* PF7 */ // option // Basic ADC scan configuration initScan.diff = false; // single-ended initScan.reference = adcRef1V25; // 1.25V reference initScan.resolution = adcRes12Bit; // 12-bit resolution initScan.acqTime = adcAcqTime32; // set acquisition time to meet minimum requirements // 64 to much for 14 channels and 1ms, use adcAcqTime32 // DMA is available in EM2 for processing SCANFIFO DVL request initScan.scanDmaEm2Wu = 1; // Enable PRS trigger and select channel 0 initScan.prsEnable = true; initScan.prsSel = (ADC_PRSSEL_TypeDef) PRS_CHANNEL; // Initialize ADC ADC_Init(ADC0, &init); ADC_InitScan(ADC0, &initScan); // Set scan data valid level (DVL) ADC0->SCANCTRLX = (ADC0->SCANCTRLX & ~_ADC_SCANCTRLX_DVL_MASK) | (((ADC_DVL - 1) << _ADC_SCANCTRLX_DVL_SHIFT) & _ADC_SCANCTRLX_DVL_MASK); // Clear the SCAN FIFO ADC0->SCANFIFOCLEAR = ADC_SCANFIFOCLEAR_SCANFIFOCLEAR; bAdcContinousTrace = ON; // fill buffers with known values, nice for debugging for (i = 0; i < (sizeof(adcBuffer) / sizeof(adcBuffer[0])); i++) { adcBuffer[i] = i; } for (i = 0; i < (sizeof(iAdc) / sizeof(iAdc[0])); i++) { iAdc[i] = i; } // Setup DMA to move ADC results to user memory initLdma(); // Set up LETIMER to trigger ADC via PRS in periodic intervals initLetimer(); } /****************************************************************************** void AdcTrace (int iLevel) Traces ADC values to debug out, iLevel defines verbosity 3 is continous logging ******************************************************************************/ void AdcTrace (int iLevel) { int i; bAdcContinousTrace = OFF; switch(iLevel) { case 0: TRACE("ADC:%6u%6u%6u%6u%6u%6u%6u%6u", adcBuffer[0], adcBuffer[1], adcBuffer[2], adcBuffer[3], \ adcBuffer[4], adcBuffer[5], adcBuffer[6], adcBuffer[7]); break; case 2: TRACE("ADC: PA0 PA1 PA2 PA3 PA4 PA5 PD9 PD10 PD11 PC6 PC7 PC8 PC9 PC10"); TRACE("ADC:%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u [mV]", ADC_MV(0), ADC_MV(1), ADC_MV(2), \ ADC_MV(3), ADC_MV(4), ADC_MV(5), ADC_MV(6), ADC_MV(7), ADC_MV(8), ADC_MV(9), ADC_MV(10),\ ADC_MV(11), ADC_MV(12), ADC_MV(13) ); break; case 4: TRACE("ADC:%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u [mV]", ADC_MV(0), ADC_MV(1), ADC_MV(2), \ ADC_MV(3), ADC_MV(4), ADC_MV(5), ADC_MV(6), ADC_MV(7), ADC_MV(8), ADC_MV(9), ADC_MV(10),\ ADC_MV(11), ADC_MV(12), ADC_MV(13) ); break; case 3: bAdcContinousTrace = ON; break; case 1: // fall through default: TESTPIN(1); TRACE("ADC: CH 0 CH 1 CH 2 CH 3 CH 4 CH 5 CH 6 CH 7 CH 8 CH 9 CH10 CH11 CH12 CH13"); TRACE(" %6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u", adcBuffer[0], adcBuffer[1], adcBuffer[2], \ adcBuffer[3], adcBuffer[4], adcBuffer[5], adcBuffer[6], adcBuffer[7], adcBuffer[8], \ adcBuffer[9], adcBuffer[10], adcBuffer[11], adcBuffer[12], adcBuffer[13]); // takes less than 3ms for 14 channels TESTPIN(0); // test conversion speed TESTPIN(1); for (i = 0; i < 14; i++) { iAdc[i] = ADC_MV(i); } // converting 14 channels to mV takes 27us TESTPIN(0); break; } return; } /****************************************************************************** void AdcTicker10ms (void) Periodically 10ms tasks ******************************************************************************/ void AdcTicker10ms (void) { return; } /****************************************************************************** void AdcTicker50ms (void) Periodically 50ms tasks ******************************************************************************/ void AdcTicker50ms (void) { return; } /****************************************************************************** void AdcTicker100ms (void) Periodically 100ms tasks ******************************************************************************/ void AdcTicker100ms (void) { return; } /****************************************************************************** void AdcTicker200ms (void) Periodically 200ms tasks ******************************************************************************/ void AdcTicker200ms (void) { if (bAdcContinousTrace) { AdcTrace(4); bAdcContinousTrace = TRUE; } return; } /****************************************************************************** void AdcTicker500ms (void) Periodically 500ms tasks ******************************************************************************/ void AdcTicker500ms (void) { return; } /****************************************************************************** void AdcTicker1s (void) Periodically 1s tasks ******************************************************************************/ void AdcTicker1s (void) { //TRACE("ADC: PA3%5umV", ADC_MV(3)); return; } /****************************************************************************** int AdcGet (int iChannel) Returns last conversion value for specified channel. Range 0..4095, full scale..1.25V (reference set to internal 1.25V) ******************************************************************************/ int AdcGet (int iChannel) { return adcBuffer[iChannel]; } /****************************************************************************** int AdcGetMv (int iChannel) Returns voltage on selected channel in mV Range 0..1250 ******************************************************************************/ int AdcGetMv (int iChannel) { return ADC_MV(iChannel); }