DSP experiments

From Hackerspace ACKspace
Revision as of 22:42, 3 November 2015 by Xopr (talk | contribs) (set project picture)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
Project: DSP experiments
Featured:
State Completed
Members Coolepascal, Danny Witberg
GitHub No GitHub project defined. Add your project here.
Description Digital Signal Processing with a microcontroller and DAC
Picture
DSP-Experiments0142b.jpg

To try out some DigitalAnalogConverters for some DigitalSignalProcessing experiments, i added an MCP4822 dual 12bits dac to my leaflabs Maple board. The Maple board is an Arduino compatible board with an 72MHz Arm Cortex 3 processor.

One of the first experiments was an DirectDigitalSynthesiser with both Sine en Cosine output. Lacking an dual channel osciloscope in out space i decided to use X/Y inputs, which of course would result into an circle on the scope screen.

As Psychic was amazed by this picture i came to the idea to make some fun demo's using this Y/X inputs on the scope whith the folowing result:

Continuing with this sucses i also came to some memory issues on the Maple so i tried the same on the chipKit MAX32 board, which in fact is an Arduino Mega compatible using an PIC32 processor. Initialy basicly copieing the code with some slight adjustments due to difference in hardware and libraries. This time i also did connect an electret microphone to one of the analog inputs, as wel as some variable resistors residing on an MIDI interface shield. I fairly quickly was able to record sound and play it back on an faster or slower rate (adjustable by one of the variable resistors)


In succession of these awesome DSP expiriments, the microchip platform offers several fast microcontrollers suitable for DSP tasks. Interfacing include I²S bus over SPI port, with a 64 bit frame, and LRCK output on the \Slave Select port. Also, an USB interface can act as an soundcard device for audio input and output. This new PIC platform has a RISC MIPS4K core, and DSP capabilities include a 32 bit single cycle multiply-accumulator.

As a start of this new Microchip platform, a reasonable powerful PIC32MX250F128B is chosen, because of the SDIP28 package, for breadbord-friendly testing, and later on we can opt for a dedicated PCB with even more CPU power.

First of all, to confirm that all hardware is working, we can start with a "hello world" for microcontrollers a blinking Led. Since this project is started off with a beginners level, a breadboard is used. Minimum connections are made: 1K pullup from MCLR to 3V3, all VDD and VSS pins are connected to 3V3 and GND respectively. Vcap is connected to a 10uF/25V capacitor to GND. RB8 in this example (pin 17) is connected to a Led with 330 Ohms series resistor.

Blinking led.jpg

#include <plib.h>

// Configuration Bit settings
// SYSCLK = 48 MHz (8MHz Internal clock / FPLLIDIV * FPLLMUL / FPLLODIV)
// PBCLK = 48 MHz (SYSCLK / FPBDIV)
// WDT OFF
// Other options are don't care
#pragma config FPLLMUL = MUL_24, FPLLIDIV = DIV_2, FPLLODIV = DIV_2, FWDTEN = OFF
#pragma config POSCMOD = OFF, FNOSC = FRCPLL, FPBDIV = DIV_1
#define SYS_FREQ (48000000L)

int main(void)
{
    int i;

    // Configure the device for maximum performance but do not change the PBDIV
    // Given the options, this function will change the flash wait states, RAM
    // wait state and enable prefetch cache but will not change the PBDIV.
    // The PBDIV value is already set via the pragma FPBDIV option above.
    SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);

    // Explorer-16 LEDs are on lower 8-bits of PORTA and to use all LEDs, JTAG port must be disabled.
    mJTAGPortEnable(DEBUG_JTAGPORT_OFF);
    
    TRISBbits.TRISB8 = 0; // make RB8 pin output

    PORTBbits.RB8 = 0;

    while (1){
        if (PORTBbits.RB8==0) {
         PORTBbits.RB8 = 1;
        } else {
         PORTBbits.RB8 = 0;
        }

        // Insert some delay
        i = 1024 * 1024;
        while (i--);
    }
}

Next, we want to test the I2S interface, by using the SPI bus. For now, we have an all digital interface applied for the I2S interface, but can easily be adapted for a CODEC, thus making analog inputs and outputs possible. It is referred to as I2S, but in fact we are using the Left Justified (LJ) implementation, without the 1 bit delay regarding the LRCK. Although the framelength is 32 bits, we are using only 16 bits of it, leaving the remaining LSB's logic '0'. The I2S to SPDIF interface is made up with a Cirrus Logic CS8427, which operates at 5V analog and 3V3 digital supply. We are using the reasonably easy available Toshiba optical TOSlink bus with a TOTX173 and a TORX173.

#include <plib.h>

