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

Dependents:   MoppyController

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 &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;
}