Senior design project UIUC

Status
Not open for further replies.

pwd847

Active member
Hello, I am an EE student attending UIUC. I am in a group of 3. Our project proposal can be found here.

We want to have a 3.5mm audio jack input. Convert that input to mono sound. use passive analog filters to separate bass, mid, and tweet signals. Use the ADC to do its thing, and perform computations. Then, a level shifted 5V logic will be sent to data line of the three LED strips and they will light up according to the music. That's pretty much the high level of the project.

A requirement of the class is create our own PCB. Our demo is sometime in early May I think.

I've been doing a lot of research on how to sample audio. I know that we are shooting for 44.1kHz (following the Nyquist freq rule). The reason we can't use an Arduino is because AnalogRead() on an Uno clocks in around 9.5kHz sampling rate. So we bought a nucleo-L432KC, but that board wasn't compatible with the FastLED library. After that I decided it was a good idea to read the FastLED documentation. I know, obvious, right? In the documentation I read that Teensy boards work very well with FastLED. So I purchased a Teensy 4.0.

And here I am.

So I am here to learn how to deal with audio on the Teensy 4.0 board (and anything else you wanna throw at me!). We would like to use the Arduino IDE, but I think I read that the AnalogRead() function has code that limits the sample freq of the ADC. Is this true?

Any advice or tips?

Thanks!
 
To be clear, I split the analog signal into 3 signals and then send those 3 signals into the adc. All 3 of those need to be sampled at 44.1kHz
 
There is a good audio CODEC shield for Teensy that samples two channels in/out at 44.1k / 16 bits, and much more suitable for audio than the on-chip ADCs and DACs. https://www.pjrc.com/store/teensy3_audio.html

The circuit is published on the site, and it would be easy to incorporate the basic circuitry into your own PCB once you have prototyped using standard Teensy components. (Watch out for swapped Teensy 4.0 pins 7 & 8 on the Rev D schematic - the pins list above is correct).

Almost all of what you need is in the Teensy audio library - and for a university project, you need to do the research yourself. Start here: https://www.pjrc.com/teensy/td_libs_Audio.html

The Teensy community, however, will be happy to help out when you hit a brick wall!

Best wishes for your project.
 
I doubt we would be allowed to utilize another external pcb. We already got special permission to use the teensy (even with female header pins on our own pcb). Can the audio board be directly replicated on ours PCB? Seems a little advanced for us.
 
I just noticed the schematic and that doesn't look like something we should integrate into our PCB. I did hear an interesting idea yesterday, that since we are using 3 different signals with 3 different frequencies, we could sample bass (200Hz), at 400Hz, mid (10kHz) at 20kHz, and tweets(20kHz) at 44.1kHz. That may help free up some resources if the input channels are run through a mux.
 
Yes, you could replicate the audio board onto yours - see the links in my last posting. You don't need the extra components for SD card or flash RAM, just those associated with the SGTL 5000.

Otherwise just go with the Teensy's two ADCs - they can sample up to 1 MHz / 12 bits.

Yes, a MUX could work - particularly for the two LF signals, using the second channel for HF. You could drive the MUX from the ADC interrupt without too much trouble. Creating two audio streams is a bt more work, and would require doubling the interrupt frequency. Again quite doable and able to be localised to just customising the input_adcs.cpp code in the audio library.
 
So I tried to use some buffer chip from the university electronic services shop (74HCT244-2N) as a level shifter and it doesn't seem to work. The output from teensy looks ok on an oscilliscope, but the output of the buffer is all weird and distorted. Per advice of Paul in another few threads, I am ordering a 74HCT125 level shifter. He has explained that this chip is much faster at outputting correct values, and should work with the WS2815 timing that is required.
 
Level shifter works great! LEDs are acting funny. I think I'll leave this thread for audio and adc issues and start another thread for fastled on ws2815's.
 
After reading this thread https://forum.pjrc.com/threads/59993-How-many-inputs-can-be-processed, I decided to just buy an external audio 44.1kHz ADC. Our University just announced that we are moving to an online-remote-instruction-only format after spring break due to the this Corona Virus garbage. Having a breadboard chip that I can just plug wires into and know that it will work seems like the way to go at this point. Also our initial PCB design is due tomorrow, so might as well throw a couple footprints on the board!
 
