Allows Asynchronous reading from the analog pins to memory. Supports: K64F
asyncADC.cpp
- Committer:
- Condo2k4
- Date:
- 2017-05-03
- Revision:
- 0:f0c00f67fba1
- Child:
- 1:57441202d8d0
File content as of revision 0:f0c00f67fba1:
#include "asyncADC.h" #include "pinmap.h" #include "PeripheralPins.h" #include "PeripheralNames.h" #include "Terminal.h" #define MAX_MOD (0xFFFF) static bool _asyncReadActive = false; static edma_handle_t _handle; static edma_tcd_t _tcd; static asyncISR _callback = NULL; static uint32_t _bufSize; // MATH SUPPORT FUNCTIONS float f_abs(float f) { return f>=0.0f ? f : -f; } int i_abs(int i) { return i>=0 ? i : -i; } bool isPowerOfTwo(unsigned int x) { return (x != 0) && ((x & (x - 1)) == 0); } int log2(unsigned int i) { int l = 0; while(i>>=1) l++; return l; } // CLOCK SUPPORT FUNTIONS void calcClockDiv(const uint32_t targetFrequency, int ÷r, bool &useMult, int &modulus) { uint32_t busClock = CLOCK_GetFreq(kCLOCK_BusClk); if(targetFrequency > busClock) { useMult = false; divider = 0; modulus = 0; } useMult = false; divider = 0; modulus = (((busClock<<1)/targetFrequency+1)>>1)-1; // round(clock/freq)-1 int bestErr = i_abs(targetFrequency - busClock/(modulus+1)); for(int i=1; i<=7; i++) { int mod = (((busClock>>(i-1))/targetFrequency+1)>>1)-1; if(mod<0) mod=0; else if(mod > MAX_MOD) mod = MAX_MOD; int err = i_abs(targetFrequency - busClock/((1<<i) * (mod+1))); if(err<bestErr || (err==bestErr && mod<modulus)) { useMult = false; divider = i; modulus = mod; } mod = (((busClock>>(i-1))/(targetFrequency*5)+1)>>1)-1; if(mod<0) mod=0; else if(mod > MAX_MOD) mod = MAX_MOD; err = i_abs(targetFrequency - busClock/((1<<i) * (mod+1) * 5)); if(err<bestErr || (err==bestErr && mod<modulus)) { useMult = true; divider = i; modulus = mod; } } for(int i=8; i<=11; i++) { int mod = (((busClock>>(i-1))/(targetFrequency*5)+1)>>1)-1; if(mod<0) mod=0; else if(mod > MAX_MOD) mod = MAX_MOD; int err = i_abs(targetFrequency - busClock/((1<<i) * (mod+1) * 5)); if(err<bestErr || (err==bestErr && mod<modulus)) { useMult = true; divider = i; modulus = mod; } } if(modulus > MAX_MOD) modulus = MAX_MOD; } void calcClockDiv(const float targetFrequency, int ÷r, bool &useMult, int &modulus) { uint32_t busClock = CLOCK_GetFreq(kCLOCK_BusClk); if(targetFrequency > busClock) { useMult = false; divider = 0; modulus = 0; return; } useMult = false; divider = 0; modulus = (int)((busClock/targetFrequency)+0.5f); // round(clock/freq) float bestErr = f_abs(targetFrequency - busClock/(modulus+1)); for(int i=1; i<=7; i++) { int mod = (int)(((busClock>>i)/targetFrequency)+0.5f); if(mod<0) mod=0; else if(mod > MAX_MOD) mod = MAX_MOD; float err = f_abs(targetFrequency - busClock/(float)((1<<i) * (mod+1))); if(err<bestErr || (err==bestErr && mod<modulus)) { useMult = false; divider = i; modulus = mod; } mod = (int)(((busClock>>i)/(targetFrequency*5.0f))+0.5f); if(mod<0) mod=0; else if(mod > MAX_MOD) mod = MAX_MOD; err = f_abs(targetFrequency - busClock/(float)((1<<i) * (mod+1) * 5)); if(err<bestErr || (err==bestErr && mod<modulus)) { useMult = true; divider = i; modulus = mod; } } for(int i=8; i<=11; i++) { int mod = (int)(((busClock>>i)/(targetFrequency*5.0f))+0.5f); if(mod<0) mod=0; else if(mod > MAX_MOD) mod = MAX_MOD; float err = f_abs(targetFrequency - busClock/(float)((1<<i) * (mod+1) * 5)); if(err<bestErr || (err==bestErr && mod<modulus)) { useMult = true; divider = i; modulus = mod; } } } // INTERRUPT SERVICE ROUTINES void singleISR(edma_handle_t *_handle, void *userData, bool transferDone, uint32_t tcds) { PDB_Enable(PDB0, false); _asyncReadActive = false; if(_callback!=NULL) _callback(0, _bufSize); } void rollingISR(edma_handle_t *_handle, void *userData, bool transferDone, uint32_t tcds) { if(_callback!=NULL) { if(_handle->base->TCD[_handle->channel].CSR & DMA_CSR_DONE_MASK) _callback(_bufSize>>1, _bufSize-1); else _callback(0, (_bufSize>>1)-1); } } // INIT METHODS void prepareADC(uint8_t adcIndex, uint8_t channelGroup, uint8_t channel) { ADC_Type* adc; if(adcIndex) { SIM->SCGC3 |= SIM_SCGC3_ADC1_MASK; adc = ADC1; } else { SIM->SCGC6 |= SIM_SCGC6_ADC0_MASK; adc = ADC0; } adc16_config_t adc16ConfigStruct; ADC16_GetDefaultConfig(&adc16ConfigStruct); ADC16_Init(adc, &adc16ConfigStruct); ADC16_EnableHardwareTrigger(adc, true); ADC16_DoAutoCalibration(adc); ADC16_EnableHardwareTrigger(adc, true); ADC16_EnableDMA(adc, true); adc16_channel_config_t channelConfig; channelConfig.channelNumber = channel; channelConfig.enableInterruptOnConversionCompleted = false; channelConfig.enableDifferentialConversion = false; ADC16_SetChannelConfig(adc, channelGroup, &channelConfig); } status_t prepareDMA(uint16_t* destination, uint32_t destSize, int16_t dmaChannel, uint8_t adcIndex, uint8_t adcGroup, bool rollingMode) { if(dmaChannel<0) { //Find free DMA channel for(int i=0; i<16; i++) { if((DMA0->ERQ & (1U<<i)) == 0) { dmaChannel=i; break; } } if(dmaChannel<0) return kStatus_EDMA_Busy; } else { //check if requested DMA channel is free if((dmaChannel>15) || DMA0->ERQ & (1U<<dmaChannel)) return kStatus_EDMA_Busy; } /* Configure DMAMUX */ DMAMUX_Init(DMAMUX0); DMAMUX_SetSource(DMAMUX0, dmaChannel, 40+adcIndex); //Trigger 40 = ADC0, 41 = ADC1 DMAMUX_EnableChannel(DMAMUX0, dmaChannel); edma_config_t edma_config; /* Init the eDMA driver */ EDMA_GetDefaultConfig(&edma_config); EDMA_Init(DMA0, &edma_config); EDMA_CreateHandle(&_handle, DMA0, dmaChannel); EDMA_InstallTCDMemory(&_handle, NULL, 0); //make sure tcd memory pool is clear EDMA_SetCallback(&_handle, rollingMode ? rollingISR : singleISR, NULL); if(rollingMode) { EDMA_SetModulo(DMA0, dmaChannel, (edma_modulo_t)(log2(destSize)+1), kEDMA_ModuloDisable); _handle.base->TCD[dmaChannel].DLAST_SGA = -2*(int32_t)destSize; } edma_transfer_config_t transfer_config; EDMA_PrepareTransfer(&transfer_config, (void*)&((adcIndex?ADC1:ADC0)->R[adcGroup]), 2, destination, 2, 2, 2*destSize, kEDMA_PeripheralToMemory); status_t ret = EDMA_SubmitTransfer(&_handle, &transfer_config); if(rollingMode) EDMA_EnableAutoStopRequest(DMA0, dmaChannel, false); //must go after submit if(ret == kStatus_Success) { EDMA_EnableChannelInterrupts(DMA0, dmaChannel, rollingMode ? (kEDMA_MajorInterruptEnable|kEDMA_HalfInterruptEnable) : kEDMA_MajorInterruptEnable); EDMA_StartTransfer(&_handle); } return ret; } void preparePDB(int divider, bool useMult, int modulus, uint8_t adcIndex, uint8_t adcGroup) { SIM->SCGC6 |= SIM_SCGC6_PDB_MASK; pdb_config_t config; PDB_GetDefaultConfig(&config); if(useMult) { switch(divider) { case 1: config.dividerMultiplicationFactor = kPDB_DividerMultiplicationFactor10; divider = 0; break; case 2: config.dividerMultiplicationFactor = kPDB_DividerMultiplicationFactor20; divider = 0; break; default: config.dividerMultiplicationFactor = kPDB_DividerMultiplicationFactor40; divider-=3; break; } } else { config.dividerMultiplicationFactor = kPDB_DividerMultiplicationFactor1; } switch(divider) { case 0: config.prescalerDivider = kPDB_PrescalerDivider1; break; case 1: config.prescalerDivider = kPDB_PrescalerDivider2; break; case 2: config.prescalerDivider = kPDB_PrescalerDivider4; break; case 3: config.prescalerDivider = kPDB_PrescalerDivider8; break; case 4: config.prescalerDivider = kPDB_PrescalerDivider16; break; case 5: config.prescalerDivider = kPDB_PrescalerDivider32; break; case 6: config.prescalerDivider = kPDB_PrescalerDivider64; break; default: config.prescalerDivider = kPDB_PrescalerDivider128; break; } config.loadValueMode = kPDB_LoadValueImmediately; config.triggerInputSource = kPDB_TriggerSoftware; config.enableContinuousMode = true; PDB_Init(PDB0, &config); PDB_EnableDMA(PDB0, false); //set if PDB signals DMA directly instead of generating hardware interrupt PDB_DisableInterrupts(PDB0, kPDB_SequenceErrorInterruptEnable); //signal errors with interrupt? PDB_EnableInterrupts(PDB0, kPDB_DelayInterruptEnable); //send interrupt after delay? pdb_adc_pretrigger_config_t adcConfig; adcConfig.enablePreTriggerMask = 1; //enable pre-trigger 0 adcConfig.enableOutputMask = 1; //enable output for pre-trigger 0 adcConfig.enableBackToBackOperationMask = 0; //disable back-to-back for all pre-triggers PDB_SetADCPreTriggerConfig(PDB0, adcIndex, &adcConfig); PDB_SetADCPreTriggerDelayValue(PDB0, 0, 0, 0); //set delay on ADC0->R[0] PDB_SetADCPreTriggerDelayValue(PDB0, 0, 1, 0); //set delay on ADC0->R[1] PDB_SetADCPreTriggerDelayValue(PDB0, 1, 0, 0); //set delay on ADC1->R[0] PDB_SetADCPreTriggerDelayValue(PDB0, 1, 1, 0); //set delay on ADC1->R[1] PDB_SetModulusValue(PDB0, modulus); //actual frequency = clock frequency / ((modulus+1) * prescale divider * divider multiplier) PDB_Enable(PDB0, true); //do at end before load PDB_DoLoadValues(PDB0); } // API uint32_t approximateFrequency(uint32_t targetFrequency) { int divider, modulus; bool useMult; calcClockDiv(targetFrequency, divider, useMult, modulus); return (CLOCK_GetFreq(kCLOCK_BusClk)/((1<<divider) * (modulus+1) * (useMult ? 10 : 2))+1)>>1; } float approximateFrequency(float targetFrequency) { int divider, modulus; bool useMult; calcClockDiv(targetFrequency, divider, useMult, modulus); return CLOCK_GetFreq(kCLOCK_BusClk)/(float)((1<<divider) * (modulus+1) * (useMult ? 5 : 1)); } async_error_t asyncAnalogToBuffer(PinName source, uint16_t* destination, uint32_t destSize, uint32_t sampleFrequency, asyncISR callback, int16_t dmaChannel) { if(_asyncReadActive) return E_ASYNC_ADC_ACTIVE; _asyncReadActive = true; if(destSize<=0) { return E_INVALID_BUFFER_SIZE; //Destination buffer size must be greater that 0 } uint32_t adc = pinmap_peripheral(source, PinMap_ADC); if(adc == (ADCName)NC) { error("Pin doese not support Analog to Digital Conversion"); } int adcIndex = adc >> ADC_INSTANCE_SHIFT, adcGroup = 0, adcChannel = adc & 0xF; prepareADC(adcIndex, adcGroup, adcChannel); switch(prepareDMA(destination, destSize, dmaChannel, adcIndex, adcGroup, false)) { case kStatus_EDMA_QueueFull: case kStatus_EDMA_Busy: return E_DMA_IN_USE; default: break; } int divider, modulus; bool useMult; calcClockDiv(sampleFrequency, divider, useMult, modulus); preparePDB(divider, useMult, modulus, adcIndex, adcGroup); _bufSize = destSize; _callback = callback; PDB_DoSoftwareTrigger(PDB0); return SUCCESS; } async_error_t asyncAnalogToCircularBuffer(PinName source, uint16_t* destination, uint32_t destSize, uint32_t sampleFrequency, asyncISR callback, int16_t dmaChannel) { if(_asyncReadActive) return E_ASYNC_ADC_ACTIVE; _asyncReadActive = true; if(destSize<=0 && !isPowerOfTwo(destSize)) { return E_INVALID_BUFFER_SIZE; //Destination buffer size must be greater that 0 and a power of 2 } uint32_t adc = pinmap_peripheral(source, PinMap_ADC); if(adc == (ADCName)NC) { error("Pin doese not support Analog to Digital Conversion"); } int adcIndex = adc >> ADC_INSTANCE_SHIFT, adcGroup = 0, adcChannel = adc & 0xF; prepareADC(adcIndex, adcGroup, adcChannel); switch(prepareDMA(destination, destSize, dmaChannel, adcIndex, adcGroup, true)) { case kStatus_EDMA_QueueFull: case kStatus_EDMA_Busy: return E_DMA_IN_USE; default: break; } int divider, modulus; bool useMult; calcClockDiv(sampleFrequency, divider, useMult, modulus); preparePDB(divider, useMult, modulus, adcIndex, adcGroup); _bufSize = destSize; _callback = callback; PDB_DoSoftwareTrigger(PDB0); return SUCCESS; } async_error_t asyncAnalogToBuffer_f(PinName source, uint16_t* destination, uint32_t destSize, float sampleFrequency, asyncISR callback, int16_t dmaChannel) { if(_asyncReadActive) return E_ASYNC_ADC_ACTIVE; _asyncReadActive = true; if(destSize<=0) { return E_INVALID_BUFFER_SIZE; //Destination buffer size must be greater that 0 } uint32_t adc = pinmap_peripheral(source, PinMap_ADC); if(adc == (ADCName)NC) { error("Pin doese not support Analog to Digital Conversion"); } int adcIndex = adc >> ADC_INSTANCE_SHIFT, adcGroup = 0, adcChannel = adc & 0xF; prepareADC(adcIndex, adcGroup, adcChannel); switch(prepareDMA(destination, destSize, dmaChannel, adcIndex, adcGroup, false)) { case kStatus_EDMA_QueueFull: case kStatus_EDMA_Busy: return E_DMA_IN_USE; default: break; } int divider, modulus; bool useMult; calcClockDiv(sampleFrequency, divider, useMult, modulus); preparePDB(divider, useMult, modulus, adcIndex, adcGroup); _bufSize = destSize; _callback = callback; PDB_DoSoftwareTrigger(PDB0); return SUCCESS; } async_error_t asyncAnalogToCircularBuffer_f(PinName source, uint16_t* destination, uint32_t destSize, float sampleFrequency, asyncISR callback, int16_t dmaChannel) { if(_asyncReadActive) return E_ASYNC_ADC_ACTIVE; _asyncReadActive = true; if(destSize<=0 && !isPowerOfTwo(destSize)) { return E_INVALID_BUFFER_SIZE; //Destination buffer size must be greater that 0 and a power of 2 } uint32_t adc = pinmap_peripheral(source, PinMap_ADC); if(adc == (ADCName)NC) { error("Pin doese not support Analog to Digital Conversion"); } int adcIndex = adc >> ADC_INSTANCE_SHIFT, adcGroup = 0, adcChannel = adc & 0xF; prepareADC(adcIndex, adcGroup, adcChannel); switch(prepareDMA(destination, destSize, dmaChannel, adcIndex, adcGroup, true)) { case kStatus_EDMA_QueueFull: case kStatus_EDMA_Busy: return E_DMA_IN_USE; default: break; } int divider, modulus; bool useMult; calcClockDiv(sampleFrequency, divider, useMult, modulus); preparePDB(divider, useMult, modulus, adcIndex, adcGroup); _bufSize = destSize; _callback = callback; PDB_DoSoftwareTrigger(PDB0); return SUCCESS; } void terminateAsyncRead() { PDB_Enable(PDB0, false); EDMA_AbortTransfer(&_handle); _asyncReadActive = false; }