PIC32: Wie verwende ich die DSP-Bibliothek?

Ich verwende ein Microstick II Board mit dem mitgelieferten PIC32MX250F128B und möchte Audioverarbeitung in Echtzeit durchführen. Ich verwende einen analogen Eingang, um den Eingangston zu erhalten, und einen PWM-Ausgang in Kombination mit einem Tiefpassfilter, gefolgt von einem Verstärker, als meinen Audioausgang (verbunden mit Kopfhörern).

Ich würde gerne eine fortgeschrittenere Signalverarbeitung wie Filterung usw. durchführen. Auf der Microchip-Website heißt es, dass es dafür eine DSP-Bibliothek gibt. Das lib-Handbuch finden Sie hier , aber es enthält nicht viele Informationen, nur einige Funktionsprototypen ...

Zunächst möchte ich eine Tiefpass- und eine Bandpassfilterung durchführen. Bisher habe ich es geschafft, fft mit dem folgenden Code auszuführen:

// include header files
#include <plib.h>
#include <p32xxxx.h>
#include <dsplib_dsp.h>
#include <fftc.h>

// Config Bits
#pragma config FNOSC = FRCPLL       // Internal Fast RC oscillator (8 MHz) w/ PLL
#pragma config FPLLIDIV = DIV_2     // Divide FRC before PLL (now 4 MHz)
#pragma config FPLLMUL = MUL_20     // PLL Multiply (now 80 MHz)
#pragma config FPLLODIV = DIV_2     // Divide After PLL (now 40 MHz)
#pragma config FWDTEN = OFF         // Watchdog Timer Disabled
#pragma config ICESEL = ICS_PGx1    // ICE/ICD Comm Channel Select (pins 4,5)
#pragma config JTAGEN = OFF         // Disable JTAG
#pragma config FSOSCEN = OFF        // Disable Secondary Oscillator

// Defines

#define fftc fft16c64 // from fftc.h, for N = 64

#define SYSCLK (40000000L)

#define SAMPLES 64          

#define PWM_FREQ   48000            // Output PWM frequency
#define DUTY_CYCLE  1               

int tab[SAMPLES];
int i = 0;

int analogRead(char analogPIN)
{
    AD1CHS = analogPIN << 16;       // AD1CHS<16:19> controls which analog pin goes to the ADC

    AD1CON1bits.SAMP = 1;           // Sampling
    while(AD1CON1bits.SAMP);        // wait until acquisition is done
    while(!AD1CON1bits.DONE);       // wait until conversion done

    return ADC1BUF0;                
}

void adcConfigureManual()
{
    AD1CON1CLR = 0x8000;    // disable ADC before configuration

    AD1CON1 = 0x00E0;       // internal counter ends sampling and starts conversion (manual sample)
    AD1CON2 = 0;            // AD1CON2<15:13> set voltage reference to pins AVSS/AVDD

    // Found on the web (todo: check the datasheet)
    AD1CON3 = 0x0f01;       // TAD = 4*TPB, acquisition time = 15*TAD
} 

int main( void)
{
    SYSTEMConfigPerformance(SYSCLK);

    // Set OC1 to pin 2 with peripheral pin select
    RPA0Rbits.RPA0R = 0x0005;

    // Configure standard PWM mode for output compare module 1 
    OC1CON = 0x0006;

    for(i = 0; i<SAMPLES; i++)
        tab[i] = 0;

    // From datasheet: 
    // PR = [FPB / (PWM Frequency * TMR Prescale Value)] – 1
    PR2 = (SYSCLK / PWM_FREQ) - 1;

    // Initial duty cycle value
    OC1RS = (PR2 + 1) * ((float)DUTY_CYCLE / 100);

    T2CONSET = 0x8000;      // Enable Timer2, prescaler 1:1
    OC1CONSET = 0x8000;     // Enable Output Compare Module 1

        // Configure pins as analog inputs
        ANSELBbits.ANSB3 = 1;   // set RB3 (AN5) to analog
        TRISBbits.TRISB3 = 1;   // set RB3 as an input
        TRISBbits.TRISB5 = 0;   // set RB5 as an output (note RB5 is a digital only pin)

        adcConfigureManual();   // Configure ADC
        AD1CON1SET = 0x8000;    // Enable ADC

        int pos=0, dat = 0;

        int log2N = 6; // log2(64) = 6
        int N = 1 << log2N; // N = 2^6 = 64
        int din[N];
        int dout[N];
        int dout2[N];
        int scratch[N];

    while(1)
    {
            //foo = analogRead 5); // note that we call pin AN5 (RB3) by it's analog number
            dat = analogRead(5);

            //dat += din[pos]*3/10;
            //if(dat > 1023) dat -= 512;

            din[pos] =  dat;

            mips_fft16(dout, din, fftc, scratch, 1);
            mips_fft16(dout2, dout, fftc, scratch, 1);


            if(++pos >= SAMPLES) pos = 0;

            OC1RS = (PR2 + 1) * ( ((float)dout2[pos])/1023); // Write new duty cycle
    }

    return 0;
}