I need a 132.3KHz clock. 44.1KHz per channel. I'm looking at using this code to do that.

Code:
#define interval  7;

elapsedmicros delay;           // interval
int i = 0;

void loop() {

    // Setup ADC to 132.3KHz sample rate 
    if ( delay >= interval ) {
    
        // subtract the interval so proper timing is continued. delay = 0 may take time to do thus throwing off timing
      delay -= interval;
    //ADC Channel [i] sample taken
    ++i;

      if(i == 4){
        i = 0;
      }
    }
}
 
Last edited:
You could do that, or you could set up a timer interrupt to set the ADC chip converting, which would give you an even time between samples. The way you are proposing to attack it could have quite significant sample jitter - adding distortion to the sampled audio signal.

To read the signal, you can poll for ready status, or set up another interrupt on conversion complete (assuming your ADC has an Interrupt pin).

Why are you using a 132.3kHz sample rate? It may be a coincidence that it's 3 x 44.1 kHz which is already twice the highest audio signal needing to be sampled (Nyquist limit).
 
At the same time you posted your reply I was looking at https://www.pjrc.com/teensy/td_timing_IntervalTimer.html. That seems like it would be the way to go.

Turns out we aren't using an external ADC. I spoke with a classmate about that, and we just aren't doing that.

The 132.3KHz sample rate comes from sampling 3 different channels all with the same ADC. So if the mux is switching between channel 0, 1, and 2, all 3 channels would have the 44.1KHz requirement that we proposed in our design document.

I read through all of chapter 66 https://www.pjrc.com/teensy/IMXRT1060RM_rev2.pdf as well. Very informative.
 
Interrupts are new to me. I have done some research and testing though. I think I understand how to interrupt to do an ADC read, but what would the interrupt look like for copying the read value to a local variable?

Code:
ADC READ code here;
While( COCOn != 1){
}
nointerrupts();
data = ADCRn;
interrupts();

This is the relevant text from the pdf on this site:

When the conversion is completed, the result is placed in the data result registers
(ADC_Rn). The conversion complete flag (COCOn) field in the Hardware Status register
is/are then set and an interrupt is generated, if the respective conversion complete
interrupt has been enabled (ADC_HCn[AIEN]=1).


I'm not sure what the last part means: "...and an interrupt is generated, if the respective conversion complete
interrupt has been enabled (ADC_HCn[AIEN]=1)."

I'm also having trouble figuring out how to read negative voltages. Audio signals oscillate between positive and negative voltages, but the teensy only reads between 0 and 1.2 Volts I thought?
 
I try to isolate different modules and get them working before combining everything together. Here is the working Bass frequency LED code that just pulses the leds via a timer. The final product will pulse them via the incoming bass signal, based on the signal's volume.

Code:
#include <FastLED.h>

#define NUM_LEDS_PER_STRIP 299
#define NUM_STRIPS 1

#define NUM_LEDS NUM_LEDS_PER_STRIP
#define DATA_PIN 1

// Define the array of leds
CRGB leds[NUM_LEDS_PER_STRIP * NUM_STRIPS];

void setup() {
  
  delay(2000);                                                                    //safety delay
  FastLED.addLeds<NUM_STRIPS, WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS_PER_STRIP);  //Paralell mode
  FastLED.setMaxPowerInVoltsAndMilliamps(12,950);                                 //Red acts funny higher than 0.95A on this particular power supply
  FastLED.setBrightness(9);                                                       //Initialize minimum brightness
  delay(100);                                                                     //Delay for the hell of it
  for(u_int16_t i = 0; i < NUM_LEDS; i++){                                        //Initialize strip to first Red color
    leds[i] = CRGB::AliceBlue;
  }
}

char color = 'r';                         //Initialize char to first 'r'

