Problems with FHT code

Problems with FHT code

Postby taco » Wed Oct 24, 2012 12:24 pm

Hello,

Thanks for the great FHT/FFT libraries that you developed for the Arduino.

I am having a problem with the FHT library (haven't tested the FFT). Any mathematical operations such as multiplication or division, that take place after these sequences:

fht_window(); // window the data for better frequency response
fht_reorder(); // reorder the data before doing the fht
fht_run(); // process the data in the fht
fht_mag_lin(); // take the output of the fht; also with log

result in corrupt/bogus values. In addition micros() and millis() return bogus values.

Is there any way around this? Is the library doing something to the "math engine" of the chip?

Below is a code example that illustrates the error. Followed by the result.

Code: Select all
/*
fht_adc.pde
guest openmusiclabs.com 9.5.12
example sketch for testing the fht library.
it takes in data on ADC0 (Analog0) and processes them
with the fht. the data is sent out over the serial
port at 115.2kb.  there is a pure data patch for
visualizing the data.
*/

#define LOG_OUT 1 // use the log output function
#define FHT_N 256 // set to 256 point fht

#include <FHT.h> // include the library

void setup() {
  Serial.begin(115200); // use the serial port
  TIMSK0 = 0; // turn off timer0 for lower jitter
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
}

void loop() {
  while(1) { // reduces jitter
    cli();  // UDRE interrupt slows this way down on arduino1.0
    unsigned long us1 = micros();
    for (int i = 0 ; i < FHT_N ; i++) { // save 256 samples
      while(!(ADCSRA & 0x10)); // wait for adc to be ready
      ADCSRA = 0xf5; // restart adc
      byte m = ADCL; // fetch adc data
      byte j = ADCH;
      int k = (j << 8) | m; // form into an int
      k -= 0x0200; // form into a signed int
      k <<= 6; // form into a 16b signed int
      fht_input[i] = k; // put real data into bins
    }
    unsigned long us2 = micros();
    fht_window(); // window the data for better frequency response
    fht_reorder(); // reorder the data before doing the fht
    fht_run(); // process the data in the fht
    fht_mag_log(); // take the output of the fht
    sei();
    Serial.print("Testing of math: ");
    Serial.print(us1);
    Serial.print(" - ");
    Serial.print(us2);
    Serial.print(" = ");
    Serial.println(us2 - us1);
    //Serial.write(255); // send a start byte
    //Serial.write(fht_log_out, FHT_N/2); // send out the data
  }
}


Code: Select all
Testing of math: 4194301183 - 3877611200 = 3978277313


As you can see 4194301183 - 3877611200 should be 316689983. Also those values came from micros() and appear to be bogus, since they don't change between iterations of the loop.

Any ideas on how to circumvent this?

Thanks,

Nils
taco
 
Posts: 4
Joined: Wed Oct 24, 2012 12:19 pm

Re: Problems with FHT code

Postby guest » Wed Oct 24, 2012 3:27 pm

to save time in the code and reduce jitter, TIMER0 is disabled by the TIMSKO = 0; line. so anything that requires TIMER0 will not work, including delay() and micros(). you can try commenting that line out, and see what it does.

also, just to check, are you using this on a MICrODEC, or an Arduino? there are some slight differences between the 2.
guest
Site Admin
 
Posts: 449
Joined: Thu May 20, 2010 11:58 pm

Re: Problems with FHT code

Postby guest » Wed Oct 24, 2012 3:48 pm

also, the cli() and sei() are there to block interrupts while it processes. im not sure why this had to be done, but with ardunio1.0 the UDRE interrupt would slow the process way down. it would take at least 10 times as long. i have no idea why that was the case. it worked fine in arduino-0022. at any rate, micros() might require that interrupt as well.
guest
Site Admin
 
Posts: 449
Joined: Thu May 20, 2010 11:58 pm

Re: Problems with FHT code

Postby taco » Thu Oct 25, 2012 9:06 am

Commenting out the TIMSK0 = 0; line and the cli() and sei() lines shows the same problem.

This is on an Arduino Uno. (chip is ATMEGA328P-PU).

I actually noticed this problem when using interrupts to acquire data, rather than the sample code that is provided. A complete example using the interrupt method is below. (It is derived from the AnalogIsrLogger at http://code.google.com/p/beta-lib/downloads/list.) Look at the freq = ... line before and after the fht in acquire(). It seems that when the acquire() function returns back to loop() I can use mathematical operations again which is a bit strange.

Code: Select all

#include <avr/wdt.h>

// Sample rate in samples per second.
// This will be rounded so the sample interval
// is a multiple of the ADC clock period.
const uint32_t SAMPLE_RATE = 40000;
// Desired sample interval in CPU cycles (will be adjusted to ADC/timer1 period)
const uint32_t SAMPLE_INTERVAL = F_CPU/SAMPLE_RATE;
// Minimum ADC clock cycles per sample interval
const uint16_t MIN_ADC_CYCLES = 15;
const uint8_t ANALOG_PIN = 0;
// Reference voltage
// Zero - use External Reference AREF
// (1 << REFS0) - Use Vcc
// (1 << REFS1) | (1 << REFS0) - use Internal 1.1V Reference
uint8_t const ADC_REF_AVCC = (1 << REFS0);

void adcInit(uint32_t ticks, uint8_t pin, uint8_t ref);
void adcStart();
uint16_t acquire();
uint16_t findMax(uint8_t arr[], int n);
uint16_t findMax(uint16_t arr[], int n);


#define LIN_OUT 1 // use the log output function
#define FHT_N 256 // set to 256 point fht

#include <FHT.h> // include the library


unsigned long StartAcq = 0;
unsigned long EndAcq = 0;
unsigned long FftCount = 0;
unsigned long SampFreqAvg = 0;

int BufPtr = 0;


boolean TogState = false;
boolean ErrorCond = false;

void setup()
{
  wdt_reset();
  Serial.begin(115200); // use the serial port
  adcInit(SAMPLE_INTERVAL, ANALOG_PIN, ADC_REF_AVCC);
  pinMode(13, OUTPUT);
  wdt_enable(WDTO_8S);
}


void loop()
{
  if (ErrorCond)
  {
    Serial.println("Error");
    return;
  }
 
  uint16_t freq = acquire();
  //Serial.println(freq);
 
  if ((FftCount / 50) % 2 != TogState)
  {
    Serial.print("Number of FHT's computed: ");
    Serial.println(FftCount);
    //Serial.print("Average sampling interval (us): ");
    //Serial.println(SampFreqAvg);
    TogState = (FftCount / 50) % 2;
    if (TogState)
    {
      digitalWrite(13, HIGH);
    }
    else
    {
      digitalWrite(13, LOW);
    }
  }
 
  wdt_reset();
}



uint16_t acquire()
{
  uint16_t fs, binNum, freq;
 
  adcStart();
  BufPtr = 0;
 
  StartAcq = micros();
  while(BufPtr < FHT_N)
  {
    millis();
  }
  EndAcq = micros();
 
  FftCount++;
  SampFreqAvg = (SampFreqAvg * (FftCount - 1) + (EndAcq - StartAcq)) / FftCount;
 
  fs = 1.0 / ((double)(EndAcq - StartAcq) / 1000000.0 / FHT_N);
 
  binNum = 20;
  freq = binNum * fs / FHT_N;
  Serial.print(freq);
  Serial.print("    ");
 
  // process data
  fht_window(); // window the data for better frequency response
  fht_reorder(); // reorder the data before doing the fht
  fht_run(); // process the data in the fht
  fht_mag_lin(); // take the output of the fht
 
  // UNFORTUNATELY THE FHT CODE CORRUPTS SOMETHING IN THE MATH ENGINE THAT
  // PREVENTS THE PEAK FROM BEING PROPERLY COMPUTED.

  freq = binNum * fs / FHT_N;
  Serial.println(freq);
 
  //binNum = findMax(fht_lin_out, FHT_N/2);
  //freq = binNum * fs / FHT_N;
 
  //Serial.print(binNum);
  //Serial.print(" ");
  //Serial.print(fs);
  //Serial.print(" ");
  //Serial.print(FHT_N);
  //Serial.print(" ");
  //Serial.println(freq);
 
  return freq;
}

uint16_t findMax(uint8_t arr[], int n)
{
  uint16_t m = 0;
  uint8_t val = 0;
  for (int i = 0; i < n; i++)
  {
    if (arr[i] > val)
    {
      m = i;
      val = arr[i];
    }
  }
  return m;
}

uint16_t findMax(uint16_t arr[], int n)
{
  uint16_t m = 0;
  uint16_t val = 0;
  for (int i = 0; i < n; i++)
  {
    if (arr[i] > val)
    {
      m = i;
      val = arr[i];
    }
  }
  return m;
}

void adcInit(uint32_t ticks, uint8_t pin, uint8_t ref) {
  if (ref & ~((1 << REFS0) | (1 << REFS1))) {
    //error("Invalid ADC reference bits");
    ErrorCond = true;
    return;
  }
  // Set ADC reference andlow three bits of analog pin number
  ADMUX = ref | (pin & 7);
#if RECORD_EIGHT_BITS
  // Left adjust ADC result to allow easy 8 bit reading
  ADMUX |= (1 << ADLAR);
#endif  // RECORD_EIGHT_BITS
 
 // trigger on timer/counter 1 compare match B
  ADCSRB = (1 << ADTS2) | (1 << ADTS0);
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  // the MUX5 bit of ADCSRB selects whether we're reading from channels
  // 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
  if (pin < 8) {
    ADCSRB &= ~(1 << MUX5);
    // disable Digital input buffer
    DIDR0 |= 1 << pin;
  } else {
    ADCSRB |= (1 << MUX5);
    // disable Digital input buffer
    DIDR2 |= 1 << (7 & pin);
  }
#else  // defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  // not a Mega disable Digital input buffer
  if (pin < 6) DIDR0 |= 1 << pin;
#endif  // defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)

#if ADPS0 != 0 || ADPS1 != 1 || ADPS2 != 2
#error unexpected ADC prescaler bits
#endif

  uint8_t adps;  // prescaler bits for ADCSRA
  for (adps = 7; adps > 0; adps--) {
   if (ticks >= (MIN_ADC_CYCLES << adps)) break;
  }
  if (adps < 3)
  {
    Serial.println("Sample Rate Too High");
    ErrorCond = true;
    return;
  }
 
  Serial.print("ADC clock MHz: ");
  Serial.println((F_CPU >> adps)*1.0e-6, 3);

  // set ADC prescaler
  ADCSRA = adps;
 
  // round so interval is multiple of ADC clock
  ticks >>= adps;
  ticks <<= adps;
 
  // Setup timer1
  // no pwm
  TCCR1A = 0;
 
  uint8_t tshift;
  if (ticks < 0X10000) {
  // no prescale, CTC mode
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
    tshift = 0;
  } else if (ticks < 0X10000*8) {
    // prescale 8, CTC mode
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11);
    tshift = 3;
  } else if (ticks < 0X10000*64) {
    // prescale 64, CTC mode
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11) | (1 << CS10);
    tshift = 6;
  } else if (ticks < 0X10000*256) {
    // prescale 256, CTC mode
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12);
    tshift = 8;
  } else if (ticks < 0X10000*1024) {
    // prescale 1024, CTC mode
    TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS12) | (1 << CS10);
    tshift = 10;
  } else {
    Serial.println("Sample Rate Too Slow");
    ErrorCond = true;
    return;
  }
  // divide by prescaler
  ticks >>= tshift;
  // set TOP for timer reset
  ICR1 = ticks - 1;
  // compare for ADC start
  OCR1B = 0;
 
  // multiply by prescaler
  ticks <<= tshift;
  Serial.print("Sample interval usec: ");
  Serial.println(ticks*1000000.0/F_CPU);
  Serial.print("Sample Rate: ");
  Serial.println((float)F_CPU/ticks);
}


