All mbed code for control over dive planes, pump motor, valve motor, BCUs, UART interface, etc.
Dependencies: mbed ESC mbed MODDMA
robotic_fish_6/AcousticControl/ToneDetector.cpp
- Committer:
- juansal12
- Date:
- 2020-01-14
- Revision:
- 0:c3a329a5b05d
File content as of revision 0:c3a329a5b05d:
/* * Author: Joseph DelPreto */ #include "ToneDetector.h" #ifdef acousticControl // The static instance ToneDetector toneDetector; #ifdef streamAcousticControlLog int32_t acousticControlLogToStream[5] = {0,0,0,0,0}; #endif //============================================ // Initialization //============================================ // Constructor ToneDetector::ToneDetector() : terminated(0), fillingBuffer0(false), transferComplete(false), readyToBegin(false), callbackFunction(0) #ifdef artificialSamplesMode , numTestSamples(0), testSampleIndex(0), sampleIndex(0) #endif { } // Set a callback function that should be called after each buffer is processed void ToneDetector::setCallback(void (*myFunction)(int32_t* tonePowers, uint32_t signalLevel)) { callbackFunction = myFunction; } // Initialize the Goertzel variables, arrays, etc. // sampleWindow, sampleInterval, and tones must have been previously set // if not, this method will return without doing anything void ToneDetector::init() { // Make sure sampleWindow, sampleInterval, and desired tones have been set if(sampleWindow == 0 || sampleInterval == 0 || numTones == 0) return; // Initialize sample buffers //printf("Sampling interval: %d us\n", sampleInterval); //printf("Buffer size: %d samples\n", sampleWindow); for(uint16_t i = 0; i < sampleWindow; i++) // not using memset since sizeof seems to not work with uint16_t? { sampleBuffer0[i] = 0; sampleBuffer1[i] = 0; } #if defined(recordSamples) && !defined(recordStreaming) savedSamplesIndex = 0; for(uint16_t i = 0; i < numSavedSamples; i++) // not using memset since sizeof seems to not work with uint16_t? savedSamples[i] = 0; #endif // Initialize goertzel arrays tonePowersWindowIndex = 0; for(int i = 0; i < numTones; i++) { goertzelCoefficients[i] = 0; for(int w = 0; w < tonePowersWindow; w++) tonePowers[i][w] = 0; tonePowersSum[i] = 0; } readyToThreshold = false; // Some convenience variables as doubles for precision (will cast to fixed point later) double sampleFrequency = 1000.0/((double)sampleInterval/1000.0); // Hz double N = (double) sampleWindow; // Calculate the coefficient for each tone //printf("Initializing Goertzel algorithm\n"); //printf(" Bin size: %f\n", sampleFrequency/N); for(int i = 0; i < numTones; i++) { // Determine K and then f for desired tone (f = desiredF/Fs) // tone/fs = f = K/N --> K = (int)(tone*N/fs) double tone = targetTones[i]; int k = (int) (0.5 + ((N * tone) / sampleFrequency)); float f = ((float)k) / N; //printf(" Desired tone %f -> %f", tone, f*sampleFrequency); float cosine = cos(2.0 * PI * f); // Store the result as a fixed-point number goertzelCoefficients[i] = toFixedPoint(2.0*cosine); //printf("\t (coefficient: %f)\n", toFloat(goertzelCoefficients[i])); } #if defined(recordOutput) && !defined(recordStreaming) savedTonePowersIndex = 0; for(uint16_t i = 0; i < numSavedTonePowers; i++) // not using memset since sizeof seems to not work with uint16_t? { for(uint8_t t = 0; t < numTones; t++) savedTonePowers[i][t] = 0; } #endif // We're ready to process some tunes! readyToBegin = true; } #ifndef artificialSamplesMode // Configure and start the DMA channels for ADC sample gathering // Will have a linked list of two DMA operations, one for each buffer void ToneDetector::startDMA() { // Create the Linked List Items lli[0] = new MODDMA_LLI; lli[1] = new MODDMA_LLI; // Prepare DMA configuration, which will be chained (one operation for each buffer) dmaConf = new MODDMA_Config; dmaConf ->channelNum ( MODDMA::Channel_0 ) ->srcMemAddr ( 0 ) ->dstMemAddr ( (uint32_t)sampleBuffer0 ) ->transferSize ( sampleWindow ) ->transferType ( MODDMA::p2m ) ->transferWidth ( MODDMA::word ) ->srcConn ( MODDMA::ADC ) ->dstConn ( 0 ) ->dmaLLI ( (uint32_t)lli[1] ) // Looks like it does the above setup and then calls this LLI - thus we have this setup mimic lli[0] and then the chain actually starts by calling lli[1] ->attach_tc ( &TC0_callback ) ->attach_err ( &ERR0_callback ) ; // Create LLI to transfer from ADC to adcBuffer0 (and then launch lli[1]) lli[0]->SrcAddr = (uint32_t)dma.LUTPerAddr(dmaConf->srcConn()); lli[0]->DstAddr = (uint32_t)sampleBuffer0; lli[0]->NextLLI = (uint32_t) lli[1]; lli[0]->Control = dma.CxControl_TransferSize(dmaConf->transferSize()) | dma.CxControl_SBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn())) | dma.CxControl_DBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn())) | dma.CxControl_SWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn())) | dma.CxControl_DWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn())) | dma.CxControl_DI() | dma.CxControl_I(); // Create LLI to transfer from ADC to adcBuffer1 (and then launch lli[0] to repeat) lli[1]->SrcAddr = (uint32_t)dma.LUTPerAddr(dmaConf->srcConn()); lli[1]->DstAddr = (uint32_t)sampleBuffer1; lli[1]->NextLLI = (uint32_t) lli[0]; lli[1]->Control = dma.CxControl_TransferSize(dmaConf->transferSize()) | dma.CxControl_SBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn())) | dma.CxControl_DBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn())) | dma.CxControl_SWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn())) | dma.CxControl_DWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn())) | dma.CxControl_DI() | dma.CxControl_I(); // Start the DMA chain fillingBuffer0 = true; transferComplete = false; if (!dma.Prepare(dmaConf)) { error("Doh! Error preparing initial dma configuration"); } } // Configure the ADC to trigger on Timer1 // Start the timer with the desired sampling interval void ToneDetector::startADC() { // We use the ADC irq to trigger DMA and the manual says // that in this case the NVIC for ADC must be disabled. NVIC_DisableIRQ(ADC_IRQn); // Power up the ADC and set PCLK LPC_SC->PCONP |= (1UL << 12); // enable power LPC_SC->PCLKSEL0 &= ~(3UL << 24); // Clear divider to use CCLK/8 = 12MHz directly (see page 57) // original example code comment: PCLK = CCLK/4 96M/4 = 24MHz // Enable the ADC, 12MHz, ADC0.0 LPC_ADC->ADCR = (1UL << 21); LPC_ADC->ADCR &= ~(255 << 8); // No clock divider (use the 12MHz directly) // Set the pin functions to ADC (use pin p15) LPC_PINCON->PINSEL1 &= ~(3UL << 14); /* P0.23, Mbed p15. */ LPC_PINCON->PINSEL1 |= (1UL << 14); // Enable ADC irq flag (to DMA). // Note, don't set the individual flags, // just set the global flag. LPC_ADC->ADINTEN = 0x100; // (see page 586 of http://www.nxp.com/documents/user_manual/UM10360.pdf) // Disable burst mode LPC_ADC->ADCR &= ~(1 << 16); // Have the ADC convert based on timer 1 LPC_ADC->ADCR |= (6 << 24); // Trigger on MAT1.0 LPC_ADC->ADCR |= (1 << 27); // Falling edge // Set up timer 1 LPC_SC->PCONP |= (1UL << 2); // Power on Timer1 LPC_SC->PCLKSEL0 &= ~(3 << 4); // No clock divider (use 12MHz directly) (see page 57 of datasheet) LPC_TIM1->PR = 11; // TC clocks at 1MHz since we selected 12MHz above (see page 507 of datasheet) LPC_TIM1->MR0 = sampleInterval-1; // sampling interval in us LPC_TIM1->MCR = 3; // Reset TCR to zero on match LPC_TIM1->EMR = (3UL<<4)|1; // Make MAT1.0 toggle. //NVIC_EnableIRQ(TIMER1_IRQn); // Enable timer1 interrupt NOTE: enabling the interrupt when MCR is 3 will make everything stop working. enabling the interrupt when MCR is 2 will work but the interrupt isn't actually called. // Start the timer (which thus starts the ADC) LPC_TIM1->TCR=0; LPC_TIM1->TCR=1; } #endif // end if(not artificial samples mode) //============================================ // Execution Control //============================================ // Start acquiring and processing samples // Will run forever or until stop() is called void ToneDetector::run() { if(!readyToBegin) return; terminated = false; //printf("\nTone detector starting...\n"); #ifdef recordStreaming #ifdef recordSamples printf("\tSample (V)"); printf("\n"); #endif #ifdef recordOutput for(uint8_t t = 0; t < numTones; t++) printf("\t%f Hz", targetTones[t]); printf("\n"); #endif #endif // Set up initial buffer configuration samplesWriting = sampleBuffer0; samplesProcessing = sampleBuffer1; fillingBuffer0 = true; transferComplete = false; // Start periodically sampling #ifdef artificialSamplesMode initTestModeSamples(); // artificially create samples sampleTicker.attach_us(&toneDetector, &ToneDetector::tickerCallback, sampleInterval); // "sample" artificial samples at desired rate #else startDMA(); if(!terminated) // If DMA got an error, terminated will be set true startADC(); #endif #ifdef debugLEDs led1 = 1; // Indicate start of tone detection #endif #ifdef debugPins // after LEDs to be more accurate debugPin1 = 1; // Indicate start of tone detection #endif // Main loop // Wait for buffers to fill and then process them while(!terminated) { // Check if a buffer of samples is gathered and if so process it if(transferComplete) { transferComplete = false; processSamples(); } } #ifdef debugPins // before LEDs to be more accurate debugPin1 = 0; // Indicate cessation of tone detection debugPin2 = 0; // Turn off indicator that at least two buffers were processed debugPin4 = 1; // Indicate completion #endif #ifdef debugLEDs led1 = 0; // Indicate cessation of tone detection led2 = 0; // Turn off indicator that at least one buffer was processed led4 = 1; // Indicate completion #endif } // Finish up (write results to file and whatnot) // This is separate method so that main program can time the running itself without this extra overhead void ToneDetector::finish() { //printf("Tone detector finished\n"); #if defined(recordSamples) && !defined(recordStreaming) // Write saved samples to file LocalFileSystem local("local"); FILE *foutSamples = fopen("/local/samples.wp", "w"); // Open "samples.wp" on the local file system for writing fprintf(foutSamples, "Sample (V)\n"); uint16_t savedSamplesN = savedSamplesIndex; do { fprintf(foutSamples, "%f\n", toFloat(savedSamples[savedSamplesN])/4.0*3.3); savedSamplesN++; savedSamplesN %= numSavedSamples; } while(savedSamplesN != savedSamplesIndex); fclose(foutSamples); #endif // recordSamples && !recordStreaming #if defined(recordOutput) && !defined(recordStreaming) // Write saved outputs to file #ifndef recordSamples LocalFileSystem local("local"); #endif // not recordSamples FILE *foutOutput = fopen("/local/out.wp", "w"); // Open "out.wp" on the local file system for writing for(uint8_t t = 0; t < numTones; t++) fprintf(foutOutput, "%f Hz\t", tones[t]); uint16_t savedTonePowersN = savedTonePowersIndex; do { for(uint8_t t = 0; t < numTones; t++) fprintf(foutOutput, "%ld \t", savedTonePowers[savedTonePowersN][t]); fprintf(foutOutput, "\n"); savedTonePowersN++; savedTonePowersN %= numSavedTonePowers; } while(savedTonePowersN != savedTonePowersIndex); fclose(foutOutput); #endif // recordOutput } // Terminate the tone detector // Note: Will actually terminate after next time buffer1 is filled, so won't be instantaneous void ToneDetector::stop() { // Stop sampling #ifdef artificialSamplesMode sampleTicker.detach(); #else lli[1]->Control = 0; // Make the DMA stop after next time buffer1 is filled while(!(!fillingBuffer0 && transferComplete)); // Wait for buffer1 to be filled LPC_TIM1->TCR=0; // Stop the timer (and thus the ADC) LPC_SC->PCONP &= ~(1UL << 2); // Power off the timer #endif // Stop the main loop terminated = true; } //============================================ // Sampling / Processing //============================================ // Acquire a new sample #ifdef artificialSamplesMode // If a buffer has been filled, swap buffers and signal the main thread to process it void ToneDetector::tickerCallback() { // Get a sample samplesWriting[sampleIndex] = testSamples[testSampleIndex]; testSampleIndex++; testSampleIndex %= numTestSamples; // Increment sample index sampleIndex++; sampleIndex %= sampleWindow; // See if we just finished a buffer if(sampleIndex == 0) { // Swap writing and processing buffers // Let the main tone detector thread know that processing should take place if(fillingBuffer0) { samplesProcessing = sampleBuffer0; samplesWriting = sampleBuffer1; } else { samplesProcessing = sampleBuffer1; samplesWriting = sampleBuffer0; } transferComplete = true; fillingBuffer0 = !fillingBuffer0; } } #else // not artificial mode - we want real samples! // Callback for DMA channel 0 void TC0_callback(void) // static method { // Swap writing and processing buffers used by main loop if(toneDetector.fillingBuffer0) { toneDetector.samplesProcessing = toneDetector.sampleBuffer0; toneDetector.samplesWriting = toneDetector.sampleBuffer1; } else { toneDetector.samplesProcessing = toneDetector.sampleBuffer1; toneDetector.samplesWriting = toneDetector.sampleBuffer0; } // Tell main() loop that this buffer is ready for processing toneDetector.fillingBuffer0 = !toneDetector.fillingBuffer0; toneDetector.transferComplete = true; // Clear DMA IRQ flags. if(toneDetector.dma.irqType() == MODDMA::TcIrq) toneDetector.dma.clearTcIrq(); if(toneDetector.dma.irqType() == MODDMA::ErrIrq) toneDetector.dma.clearErrIrq(); } // Configuration callback on Error for channel 0 void ERR0_callback(void) // static method { // Stop sampling LPC_TIM1->TCR = 0; // Stop the timer (and thus the ADC) LPC_SC->PCONP &= ~(1UL << 2); // Power off the timer // Stop the main loop (don't call stop() since that would wait for next buffer to fill) toneDetector.terminated = true; error("Oh no! My Mbed EXPLODED! :( Only kidding, go find the problem (DMA chan 0)"); } #endif // Goertzelize the process buffer void ToneDetector::processSamples() { #ifdef debugLEDs if(fillingBuffer0) led2 = 1; // Indicate that at least two buffers have been recorded led3 = 1; // Indicate start of processing #endif #ifdef debugPins // after LEDs and timer to be more accurate if(fillingBuffer0) debugPin2 = 1; // Indicate that at least two buffers have been recorded debugPin3 = 1; // Indicate start of processing #endif // Create variables for storing the Goertzel series int32_t s0, s1, s2; volatile int32_t newTonePower; // Create variables for getting max input value int32_t sample = 0; uint32_t signalLevel = 0; // For each desired tone, compute power and then reset the Goertzel for(uint8_t i = 0; i < numTones; i++) { // Reset s0 = 0; s1 = 0; s2 = 0; // Compute the Goertzel series for(uint16_t n = 0; n < sampleWindow; n++) { // Note: bottom 4 bits from ADC indicate channel, top 12 are the actual data // Note: Assuming Q10 format, we are automatically scaling the ADC value to [0, 4] range sample = ((int32_t)((samplesProcessing[n] >> 4) & 0xFFF)); // Get signal level indicator if(i == 0) { if(sample-2048 >= 0) signalLevel += (sample-2048); else signalLevel += (2048-sample); } // TODO check the effect of this subtraction? //sample -= 2048; s0 = sample + fixedPointMultiply(goertzelCoefficients[i], s1) - s2; s2 = s1; s1 = s0; } // Compute the power newTonePower = fixedPointMultiply(s2,s2) + fixedPointMultiply(s1,s1) - fixedPointMultiply(fixedPointMultiply(goertzelCoefficients[i],s1),s2); // Update the running sum tonePowersSum[i] -= tonePowers[i][tonePowersWindowIndex]; tonePowersSum[i] += newTonePower; // Update the history of powers tonePowers[i][tonePowersWindowIndex] = newTonePower; } // See if first circular buffer has been filled readyToThreshold = readyToThreshold || (tonePowersWindowIndex == tonePowersWindow-1); // Deliver results if a callback function was provided if(callbackFunction != 0 && readyToThreshold) callbackFunction(tonePowersSum, signalLevel >> 7); // divide signal level by 128 is basically making it an average (125 samples/buffer) #ifdef streamAcousticControlLog acousticControlLogToStream[0] = tonePowers[0][tonePowersWindowIndex]; acousticControlLogToStream[1] = tonePowers[1][tonePowersWindowIndex]; acousticControlLogToStream[2] = signalLevel >> 7; #endif #ifdef debugLEDs led3 = 0; // Indicate completion of processing #endif #ifdef recordSamples #ifdef recordStreaming for(uint16_t n = 0; n < sampleWindow; n++) printf("%ld\n", (samplesProcessing[n] >> 4) & 0xFFF); #else for(uint16_t n = 0; n < sampleWindow; n++) { savedSamples[savedSamplesIndex++] = (samplesProcessing[n] >> 4) & 0xFFF; savedSamplesIndex %= numSavedSamples; } #endif #endif #ifdef recordOutput #ifdef recordStreaming for(uint8_t t = 0; t < numTones; t++) printf("%ld\t", tonePowers[t][tonePowersWindowIndex] >> 10); // used to shift 10 printf("\n"); #else for(uint8_t t = 0; t < numTones; t++) { savedTonePowers[savedTonePowersIndex][t] = tonePowers[t][tonePowersWindowIndex]; } savedTonePowersIndex++; savedTonePowersIndex %= numSavedTonePowers; #endif #endif // Increment window index (circularly) tonePowersWindowIndex = (tonePowersWindowIndex+1) % tonePowersWindow; #ifdef debugPins // before LEDs, timer, and recordSamples to be more accurate debugPin3 = 0; // Indicate completion of processing #endif } int32_t* ToneDetector::getTonePowers() { return tonePowersSum; } //============================================ // Testing / Debugging //============================================ #ifdef artificialSamplesMode // Use artificial samples, either from a file or from summing cosine waves of given frequencies // Samples in a file will be interpreted as volts, so should be in range [0, 3.3] void ToneDetector::initTestModeSamples() { #ifdef sampleFilename LocalFileSystem local("local"); printf("Using samples from file: "); printf(sampleFilename); printf("\n"); FILE *fin = fopen(sampleFilename, "r"); // Open "setup.txt" on the local file system for read if(fin == 0) { printf(" *** Cannot open file! ***\n"); wait_ms(3000); numTestSamples = 1; testSamples = new uint32_t[numTestSamples]; testSamples[0] = 0; testSampleIndex = 0; return; } // See how long the file is int numTestSamples = 0; float val; float maxVal = 0; float minVal = 0; float avgVal = 0; int res = fscanf(fin, "%d\n", &val); while(res > 0) { numTestSamples++; res = fscanf(fin, "%f\n", &val); if(val > maxVal) maxVal = val; if(val < minVal) minVal = val; avgVal = numTestSamples > 1 ? (avgVal + val)/2.0 : val; } printf(" Found %d samples in the file, max %f, min %f, avg %f\n", numTestSamples, maxVal, minVal, avgVal); if(minVal < 0) printf(" WARNING: File supposed to represent voltage input to ADC, so negative numbers will be interpreted as 0\n"); if(maxVal > 3.3) printf(" WARNING: File supposed to represent voltage input to ADC, so numbers greater than 3.3 will be interpreted as 3.3\n"); fclose(fin); // Read the samples testSamples = new uint32_t[numTestSamples]; fin = fopen(sampleFilename, "r"); // Open "setup.txt" on the local file system for read for(int i = 0; i < numTestSamples; i++) { // Read the voltage fscanf(fin, "%f\n", &val); // Clip it like the ADC would val = val > 3.3 ? 3.3 : val; val = val < 0 ? 0 : val; // Convert voltage to 12-bit ADC reading testSamples[i] = val/3.3*4096.0; // Shift it by 4 to mimic what the DMA would write for a reading (lower 4 would indicate ADC channel) testSamples[i] = testSamples[i] << 4; } fclose(fin); testSampleIndex = 0; sampleIndex = 0; #else // not using file for samples, will create a sum of cosine waves instead numTestSamples = 1000; testSamples = new uint32_t[numTestSamples]; testSampleIndex = 0; // Adjust overall amplitude and offset - make sure it's nonnegative float amplitude = 1; // volts float baseline = 1.5; // volts // Print out the frequencies being used float frequencies[] = sumSampleFrequencies; // Test signal will be a summation of cosines at these frequencies (in Hz) printf("Using samples from cosines with the following frequencies:\n"); for(int f = 0; f < sizeof(frequencies)/sizeof(float); f++) printf(" %f\n", frequencies[f]); printf("\n"); // Create the samples float sampleFrequency = 1000.0/((double)sampleInterval/1000.0); for(uint16_t n = 0; n < numTestSamples; n++) { float nextSample = 0; // Sum the frequencies for(int f = 0; f < sizeof(frequencies)/sizeof(float); f++) nextSample += cos(2.0*PI*frequencies[f]*(float)n/sampleFrequency); // Normalize nextSample /= (float)(sizeof(frequencies)/sizeof(float)); // Amplify nextSample *= amplitude; // Positivify nextSample += baseline; // Convert to 12-bit ADC reading testSamples[n] = ((uint32_t)(nextSample/3.3*4095.0)); // Shift it by 4 to mimic what the DMA would write for a reading (lower 4 would indicate ADC channel) testSamples[n] = testSamples[n] << 4; } sampleIndex = 0; #endif // which sample mode } #endif // artificialSamplesMode #endif // #ifdef acousticControl