// Configuration Bit settings
// SYSCLK = 48 MHz (8MHz Internal clock / FPLLIDIV * FPLLMUL / FPLLODIV)
// PBCLK = 48 MHz (SYSCLK / FPBDIV)
// WDT OFF
// Other options are don't care
#pragma config FPLLMUL = MUL_24, FPLLIDIV = DIV_2, FPLLODIV = DIV_2, FWDTEN = OFF
#pragma config POSCMOD = OFF, FNOSC = FRCPLL, FPBDIV = DIV_1
#define SYS_FREQ (48000000L)

int main(void)
{
    int i;
    signed int receive_data;

    // Configure the device for maximum performance but do not change the PBDIV
    // Given the options, this function will change the flash wait states, RAM
    // wait state and enable prefetch cache but will not change the PBDIV.
    // The PBDIV value is already set via the pragma FPBDIV option above.
    SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);

    // JTAG port must be disabled.
    DDPCONbits.JTAGEN = 0;
    
    TRISBbits.TRISB8 = 0; // make RB8 pin output
    TRISBbits.TRISB7 = 0; // make RB7 pin output
    TRISBbits.TRISB13 = 0; // make RB13 pin output

    PORTBbits.RB8 = 0;
    PORTBbits.RB7 = 1;

    ANSELA = 0x0000; // all ports digital
    ANSELB = 0x0000; // all ports digital

    SYSKEY = 0x00000000; 
    SYSKEY = 0xAA996655; 
    SYSKEY = 0x556699AA; 
    CFGCONbits.IOLOCK=0; // unlock configuration 
    CFGCONbits.PMDLOCK=0;
    SS1R = 0b0000; // LRCK on pin RA0
    SDI1R = 0b0000; // SDOUT on pin RA1 (SDI)
    RPB13R = 0b0011; // SDIN on pin RB13 (SDO)
    // SCLK is connected to pin RB14 (SCK) by default
    CFGCONbits.IOLOCK=1; // relock configuration 
    CFGCONbits.PMDLOCK=1; 
    SYSKEY = 0x00000000;

    // configure the SPI1 bus for LJ audio transmission slave 16 bit of a 32 bit left and 32 bit right frame.
    SPI1CON = 0;
    SPI1CON2 = 0;
    receive_data = SPI1BUF;
    IPC7bits.SPI1IP = 3;
    IPC7bits.SPI1IS = 1;
    IEC1bits.SPI1RXIE = 1;
    SPI1STATCLR = 0x40;
    SPI1CON2 = 0x00000081;
    SPI1CON = 0x20008400;

    INTCONbits.MVEC = 1; //Enable multivectored INT
    INTEnableInterrupts();

    while (1){
        if (PORTBbits.RB8==0) {
         PORTBbits.RB8 = 1;
        } else {
         PORTBbits.RB8 = 0;
        }
        // Insert some delay
        i = 1024 * 1024;
        while (i--);
    }
}

void __ISR(_SPI_1_VECTOR, ipl3) SPI1handler(void){
 signed int test;
 if (IFS1bits.SPI1RXIF){
  IFS1bits.SPI1RXIF = 0;
  test = SPI1BUF;
   test = test >> 2;
  if (test & 0x2000){
   test |= 0b1100000000000000;
  }  
  SPI1BUF = test;
 }
}

Next, we can actively try to use the Microchip's PIC32 DSP library. These special commands are written in MIPS4K assemby language, and are far more effective than standard C. We can call these assembly functions from C32, by using the DSP library. For implementing a FIR filter, standard functions exist. With mips_fir16_setup() we can convert a bunch of filter coefficients to a more dsp-friendly setup, so mips_fir16() can use them. Also, we need some sort of pingpong buffering. Because the filter function applies to a set number of samples, we have to have a sample buffer. However, while the samples are being treated by the FIR filter, other samples are still received of course. We have to put those in another buffer, and alternate the buffers for FIR treatment. This is the main idea of a pingpong buffer.

In this example, a lowpass filter is applied to the left channel, while the right is bypassed. The Led output configured earlier acts as a busy indicator. Using the FIR implementation to a 48KHz signal, the controller is 41,8% occupied.

Dsp fir.jpg

#include <plib.h>
#include <dsplib_dsp.h>
#include <stdbool.h>

// Configuration Bit settings
// SYSCLK = 48 MHz (8MHz Internal clock / FPLLIDIV * FPLLMUL / FPLLODIV)
// PBCLK = 48 MHz (SYSCLK / FPBDIV)
// WDT OFF
// Other options are don't care
#pragma config FPLLMUL = MUL_24, FPLLIDIV = DIV_2, FPLLODIV = DIV_2, FWDTEN = OFF
#pragma config POSCMOD = OFF, FNOSC = FRCPLL, FPBDIV = DIV_1
#define SYS_FREQ (48000000L)

#define buffer_length 32
#define coefficient_length 32