void adcStart() {
  // Enable ADC, Auto trigger mode, Enable ADC Interrupt, Start A2D Conversions
  ADCSRA |= (1 << ADATE)  |(1 << ADEN) | (1 << ADIE) | (1 << ADSC) ;
  // enable timer1 interrupts
  TIMSK1 = (1 <<OCIE1B);
  TCNT1 = 0;
}




// ADC done interrupt
ISR(ADC_vect) {
  // read ADC
#if RECORD_EIGHT_BITS
  uint8_t d = ADCH;
#else  // RECORD_EIGHT_BITS
  uint8_t low = ADCL;
  uint8_t high = ADCH;
  uint16_t d = (high << 8) | low;
#endif  // RECORD_EIGHT_BITS
 
  int k = d - 0x0200; // form into a signed int
  k <<= 6; // form into a 16b signed int
 
  // Only write to the buffer if it's not full.
  if (BufPtr < FHT_N)
  {
    fht_input[BufPtr] = k;
  }
 
  BufPtr++;
}

ISR(TIMER1_COMPB_vect) {}

taco
 
Posts: 4
Joined: Wed Oct 24, 2012 12:19 pm

Re: Problems with FHT code

Postby guest » Thu Oct 25, 2012 10:30 pm

ok, so heres whats happening. the FHT code uses all the registers, and i assumed that the compiler kept track of this, since i declared this in the header file that i wrote. but, i guess it doesnt, and the FHT code is clobbering the registers that hold the variables you are using, since they are declared as local variables. a work around for this, is to have any variables that are going to be used after FHT is called to be global.