Zunächst möchte ich wissen, ob das, was ich tue, richtig ist. Ich meine, es gibt keine ifft-Funktion, führt die zweite fft eine ifft aus? Zweitens kann ich dadurch einen hochfrequenten Ton hören, der aus einer Latenz resultieren kann, die die Änderung des PWM-Tastverhältnisses verzögert OC1RS. Wie kann ich das beheben? Ich habe gesehen, dass Leute normalerweise Timer2 verwenden, um das Tastverhältnis zu ändern, aber jedes Mal, wenn ich es versucht habe, bekomme ich nichts am Ausgang (kein Ton). Wie kann ich das in meinem Fall umsetzen?

Haben Sie eine Ausgabe von „Numerical Recipes“? Es ist ein sehr gutes Buch zu diesen und anderen Themen. Einschließlich der Filterung mit FFTs/IFFTs. Aber es ist nicht schwer zu sehen, wie man es mit roher Gewalt macht. Stellen Sie sicher, dass Sie auch die Windowing-Funktionen und ihren Zweck verstehen. E Brighams "Fast Fourier Transform and Its Applications" in so ziemlich JEDER Ausgabe ist im Allgemeinen ein erstklassiges Buch zu diesem Thema. Und Ihre Frage scheint ziemlich allgemein zu sein, also ist das auch ein gutes Buch, das Sie sich besorgen und gründlich lesen sollten.
Ich habe davon gehört, danke für diesen guten Vorschlag. Aber meine Frage ist eher "wie man es richtig macht", ich meine in Bezug auf die Leistung. Ich weiß, wie man FFT verwendet, ich habe sie viel mit Matlab verwendet, aber nie mit einem PIC oder dsPIC. Daher möchte ich die DSP-Lib-Funktion so relevant wie möglich verwenden.
Leistung besteht aus mindestens vier Fragen: Was können "vorgefertigte" Bibliotheken leisten? Wie kann ich die Problembeschreibung ändern, um "konservierte" Bibliotheken für die Leistung besser zu nutzen? Was könnte ich erreichen, wenn ich benutzerdefinierten Code für meine Bedürfnisse schreibe? Erfüllen einige oder alle davon meine Anforderungen? Da Sie Ihre Bewerbung besser kennen als jeder von uns und da es sich bei den oben genannten wirklich um "Bewerbungsfragen" handelt, bin ich mir nicht sicher, wie wir hier ohne viele weitere Informationen zu den Bewerbungsdetails viel helfen können. (Tiefpass ist jedoch einfach. Was haben Sie versucht?)
Dank Ihrer Kommentare konnte ich FFT durchführen. Aber ich habe noch einige Fragen zu den Ergebnissen, die ich erhalte, und insbesondere zur Verwendung von Timer2, der das Latenzproblem möglicherweise beheben könnte.

Antworten (1)

Ich finde, dass dies am besten mit dem DCMI-Tool und der Simulation gelernt wird, obwohl Mipsfir aus irgendeinem Grund nicht simuliert.

mips_fft16/32 macht fft und ifft. Es ist wirklich alles dasselbe. Überträgt Eingaben von einer Domäne zur anderen.

Ihre Implementierung weist Mängel auf. Die Ausgabe der mipsfft ist eine komplexe Zahl, mich wundert das sogar kompiliert. Machen Sie eine Studie über komplexe Zahlen. Ich sehe Ihre fftc-Definition nicht, aber eine Größe von 1 macht im mips_fft16-Aufruf keinen Sinn. Es ist wirklich ziemlich einfach. Wenn Sie eine Sinuswelle in einem Sample-Puffer von 32 erzeugen möchten, geben Sie den Wert (amplitute*32) in din[1].re ein. din[2].re für einen Doppelsinus und so weiter. Also für eine Abtastrate von 16000, 16000/32 = 500 * 1 = 500 Hz. Beachten Sie, dass Ihre Ausgabe in dout[x].re ist. Ein Aufruf von fft/ifft sieht also für eine fft der Größe 32 eher so aus. mips_fft16(dout, din, fftc16c32, scratch, 6);

Denken Sie auch daran, dass die Ausgabe eines fft real und imaginär sein wird. Wenn Sie also nur Spitzen wollen, vergessen Sie nicht, dass A zum Quadrat + B zum Quadrat = C zum Quadrat.

Real und imaginär = Phase der Ausgabe, stark vereinfacht.

OC1RS = (PR2 + 1) * ( ((float)dout2[pos])/1023); // Write new duty cycle

Dies ist überhaupt nicht der richtige Weg, dies zu tun. Das OC1RS-Update kann überall sein, aber ich schlage vor, einen Timer mit einer ausgewählten Abtastrate, sagen wir 16000, einzubauen. Dann füttern Sie OC1RS mit einer Art inkrementierendem Datenzeiger, um durch Ihre Ausgabe zu gehen.