Allows Asynchronous reading from the analog pins to memory. Supports: K64F

Dependents:   MoppyController

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 &divider, 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 &divider, 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;
+}
+