Allows Asynchronous reading from the analog pins to memory. Supports: K64F
Diff: asyncADC.cpp
- Revision:
- 0:f0c00f67fba1
- Child:
- 1:57441202d8d0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/asyncADC.cpp Wed May 03 12:27:37 2017 +0000 @@ -0,0 +1,499 @@ +#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; +} +