i can rewrite the header files to push all of the registers onto the stack before and after the function calls, which is another 128 clock cycles per function call, or around 32us per typical FHT usage. or i could write a generic command that does that the user enters before and after, which would cut the time down to 8us. hard to say what the best solution is.

also, commenting out the TIMSK0 line is required to give useful millis() and micros() results. commenting out the cli() and sei() lines gives a consistent 6600us return time, which is how long 256 samples at 500khz adc clock should take. with them in, it gives wrong results.

thanks for taking the time to post up the errors, and let me know what you think a good solution would be here. its probably best to write stack pushers into the header file, just to make it easier on the end user, at the sacrifice of some speed.


Code: Select all
#define LOG_OUT 1 // use the log output function
#define FHT_N 256 // set to 256 point fht

#include <FHT.h> // include the library

unsigned long us1 = 0;
unsigned long us2 = 0;

void setup() {
  Serial.begin(115200); // use the serial port
  //TIMSK0 = 0; // turn off timer0 for lower jitter
  ADCSRA = 0xe5; // set the adc to free running mode
  ADMUX = 0x40; // use adc0
  DIDR0 = 0x01; // turn off the digital input for adc0
}

void loop() {
  while(1) { // reduces jitter
    //cli();  // UDRE interrupt slows this way down on arduino1.0
    us1 = micros();
    for (int i = 0 ; i < FHT_N ; i++) { // save 256 samples
      while(!(ADCSRA & 0x10)); // wait for adc to be ready
      ADCSRA = 0xf5; // restart adc
      byte m = ADCL; // fetch adc data
      byte j = ADCH;
      int k = (j << 8) | m; // form into an int
      k -= 0x0200; // form into a signed int
      k <<= 6; // form into a 16b signed int
      fht_input[i] = k; // put real data into bins
    }
    us2 = micros();
    fht_window(); // window the data for better frequency response
    fht_reorder(); // reorder the data before doing the fht
    fht_run(); // process the data in the fht
    fht_mag_log(); // take the output of the fht
    //sei();
    Serial.print("Testing of math: ");
    Serial.print(us1);
    Serial.print(" - ");
    Serial.print(us2);
    Serial.print(" = ");
    Serial.println(us2 - us1);
    //Serial.write(255); // send a start byte
    //Serial.write(fht_log_out, FHT_N/2); // send out the data
  }
}
guest
Site Admin
 