void loop() {
  u_int8_t brightness = 9;                //Brightness starts at 9
  
  while(brightness <= 60){                //Increase brightness of color
      brightness += 1;
      FastLED.setBrightness(brightness);
      FastLED.show();
      delayMicroseconds(275);             //Minimum delay without visable errors
    }
    
  while(brightness >= 9){                 //Decrease brightness of color
      brightness -= 1;
      FastLED.setBrightness(brightness);
      FastLED.show();
      delayMicroseconds(275);             //Minimum delay without visable errors
    }
    
 switch (color){                         //Case for color switching.  Will add dozens more colors in the future.
    
    case 'r':
      color = 'g';
      
      for( i = 0; i <NUM_LEDS; i++){  
        leds[i] = CRGB::Green;
      }
      break;

    case 'g':
      color = 'b';
      
      for(u_int16_t i = 0; i <NUM_LEDS; i++){  
        leds[i] = CRGB::Blue;
      }
      break;

    case 'b':
      color = 'p';
      
      for(u_int16_t i = 0; i <NUM_LEDS; i++){  
        leds[i] = CRGB::Red;
      }
      break;

    case 'p':
      color = 'o';
      
      for(u_int16_t i = 0; i <NUM_LEDS; i++){  
        leds[i] = CRGB::Purple;
      }
      break;

  case 'o':
      color = 't';
      
      for(u_int16_t i = 0; i <NUM_LEDS; i++){  
        leds[i] = CRGB::DarkOrange;
      }
      break;

  case 't':
      color = 'r';
      
      for(u_int16_t i = 0; i <NUM_LEDS; i++){  
        leds[i] = CRGB::Turquoise;
      }
      break;

    default:                              //If white ever pulses, there is something wrong with the switch statement.
          color = 'w';
      
      for(u_int16_t i = 0; i <NUM_LEDS; i++){  
        leds[i] = CRGB::White;
      }
  } 
}

Similary, Ive been messing around with ADC code. Unfortunately, I left my function generator at home so this code only reads all 0's. I'm still trying to set the interrupt priority for these signals. I figure if I make Tweets high priority, Mids medium priority, and Bass medium-low priority, they nyquist sampling rate will still be enforced.

Code:
#define PIN_BASS  A0
#define PIN_MID   A1
#define PIN_TWEET A2
#define BITS      10
IntervalTimer ADC_Read_Timer;

void setup() {
  
  IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02 &= ~ (1<<12) ; // disable keeper
  IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03 &= ~ (1<<12) ; // disable keeper
  IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_04 &= ~ (1<<12) ; // disable keeper
  
  pinMode(PIN_BASS,  INPUT);
  pinMode(PIN_MID,   INPUT);
  pinMode(PIN_TWEET, INPUT);
  
  analogReadResolution(BITS);
  
  ADC_Read_Timer.begin(ADC_READ_BASS,  22);
  ADC_Read_Timer.begin(ADC_READ_MID,   22);
  ADC_Read_Timer.begin(ADC_READ_TWEET, 22);
  
  Serial.begin(115200);
}

volatile int AnalogValue_Bass;
volatile int AnalogValue_Mid;
volatile int AnalogValue_Tweet;

void ADC_READ_BASS(){
  AnalogValue_Bass = analogRead(PIN_BASS);
}

void ADC_READ_MID(){
  AnalogValue_Mid = analogRead(PIN_MID);
}

void ADC_READ_TWEET(){
  AnalogValue_Tweet = analogRead(PIN_TWEET);
}

void loop() {
  int AnalogValue_Bass_copy;
  int AnalogValue_Mid_copy;
  int AnalogValue_Tweet_copy;
  
  noInterrupts();
  AnalogValue_Bass_copy  = AnalogValue_Bass_copy;
  interrupts();
  
  noInterrupts();
  AnalogValue_Mid_copy   = AnalogValue_Mid_copy;
  interrupts();
  
  noInterrupts();
  AnalogValue_Tweet_copy = AnalogValue_Tweet_copy;
  interrupts();
  
  Serial.print("Analog value Bass is  ");
  Serial.println(AnalogValue_Bass_copy);
  Serial.print("Analog value Mid is  ");
  Serial.println(AnalogValue_Mid_copy);
  Serial.print("Analog value Tweet is  ");
  Serial.println(AnalogValue_Tweet_copy);
}

Any critiques?
 
Last edited:
To discuss yesterday's question:

If you have not offset the voltage presented to the Teensy by around half the available measurement window (resistor bridge for bias, say 2 x 100k ohms from supply to ground, and AC couple the signal 1u capacitor (NOT X7R) should do the trick), then all the negative values will register as zero (underflow). This is not necessarily an issue, as generally the negative swing is (short and long-term) pretty much equal to the positive.