bool left_right_indicator;
bool ping_pong_indicator;
bool fir_filter_start;
unsigned char fill_buffer_indicator;
int16 input_buffer_a[buffer_length];
int16 input_buffer_b[buffer_length];
int16 output_buffer_a[buffer_length];
int16 output_buffer_b[buffer_length];
int16 delay_line[coefficient_length];
int16 coefficients_2x[coefficient_length*2];
int16 delay_buffer[coefficient_length];
int16 coefficient_buffer[coefficient_length] = {0x008c, 0x00b1, 0x0101, 0x0185, 0x0244, 0x033f, 0x0473, 0x05da,
                                                0x0766, 0x0909, 0x0aae, 0x0c43, 0x0db1, 0x0ee5, 0x0fce, 0x1060,
                                                0x1091, 0x1060, 0x0fce, 0x0ee5, 0x0db1, 0x0c43, 0x0aae, 0x0909,
                                                0x0766, 0x05da, 0x0473, 0x033f, 0x0244, 0x0185, 0x0101, 0x00b1};
// The coefficient_buffer holds the FIR filter kernel. The values represent the coefficient, times 65536, 
// in two's complement form. So, for a -0.5 coefficient, the value is 0xC000.

int main(void){
 int counter;

 SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);
 DDPCONbits.JTAGEN = 0;
    
 TRISBbits.TRISB8 = 0; // make RB8 pin output
 TRISBbits.TRISB7 = 0; // make RB7 pin output
 TRISBbits.TRISB13 = 0; // make RB13 pin output

 PORTBbits.RB8 = 0;
 PORTBbits.RB7 = 1;

 ANSELA = 0x0000; // all ports digital
 ANSELB = 0x0000; // all ports digital

 SYSKEY = 0x00000000; 
 SYSKEY = 0xAA996655; 
 SYSKEY = 0x556699AA; 
 CFGCONbits.IOLOCK=0; // unlock configuration 
 CFGCONbits.PMDLOCK=0;
 SS1R = 0b0000; // LRCK on pin RA0
 SDI1R = 0b0000; // SDOUT on pin RA1 (SDI)
 RPB13R = 0b0011; // SDIN on pin RB13 (SDO)
 // SCLK is connected to pin RB14 (SCK) by default
 CFGCONbits.IOLOCK=1; // relock configuration 
 CFGCONbits.PMDLOCK=1; 
 SYSKEY = 0x00000000;

 // configure the SPI1 bus for LJ audio transmission slave 16 bit of a 32 bit left and 32 bit right frame.
 SPI1CON = 0;
 SPI1CON2 = 0;
 IPC7bits.SPI1IP = 3;
 IPC7bits.SPI1IS = 1;
 IEC1bits.SPI1RXIE = 1;
 SPI1STATCLR = 0x40;
 SPI1CON2 = 0x00000081;
 SPI1CON = 0x20008400;

 INTCONbits.MVEC = 1; //Enable multivectored INT
 INTEnableInterrupts();
    
 mips_fir16_setup(coefficients_2x, coefficient_buffer, coefficient_length);

 while (1){
  if (fir_filter_start == 1){
   PORTBbits.RB8 = 1;
   for (counter=0, counter<coefficient_length; counter++;){ //initialise the delay line to 0
    delay_line[counter] = 0;
   }
   if (ping_pong_indicator){
    mips_fir16(output_buffer_a, input_buffer_a, coefficients_2x, delay_line, buffer_length, coefficient_length, 0);
   } else {
    mips_fir16(output_buffer_b, input_buffer_b, coefficients_2x, delay_line, buffer_length, coefficient_length, 0);
   }
   fir_filter_start = 0;
   PORTBbits.RB8 = 0;
  }
 }
}

void __ISR(_SPI_1_VECTOR, ipl3) SPI1handler(void){
 signed int sample_data;
 if (IFS1bits.SPI1RXIF){
  IFS1bits.SPI1RXIF = 0;
  if (_RA0 == 0){
   left_right_indicator = FALSE;
   if(ping_pong_indicator){
    input_buffer_a[fill_buffer_indicator] = SPI1BUF;
    SPI1BUF = output_buffer_b[fill_buffer_indicator];
   } else {
    input_buffer_b[fill_buffer_indicator] = SPI1BUF;
    SPI1BUF = output_buffer_a[fill_buffer_indicator];
   }
   if (fill_buffer_indicator >= buffer_length-1){
    if (ping_pong_indicator){
     ping_pong_indicator = FALSE;
    } else {
     ping_pong_indicator = TRUE;
    }
    fill_buffer_indicator = 0;
    fir_filter_start = 1;
   } else {
    fill_buffer_indicator++;
   }
  } else {
   sample_data = SPI1BUF;
   left_right_indicator = TRUE;
   SPI1BUF = sample_data;
  }
 }
}