Posts: 449
Joined: Thu May 20, 2010 11:58 pm

Re: Problems with FHT code

Postby guest » Fri Oct 26, 2012 12:26 am

ok, so after a bit of digging, it is specifically the pointer register that was causing the problem. i read up a bit on register usage:

http://www.nongnu.org/avr-libc/user-man ... _reg_usage

and it turns out that all functions that use r2:r17,r28:r29 must save and restore, and r1 must be cleared (which i was already doing). its not too much more to save everything, so i might just do that to be on the safe side anyways.
guest
Site Admin
 
Posts: 449
Joined: Thu May 20, 2010 11:58 pm

Re: Problems with FHT code

Postby guest » Fri Oct 26, 2012 1:47 am

a new file is up on the wiki
http://wiki.openmusiclabs.com/wiki/ArduinoFHT

i have yet to fix the FFT library
guest
Site Admin
 
Posts: 449
Joined: Thu May 20, 2010 11:58 pm

Re: Problems with FHT code

Postby taco » Fri Oct 26, 2012 9:51 am

Wow, thanks for fixing that so quick. It works great now! Thank so much.
taco
 
Posts: 4
Joined: Wed Oct 24, 2012 12:19 pm

Re: Problems with FHT code

Postby guest » Sat Oct 27, 2012 1:18 am

thanks for finding the bug. keep us posted on how your project turns out
guest
Site Admin
 
Posts: 449
Joined: Thu May 20, 2010 11:58 pm

Re: Problems with FHT code

Postby taco » Fri Nov 02, 2012 1:40 pm

For this project, I was developing a code base that can be used by our researchers and students. I've posted a blog entry here that shows the final code:

http://vtchl.illinois.edu/node/557

Thanks!
taco
 
Posts: 4
Joined: Wed Oct 24, 2012 12:19 pm


Return to Software

Who is online

Users browsing this forum: No registered users and 0 guests


cron