The input voltage to analogRead value ratio varies amongst Teensies (not sure which variety you're using), and calibration is a must for any electrical engineer!

Sounds like you're making good progress. Sorry, I don't use FastLED so I can't comment on that code.

The other code looks OK, as long as you have analogue filters and rectifiers feeding the ADC. If so, negative voltages won't appear after rectification.

If you are just filtering, then you need to run a moving peak value algorithm on your inputs, as the instantaneous values don't make much sense. RMS is a better measurement than peak, but if you're basically assuming sine wave input, then a simple numeric scaling will do the trick (1/2.8 for p-p, 1/1.4 for rectified).
 
According to this video [https://youtu.be/-2B4Dmy4J1Y?t=2m18s] (Go to time 2:18 in the video), This circuit should take care of having a proper voltage range for input to the Teensy direct from a phone or MP3 player:

audio_circuit.PNG

Some tinkering will need to be done for this to work with the signal going through the 2nd order filters before being an input to this circuit, but I think this looks promising.

Another thing I'm having trouble visualizing is how we will take these ADC values and use them to calculate when the bass is pulsing. My guess (without having access to instrumentation to test my theory) is that when the amplitude of the signal increases, that means that the volume of the bass has increased, which means that there has been a pulse.

Hopefully we have access to a better oscilloscope in a few days.
 
OK, I thought that you were going to use some analog filters to separate bass/mid/treble and then multiplex them into the ADC. The 2nd order filters here won't be needed if your other filters are well designed.

If you are still taking this approach, then you need to run some code to calculate RMS or peak values over a suitable period, to detect the current signal level. An even neater approach is to calculate a running average. The period will be determined by what you want to do with the output. I think you have mentioned modulating some LEDs. A time constant of 0.2-0.5 secs should work for this. Think of it as a simple, continuous FFT process.

You will need to outline your approach in your design documentation and how you overcame the issues encountered for your project. I'd suggest you write a clear specification of the problem to be solved, and the basic approach to how you're going to solve it, if this has not already been done. Otherwise, you'll be chasing your tail as you continually have t fix issues that could have been dealt with in the design phase.

Happy trails!
 
Oh. Some other students who have worked with audio in the past told us that we may need 2nd order filters. I did ask them if we needed that even with the output being in the form of LEDs and not audio. We will experiment with both and see how it works out. Yes we are still taking the analog filter approach.

Why would we take a FFT if the signal has already been filtered? I am taking digital signal processing right now and we won't learn about FFT's for awhile yet. I've done analog FT's before, but that is usually to see what kind of filter the analog circuit looks like in the frequency domain.

As far as documentation, we are each keeping a lab notebook, and we will have our first "post spring break meeting" tomorrow. I will make sure to talk about are design at this meeting. Additionally, our final report (due at the end of April), will definitely contain all of this information.

Thanks!
 
OK, using the analog filter approach, you'll need to consider the characteristics of the ADCs in your overall design.

If these 2 pole filters make sense, with the other other band shaping you are putting in place, then that's excellent. The LF portion of your spectrum may be particularly impacted by their presence - depending on the frequency of the filter poles.

My point was simply to consider them as part of the overall design question for filtering, rather than just assuming they're needed.

Sounds, like you're making good progress. Keep up the good work!
 
Oh cool thanks! All of the code that I have found online uses FT's. I'm hesitant to use that because then my instructor could ask why I had the analog filters, if I was just gonna use an FT anyway. We are looking into reading the amplitudes of the signals and maybe that could correspond to the volume level.
 
Hey from Georgia Tech! I did an audio visualizer project last spring. It might be a useful reference.

So I'm trying to understand your circuit here, but I'm having a hard time figuring it out. When I modify the resistor values nothing really happens in terms of decreasing the amplitude of the signal.

I know that the 5V power supply uses a voltage divider to add the DC bias, but manipulating the AC signal down to 0.6 V isn't working. I also used that link to recreate the input circuit that I posted a few posts up with no luck.

Also, how much wiggle room should I leave on the top and bottom end of the ADC spectrum? If I assume my signal is centered at 0.6VDC, I was thinking an amplitude of 0.5VAC would leave 0.1V on the top and bottom.
 
Status
Not open for further replies.
Back
Top